From 2f672b861fe10497231909cf85c15636285bfe50 Mon Sep 17 00:00:00 2001 From: edmand46 Date: Thu, 17 Sep 2026 12:27:51 +0300 Subject: [PATCH] docs: module call response, error codes, remove scenarios --- README.md | 120 +++++++----------- skills/rudder-go-sdk/SKILL.md | 46 +++---- skills/rudder-go-sdk/reference/inventory.md | 43 ------- .../rudder-go-sdk/reference/leaderboards.md | 54 -------- skills/rudder-go-sdk/reference/modules.md | 33 +++++ skills/rudder-go-sdk/reference/players.md | 8 +- skills/rudder-go-sdk/reference/quests.md | 52 -------- skills/rudder-go-sdk/reference/resources.md | 32 +++++ 8 files changed, 136 insertions(+), 252 deletions(-) delete mode 100644 skills/rudder-go-sdk/reference/inventory.md delete mode 100644 skills/rudder-go-sdk/reference/leaderboards.md create mode 100644 skills/rudder-go-sdk/reference/modules.md delete mode 100644 skills/rudder-go-sdk/reference/quests.md create mode 100644 skills/rudder-go-sdk/reference/resources.md diff --git a/README.md b/README.md index 979a8fa..d816d63 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ wire-формат, в прикладном коде его импортиров ## Handles -`client.Player(id)` и `client.Leaderboard(slug)` создают handle без -HTTP-запроса. Handles — сахар над batch-методами сервисов: каждая операция -отправляет batch из одного элемента с подставленным playerID/slug. +`client.Player(id)` создаёт handle без HTTP-запроса. Handle — сахар над +batch-методами сервисов: каждая операция отправляет batch из одного элемента +с подставленным playerID. ```go player := client.Player("p1") @@ -64,13 +64,6 @@ wallet, err := player.Wallet().Adjust(ctx, rudder.AdjustPlayerWalletParameters{ }) history, err := player.Wallet().History(ctx, rudder.WalletHistoryParameters{Limit: 50}) -item, err := player.Inventory().Adjust(ctx, rudder.AdjustPlayerInventoryParameters{ - Slug: "sword", - Amount: 1, - Reason: "quest reward", -}) -inventory, err := player.Inventory().List(ctx, rudder.ListInventoryParameters{Limit: 50}) - storage, err := player.Storage().Get(ctx, "settings") updated, err := player.Storage().Upsert(ctx, rudder.UpsertPlayerStorageParameters{ Type: "settings", @@ -78,10 +71,6 @@ updated, err := player.Storage().Upsert(ctx, rudder.UpsertPlayerStorageParameter }) err = player.Storage().Delete(ctx, rudder.DeletePlayerStorageParameters{Type: "settings"}) -quests, err := player.Quests().List(ctx) -err = player.Quests().Reset(ctx, rudder.PlayerQuestParameters{QuestSlug: "daily-1"}) -// ForceComplete и ForceClaim — аналогично - updatedPlayer, err := player.Update(ctx, rudder.UpdatePlayerParameters{Nickname: "Neo"}) banned, err := player.Ban(ctx, rudder.BanPlayerParameters{ Reason: "cheating", @@ -89,18 +78,6 @@ banned, err := player.Ban(ctx, rudder.BanPlayerParameters{ }) unbanned, err := player.Unban(ctx, "") err = player.Delete(ctx, "") - -board := client.Leaderboard("weekly") - -entries, err := board.List(ctx, rudder.ListLeaderboardEntriesParameters{Limit: 100}) -// entries.Entries []rudder.Rank, entries.NextCursor - -entry, err := board.Get(ctx, "p1") // *rudder.Rank с rank -around, err := board.GetAround(ctx, "p1", 10) // []rudder.Rank вокруг игрока - -err = board.Submit(ctx, []rudder.LeaderboardScore{{PlayerID: "p1", Score: 1500}}) -err = board.Update(ctx, []rudder.LeaderboardScore{{PlayerID: "p1", Score: 1600}}) -err = board.Delete(ctx, []string{"p1", "p2"}) ``` ## Batch-first API @@ -118,12 +95,6 @@ wallets, err := client.Wallet.Adjust(ctx, rudder.AdjustWalletParameters{ }, }) -items, err := client.Inventory.Adjust(ctx, rudder.AdjustInventoryParameters{ - Items: []rudder.InventoryAdjustment{ - {PlayerID: "p1", Slug: "sword", Amount: 1, Reason: "quest reward"}, - }, -}) - updated, err := client.Players.Update(ctx, rudder.UpdatePlayersParameters{ Items: []rudder.PlayerUpdate{ {PlayerID: "p1", Nickname: "Neo"}, @@ -143,8 +114,42 @@ unbanned, err := client.Players.Unban(ctx, rudder.UnbanPlayersParameters{ err = client.Players.Delete(ctx, rudder.DeletePlayersParameters{ PlayerIDs: []string{"p3"}, }) + +err = client.Storage.Upsert(ctx, rudder.UpsertStorageParameters{ + Items: []rudder.ProjectStorageInput{ + {Type: "config", Data: `{"event":"x2"}`}, + }, +}) + +err = client.Storage.Delete(ctx, rudder.DeleteStorageParameters{ + Types: []string{"config"}, +}) + +rows, err := client.Resources.Create(ctx, "entries", []rudder.ResourceBatchItem{ + {PlayerID: "p1", Data: map[string]any{"board": "weekly", "score": 1500}}, +}) +// Update и Delete — аналогично, строка задаётся полем ID ``` +## Модули + +Игровые фичи (инвентарь, квесты, лидерборды, магазины, battle pass, remote +config) — это установленные модули. Их данные доступны через +`client.Resources`, а функции с триггером `admin` вызываются через +`client.Modules.Call` (`POST /game/v1/modules/{module}/{fn}`): + +```go +out, err := client.Modules.Call(ctx, rudder.CallParameters{ + Module: "leaderboards", + Fn: "resetNow", + Args: map[string]any{"row": map[string]any{"id": boardID}}, +}) +// out.Result, out.Error, out.Log, out.Console; DryRun: true всегда откатывает транзакцию +``` + +Бизнес-ошибка модуля приходит как `*rudder.APIError` со статусом 422 и +`Code`, который задаёт сам модуль (произвольная строка, констант для неё нет). + ## Верификация авторизации `Players.VerifyAuth` — одиночный (не batch) вызов `POST /players/auth/verify`: @@ -165,60 +170,26 @@ if !rudder.VerifyWebhookSignature(secret, body, r.Header.Get("X-Signature")) { } ``` -err = client.Quests.Reset(ctx, rudder.PlayerQuestsParameters{ - Items: []rudder.PlayerQuestRef{ - {PlayerID: "p1", QuestSlug: "daily-1"}, - }, -}) -// ForceComplete и ForceClaim — аналогично - -err = client.Leaderboards.Submit(ctx, "weekly", []rudder.LeaderboardScore{ - {PlayerID: "p1", Score: 1500}, -}) -// Update и Delete — аналогично - -err = client.Storage.Upsert(ctx, rudder.UpsertStorageParameters{ - Items: []rudder.ProjectStorageInput{ - {Type: "config", Data: `{"event":"x2"}`}, - }, -}) - -err = client.Storage.Delete(ctx, rudder.DeleteStorageParameters{ - Types: []string{"config"}, -}) -``` - ## Каталоги и read-операции Курсорная пагинация: передавайте `Limit`/`Cursor`, следующую страницу запрашивайте по `NextCursor` из ответа. ```go -boards, err := client.Leaderboards.List(ctx, rudder.ListLeaderboardsParameters{Limit: 100}) -// boards.Items []rudder.Leaderboard, boards.NextCursor - -quests, err := client.Quests.List(ctx, rudder.ListQuestsParameters{ - Status: &status, // "active" или "archived", optional - Limit: 100, -}) -// quests.Items []rudder.Quest, quests.NextCursor - history, err := client.Wallet.History(ctx, playerID, rudder.WalletHistoryParameters{ Currency: ¤cy, // optional Limit: 50, }) // history.Entries []rudder.WalletTransaction, history.NextCursor -inventory, err := client.Inventory.List(ctx, playerID, rudder.ListInventoryParameters{ - Limit: 50, -}) -// inventory.Items []rudder.InventoryItem, inventory.NextCursor - storage, err := client.Storage.List(ctx, rudder.ListStorageParameters{ Limit: 100, Search: "config", // optional }) item, err := client.Storage.Get(ctx, "config") + +rows, err := client.Resources.List(ctx, "boards") +// rows.Items, rows.Total ``` ## Ошибки @@ -227,13 +198,16 @@ item, err := client.Storage.Get(ctx, "config") var apiErr *rudder.APIError if errors.As(err, &apiErr) { apiErr.Status // HTTP status - apiErr.Code // машинный код из тела ошибки gateway - apiErr.RequestID // корреляция с логами gateway + apiErr.Code // машинный код из тела ошибки + apiErr.RequestID // корреляция с логами сервера } ``` +Сгенерированные коды сервера — `forbidden` и `row_referenced`. Бизнес-ошибки +модулей (422) несут собственный код модуля. + ## Генерация -`models/` генерируется apigen'ом из `liveops-gateway/openapi.yaml` -(`make generate-openapi` в liveops-gateway). Руками не править. +`models/` генерируется apigen'ом из `liveops-backend/openapi/openapi.yaml` +(`make generate-openapi` в liveops-backend). Руками не править. Публичные имена — aliases в корне пакета `rudder` (`types.go`). diff --git a/skills/rudder-go-sdk/SKILL.md b/skills/rudder-go-sdk/SKILL.md index 8bcfdec..607f964 100644 --- a/skills/rudder-go-sdk/SKILL.md +++ b/skills/rudder-go-sdk/SKILL.md @@ -1,6 +1,6 @@ --- name: rudder-go-sdk -description: Use when working with the Rudder Go server SDK (package rudder, module hub.rudder.build/rudder/rudder-go-sdk/v2) for server-side admin operations — players, wallet, inventory, storage, quests, leaderboards, webhook signature verification. +description: Use when working with the Rudder Go server SDK (package rudder, module hub.rudder.build/rudder/rudder-go-sdk/v2) for server-side admin operations — players, wallet, storage, resource rows, admin module calls, webhook signature verification. --- # Rudder Go server SDK @@ -23,8 +23,8 @@ go get hub.rudder.build/rudder/rudder-go-sdk/v2@latest `AdminKey` is the Admin Key of one environment. A project has exactly two, `staging` and `prod` (app.rudder.build → project → Settings → Environments). The key identifies both the project and the environment, so no project ID and -no environment argument are ever passed; players, wallets, inventory, storage, -quests and leaderboard entries of the two environments are fully isolated. +no environment argument are ever passed; players, wallets, storage, resource +rows and installed modules of the two environments are fully isolated. Keep it server-side only, never in game clients. ```go @@ -37,8 +37,11 @@ client, err := rudder.New(rudder.Config{ }) ``` -`client` exposes services: `Players`, `Leaderboards`, `Wallet`, `Inventory`, -`Quests`, `Storage`. All methods take `context.Context` first. +`client` exposes services: `Players`, `Wallet`, `Storage`, `Resources`, +`Modules`. All methods take `context.Context` first. Game-design features +(inventory, quests, leaderboards, stores, battle pass, remote config) are +installed modules; reach their data through `Resources` and their admin +functions through `Modules`. Public types live in package `rudder` (aliases in `types.go`). The `models` package is generated wire format — never import it in application code and @@ -49,7 +52,7 @@ liveops-backend). All top-level mutating operations are batch-only. Request body is `{idempotencyKey?, items:[...]}` with `playerId` inside each item -(leaderboard delete takes `{playerIds:[...]}`, project-storage delete +(player unban and delete take `{playerIds:[...]}`, project-storage delete `{types:[...]}`). Rules: - at most 100 items per batch @@ -57,15 +60,14 @@ All top-level mutating operations are batch-only. Request body is - one optional `IdempotencyKey` per batch; retrying with the same key is safe - errors report the index and playerId of the failing item -Method naming is uniform: verb + entity (`List`/`Get`/`Submit`/`Update`/ +Method naming is uniform: verb + entity (`List`/`Get`/`Create`/`Update`/ `Upsert`/`Delete`/`Adjust`/`History`). ## Handles -`client.Player(id)` and `client.Leaderboard(slug)` return handles without any -HTTP request. Handles are sugar over the batch operations with n=1: the -handle injects `playerID`/`slug` into a single-item batch and unwraps the -first result. +`client.Player(id)` returns a handle without any HTTP request. The handle is +sugar over the batch operations with n=1: it injects `playerID` into a +single-item batch and unwraps the first result. ```go player := client.Player("p1") @@ -75,15 +77,10 @@ wallet, err := player.Wallet().Adjust(ctx, rudder.AdjustPlayerWalletParameters{ Amount: 100, Reason: "compensation", }) - -board := client.Leaderboard("weekly") -err = board.Submit(ctx, []rudder.LeaderboardScore{{PlayerID: "p1", Score: 1500}}) ``` -`client.Player(id)` exposes sub-handles `Wallet()`, `Inventory()`, -`Storage()`, `Quests()` plus `Update`/`Ban`/`Unban`/`Delete`. -`client.Leaderboard(slug)` exposes `List`/`Get`/`GetAround`/`Submit`/ -`Update`/`Delete`. +`client.Player(id)` exposes sub-handles `Wallet()` and `Storage()` plus +`Update`/`Ban`/`Unban`/`Delete`. ## Top-level Storage vs PlayerHandle.Storage() @@ -97,10 +94,10 @@ confuse them. All API failures return `*rudder.APIError` (`Status`, `Code`, `Message`, `RequestID`); use `errors.As`. Client-side misconfiguration returns -`Code: "sdk/invalid-options"` (`rudder.ErrorCodeInvalidOptions`). Scenario -engine codes surfaced in `Code` include `early_completion`, `forbidden`, -`level_not_reached`, `node_not_active`, `objectives_incomplete`, -`run_expired`, `run_not_active`, `scenario_not_active`, `unknown_run`. +`Code: "sdk/invalid-options"` (`rudder.ErrorCodeInvalidOptions`). The +generated server codes are `forbidden` and `row_referenced`; other admin +errors carry unstructured code strings. A module business error is a 422 whose +`Code` is defined by the module (an open string, no generated constants). ## Webhook verification @@ -114,7 +111,6 @@ if !rudder.VerifyWebhookSignature(secret, body, r.Header.Get("X-Signature")) { - `reference/players.md` — list/get/verify-auth, batch update/ban/unban/delete, PlayerHandle - `reference/wallet.md` — batch adjust, history, PlayerWalletHandle -- `reference/inventory.md` — batch adjust, list, PlayerInventoryHandle - `reference/storage.md` — project storage (top-level) and player storage (handle) -- `reference/quests.md` — quest definitions list, batch reset/force-complete/force-claim, PlayerQuestsHandle -- `reference/leaderboards.md` — definitions list, LeaderboardHandle entries +- `reference/resources.md` — batch create/update/delete and list of resource rows +- `reference/modules.md` — admin module function calls, dry run, module errors diff --git a/skills/rudder-go-sdk/reference/inventory.md b/skills/rudder-go-sdk/reference/inventory.md deleted file mode 100644 index 3e25339..0000000 --- a/skills/rudder-go-sdk/reference/inventory.md +++ /dev/null @@ -1,43 +0,0 @@ -# Inventory - -Source: `inventory.go`, `player_inventory.go`. Types: `InventoryItem`, `InventoryList`, `InventoryAdjustment`. - -## Batch adjust - -```go -func (s *InventoryService) Adjust(ctx context.Context, parameters AdjustInventoryParameters) ([]InventoryItem, error) -type AdjustInventoryParameters struct { - IdempotencyKey string - Items []InventoryAdjustment -} -// InventoryAdjustment: {PlayerID, Slug, Amount int64, Reason} -// Negative Amount removes items. Returns updated items in item order. -``` - -## List - -```go -func (s *InventoryService) List(ctx context.Context, playerID string, parameters ListInventoryParameters) (*InventoryList, error) -type ListInventoryParameters struct { - Limit int - Cursor *string -} -// InventoryList: {Items []InventoryItem, NextCursor string} -// InventoryItem: {ID, Slug, Amount int64, UpdatedAt} -``` - -## PlayerInventoryHandle - -`client.Player(id).Inventory()`: - -```go -func (h *PlayerInventoryHandle) Adjust(ctx context.Context, parameters AdjustPlayerInventoryParameters) (*InventoryItem, error) -type AdjustPlayerInventoryParameters struct { - IdempotencyKey string - Slug string - Amount int64 - Reason string -} - -func (h *PlayerInventoryHandle) List(ctx context.Context, parameters ListInventoryParameters) (*InventoryList, error) -``` diff --git a/skills/rudder-go-sdk/reference/leaderboards.md b/skills/rudder-go-sdk/reference/leaderboards.md deleted file mode 100644 index 86cde53..0000000 --- a/skills/rudder-go-sdk/reference/leaderboards.md +++ /dev/null @@ -1,54 +0,0 @@ -# Leaderboards - -Source: `leaderboards.go` (definitions + batch entry mutations), `leaderboard.go` (handle). -Types: `Leaderboard`, `LeaderboardList`, `LeaderboardScore`, `Rank`, `RankList`. - -## Definitions - -```go -func (s *LeaderboardsService) List(ctx context.Context, parameters ListLeaderboardsParameters) (*LeaderboardList, error) -type ListLeaderboardsParameters struct { - Limit int - Cursor *string -} -// LeaderboardList: {Items []Leaderboard, NextCursor string} -// Leaderboard: {Slug, Name, Metric, ResetPeriod, SortingOrder, MaxEntries int64} -``` - -## Batch entry mutations (service level) - -Note: leaderboard entry mutations do NOT take an IdempotencyKey — the generated -batch request bodies carry only items/playerIds. - -```go -func (s *LeaderboardsService) Submit(ctx context.Context, slug string, items []LeaderboardScore) error -func (s *LeaderboardsService) Update(ctx context.Context, slug string, items []LeaderboardScore) error -// LeaderboardScore: {PlayerID, Score float64} - -func (s *LeaderboardsService) Delete(ctx context.Context, slug string, playerIDs []string) error -// body: {playerIds: [...]} -``` - -## LeaderboardHandle - -`client.Leaderboard(slug)` — no request at creation. - -```go -func (h *LeaderboardHandle) List(ctx context.Context, parameters ListLeaderboardEntriesParameters) (*RankList, error) -type ListLeaderboardEntriesParameters struct { - Limit int - Cursor *string -} -// RankList: {Entries []Rank, NextCursor string} - -func (h *LeaderboardHandle) Get(ctx context.Context, playerID string) (*Rank, error) - -func (h *LeaderboardHandle) GetAround(ctx context.Context, playerID string, limit int) ([]Rank, error) -// entries around the player's rank - -func (h *LeaderboardHandle) Submit(ctx context.Context, items []LeaderboardScore) error -func (h *LeaderboardHandle) Update(ctx context.Context, items []LeaderboardScore) error -func (h *LeaderboardHandle) Delete(ctx context.Context, playerIDs []string) error -``` - -`Rank`: `{PlayerID, PlayerName, Rank int64, Score float64}`. diff --git a/skills/rudder-go-sdk/reference/modules.md b/skills/rudder-go-sdk/reference/modules.md new file mode 100644 index 0000000..0ed883e --- /dev/null +++ b/skills/rudder-go-sdk/reference/modules.md @@ -0,0 +1,33 @@ +# Modules + +Source: `modules.go`. Calls an installed module function with trigger `admin` +through `POST /game/v1/modules/{module}/{fn}`, in the admin key's environment. + +```go +func (s *ModulesService) Call(ctx context.Context, parameters CallParameters) (*CallResult, error) +type CallParameters struct { + Module string + Fn string + Args any // marshalled to JSON as "args" + DryRun bool // true: the call always rolls back +} +type CallResult struct { + Result any // "result" + Error string // "error" + Log []any // "log": kernel calls made by the function + Console []string // "console": captured stdout/stderr +} +``` + +```go +out, err := client.Modules.Call(ctx, rudder.CallParameters{ + Module: "leaderboards", + Fn: "resetNow", + Args: map[string]any{"row": map[string]any{"id": boardID}}, +}) +``` + +A business error raised by the module (`Fail(code, message)` in a module SDK) +returns `*rudder.APIError` with `Status` 422 and `Code` set to the module's +own code. Module codes are open strings; there are no generated constants for +them. A trap returns 500 and a timeout 504, both without details. diff --git a/skills/rudder-go-sdk/reference/players.md b/skills/rudder-go-sdk/reference/players.md index 1e85823..c4f5c43 100644 --- a/skills/rudder-go-sdk/reference/players.md +++ b/skills/rudder-go-sdk/reference/players.md @@ -14,7 +14,7 @@ type ListPlayersParameters struct { // PlayerList: {Players []Player, Total int, Limit int, Offset int} func (s *PlayersService) Get(ctx context.Context, playerID string) (*PlayerDetails, error) -// PlayerDetails: {Player Player, Wallets []WalletBalance, Inventory []InventoryItem, +// PlayerDetails: {Player Player, Identities []PlayerIdentity, Wallets []WalletBalance, // Storages []PlayerStorage, WalletTransactions []WalletTransaction} func (s *PlayersService) VerifyAuth(ctx context.Context, accessToken string) (*PlayerAuthVerification, error) @@ -57,10 +57,8 @@ type DeletePlayersParameters struct { `client.Player(id)` — no request at creation. ```go -h.Wallet() *PlayerWalletHandle -h.Inventory() *PlayerInventoryHandle -h.Storage() *PlayerStorageHandle -h.Quests() *PlayerQuestsHandle +h.Wallet() *PlayerWalletHandle +h.Storage() *PlayerStorageHandle func (h *PlayerHandle) Update(ctx context.Context, parameters UpdatePlayerParameters) (*Player, error) type UpdatePlayerParameters struct { diff --git a/skills/rudder-go-sdk/reference/quests.md b/skills/rudder-go-sdk/reference/quests.md deleted file mode 100644 index f206eaf..0000000 --- a/skills/rudder-go-sdk/reference/quests.md +++ /dev/null @@ -1,52 +0,0 @@ -# Quests - -Source: `quests.go`, `player_quests.go`. Types: `Quest`, `QuestObjective`, `QuestReward`, `QuestList`, `PlayerQuest`, `PlayerQuestObjective`, `PlayerQuestRef`. - -## Quest definitions (catalog) - -```go -func (s *QuestsService) List(ctx context.Context, parameters ListQuestsParameters) (*QuestList, error) -type ListQuestsParameters struct { - Status *string // optional: "active" or "archived" - Limit int - Cursor *string -} -// QuestList: {Items []Quest, NextCursor string} -// Quest: {ID, Slug, Name, Status, NextQuestID, Objectives []QuestObjective, Rewards []QuestReward} -// QuestObjective: {ID, Metric, Target int64} -// QuestReward: {Amount int64, Currency, ItemID} -``` - -## Batch player-quest operations - -All three share the same parameters and return only `error`: - -```go -func (s *QuestsService) Reset(ctx context.Context, parameters PlayerQuestsParameters) error -func (s *QuestsService) ForceComplete(ctx context.Context, parameters PlayerQuestsParameters) error -func (s *QuestsService) ForceClaim(ctx context.Context, parameters PlayerQuestsParameters) error - -type PlayerQuestsParameters struct { - IdempotencyKey string - Items []PlayerQuestRef // {PlayerID, QuestSlug} -} -``` - -## PlayerQuestsHandle - -`client.Player(id).Quests()`: - -```go -func (h *PlayerQuestsHandle) List(ctx context.Context) ([]PlayerQuest, error) -// PlayerQuest: {QuestSlug, Name, Status, Objectives []PlayerQuestObjective} -// PlayerQuestObjective: {ObjectiveID, Current int64, Target int64} - -func (h *PlayerQuestsHandle) Reset(ctx context.Context, parameters PlayerQuestParameters) error -func (h *PlayerQuestsHandle) ForceComplete(ctx context.Context, parameters PlayerQuestParameters) error -func (h *PlayerQuestsHandle) ForceClaim(ctx context.Context, parameters PlayerQuestParameters) error - -type PlayerQuestParameters struct { - IdempotencyKey string - QuestSlug string -} -``` diff --git a/skills/rudder-go-sdk/reference/resources.md b/skills/rudder-go-sdk/reference/resources.md new file mode 100644 index 0000000..d46e007 --- /dev/null +++ b/skills/rudder-go-sdk/reference/resources.md @@ -0,0 +1,32 @@ +# Resources + +Source: `resources.go`. Batch row operations on a resource through +`/game/v1/resources/{slug}/rows`. + +```go +type ResourceBatchItem struct { + PlayerID string // "playerId": owner for player-scoped resources + ID string // "id", omitempty: row id for update and delete + Data map[string]any // "data", omitempty: row fields + Version int64 // "version", omitempty +} +type ResourceRowsPage struct { + Items []any // "items" + Total int // "total" +} + +func (s *ResourcesService) List(ctx context.Context, slug string) (*ResourceRowsPage, error) // GET +func (s *ResourcesService) Create(ctx context.Context, slug string, items []ResourceBatchItem) (*ResourceRowsPage, error) // POST +func (s *ResourcesService) Update(ctx context.Context, slug string, items []ResourceBatchItem) (*ResourceRowsPage, error) // PATCH +func (s *ResourcesService) Delete(ctx context.Context, slug string, items []ResourceBatchItem) error // DELETE +``` + +```go +rows, err := client.Resources.Create(ctx, "entries", []rudder.ResourceBatchItem{ + {PlayerID: "p1", Data: map[string]any{"board": "weekly", "score": 1500}}, +}) +``` + +Deleting a row that other rows still reference fails with `Status` 409 and +`Code` `row_referenced`. A failing batch +item fails the whole batch with 400.