@crowdedkingdoms/crowdyjs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/MIGRATION.md +247 -0
  3. package/README.md +303 -0
  4. package/dist/auth-state.d.ts +11 -0
  5. package/dist/auth-state.d.ts.map +1 -0
  6. package/dist/auth-state.js +13 -0
  7. package/dist/client.d.ts +135 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +150 -0
  10. package/dist/crowdy-client.d.ts +182 -0
  11. package/dist/crowdy-client.d.ts.map +1 -0
  12. package/dist/crowdy-client.js +146 -0
  13. package/dist/domains/actors.d.ts +117 -0
  14. package/dist/domains/actors.d.ts.map +1 -0
  15. package/dist/domains/actors.js +140 -0
  16. package/dist/domains/admin.d.ts +61 -0
  17. package/dist/domains/admin.d.ts.map +1 -0
  18. package/dist/domains/admin.js +33 -0
  19. package/dist/domains/appAccess.d.ts +141 -0
  20. package/dist/domains/appAccess.d.ts.map +1 -0
  21. package/dist/domains/appAccess.js +198 -0
  22. package/dist/domains/apps.d.ts +192 -0
  23. package/dist/domains/apps.d.ts.map +1 -0
  24. package/dist/domains/apps.js +217 -0
  25. package/dist/domains/auth.d.ts +163 -0
  26. package/dist/domains/auth.d.ts.map +1 -0
  27. package/dist/domains/auth.js +208 -0
  28. package/dist/domains/avatars.d.ts +94 -0
  29. package/dist/domains/avatars.d.ts.map +1 -0
  30. package/dist/domains/avatars.js +137 -0
  31. package/dist/domains/billing.d.ts +97 -0
  32. package/dist/domains/billing.d.ts.map +1 -0
  33. package/dist/domains/billing.js +131 -0
  34. package/dist/domains/channels.d.ts +293 -0
  35. package/dist/domains/channels.d.ts.map +1 -0
  36. package/dist/domains/channels.js +353 -0
  37. package/dist/domains/chunks.d.ts +133 -0
  38. package/dist/domains/chunks.d.ts.map +1 -0
  39. package/dist/domains/chunks.js +153 -0
  40. package/dist/domains/controlPlane.d.ts +174 -0
  41. package/dist/domains/controlPlane.d.ts.map +1 -0
  42. package/dist/domains/controlPlane.js +252 -0
  43. package/dist/domains/environments.d.ts +155 -0
  44. package/dist/domains/environments.d.ts.map +1 -0
  45. package/dist/domains/environments.js +223 -0
  46. package/dist/domains/gameApps.d.ts +114 -0
  47. package/dist/domains/gameApps.d.ts.map +1 -0
  48. package/dist/domains/gameApps.js +169 -0
  49. package/dist/domains/gameModel.d.ts +668 -0
  50. package/dist/domains/gameModel.d.ts.map +1 -0
  51. package/dist/domains/gameModel.js +816 -0
  52. package/dist/domains/host.d.ts +35 -0
  53. package/dist/domains/host.d.ts.map +1 -0
  54. package/dist/domains/host.js +40 -0
  55. package/dist/domains/organizations.d.ts +179 -0
  56. package/dist/domains/organizations.d.ts.map +1 -0
  57. package/dist/domains/organizations.js +269 -0
  58. package/dist/domains/payments.d.ts +104 -0
  59. package/dist/domains/payments.d.ts.map +1 -0
  60. package/dist/domains/payments.js +129 -0
  61. package/dist/domains/platform.d.ts +49 -0
  62. package/dist/domains/platform.d.ts.map +1 -0
  63. package/dist/domains/platform.js +50 -0
  64. package/dist/domains/quotas.d.ts +62 -0
  65. package/dist/domains/quotas.d.ts.map +1 -0
  66. package/dist/domains/quotas.js +79 -0
  67. package/dist/domains/serverStatus.d.ts +90 -0
  68. package/dist/domains/serverStatus.d.ts.map +1 -0
  69. package/dist/domains/serverStatus.js +104 -0
  70. package/dist/domains/sharedEnvironment.d.ts +133 -0
  71. package/dist/domains/sharedEnvironment.d.ts.map +1 -0
  72. package/dist/domains/sharedEnvironment.js +179 -0
  73. package/dist/domains/state.d.ts +64 -0
  74. package/dist/domains/state.d.ts.map +1 -0
  75. package/dist/domains/state.js +75 -0
  76. package/dist/domains/teams.d.ts +292 -0
  77. package/dist/domains/teams.d.ts.map +1 -0
  78. package/dist/domains/teams.js +352 -0
  79. package/dist/domains/teleport.d.ts +41 -0
  80. package/dist/domains/teleport.d.ts.map +1 -0
  81. package/dist/domains/teleport.js +43 -0
  82. package/dist/domains/udp.d.ts +405 -0
  83. package/dist/domains/udp.d.ts.map +1 -0
  84. package/dist/domains/udp.js +457 -0
  85. package/dist/domains/usage.d.ts +76 -0
  86. package/dist/domains/usage.d.ts.map +1 -0
  87. package/dist/domains/usage.js +110 -0
  88. package/dist/domains/users.d.ts +147 -0
  89. package/dist/domains/users.d.ts.map +1 -0
  90. package/dist/domains/users.js +195 -0
  91. package/dist/domains/voxels.d.ts +136 -0
  92. package/dist/domains/voxels.d.ts.map +1 -0
  93. package/dist/domains/voxels.js +153 -0
  94. package/dist/errors.d.ts +158 -0
  95. package/dist/errors.d.ts.map +1 -0
  96. package/dist/errors.js +142 -0
  97. package/dist/generated/graphql.d.ts +12206 -0
  98. package/dist/generated/graphql.d.ts.map +1 -0
  99. package/dist/generated/graphql.js +474 -0
  100. package/dist/index.d.ts +84 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +85 -0
  103. package/dist/logger.d.ts +8 -0
  104. package/dist/logger.d.ts.map +1 -0
  105. package/dist/logger.js +1 -0
  106. package/dist/realtime.d.ts +319 -0
  107. package/dist/realtime.d.ts.map +1 -0
  108. package/dist/realtime.js +390 -0
  109. package/dist/session.d.ts +73 -0
  110. package/dist/session.d.ts.map +1 -0
  111. package/dist/session.js +96 -0
  112. package/dist/subscriptions.d.ts +2 -0
  113. package/dist/subscriptions.d.ts.map +1 -0
  114. package/dist/subscriptions.js +1 -0
  115. package/dist/types.d.ts +658 -0
  116. package/dist/types.d.ts.map +1 -0
  117. package/dist/types.js +61 -0
  118. package/dist/utils.d.ts +98 -0
  119. package/dist/utils.d.ts.map +1 -0
  120. package/dist/utils.js +136 -0
  121. package/dist/world.d.ts +236 -0
  122. package/dist/world.d.ts.map +1 -0
  123. package/dist/world.js +275 -0
  124. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CrowdedKingdoms
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/MIGRATION.md ADDED
@@ -0,0 +1,247 @@
1
+ # CrowdyJS 1.0.0 — npm org rename
2
+
3
+ The package moved to the **`@crowdedkingdoms`** npm organization and its version
4
+ line was reset:
5
+
6
+ - **Old:** `@crowdedkingdomstudios/crowdyjs@6.1.0`
7
+ - **New:** `@crowdedkingdoms/crowdyjs@1.0.0` — **identical code**, new package name.
8
+
9
+ To upgrade, change your install and imports:
10
+
11
+ ```bash
12
+ npm uninstall @crowdedkingdomstudios/crowdyjs
13
+ npm install @crowdedkingdoms/crowdyjs
14
+ ```
15
+
16
+ ```ts
17
+ // before: import { createCrowdyClient } from '@crowdedkingdomstudios/crowdyjs';
18
+ import { createCrowdyClient } from '@crowdedkingdoms/crowdyjs';
19
+ // generated docs export likewise: '@crowdedkingdoms/crowdyjs/generated'
20
+ ```
21
+
22
+ No API, behavior, or type changes vs `@crowdedkingdomstudios/crowdyjs@6.1.0`. The
23
+ old package is deprecated and points here. The notes below (kept for history)
24
+ describe the feature set as of the former 6.x line, which `1.0.0` ships as-is.
25
+
26
+ # CrowdyJS v6.1 Notes
27
+
28
+ v6.1 is **additive** — new methods and fields only, no breaking changes. It
29
+ completes the "full-surface" goal so every non-deprecated public root field on
30
+ both APIs now has a typed SDK method, and adds Relay cursor-pagination variants
31
+ alongside the existing offset list methods.
32
+
33
+ ## Added
34
+
35
+ - **Grids**: `client.gameApps.deleteGrid(input)` (also `client.admin.grids.deleteGrid`)
36
+ — delete a studio-created peer grid (game-api `deleteGrid`, requires
37
+ `cks-game-api >= v0.12.3`).
38
+ - **Game model (studio reads + revoke)**: `client.gameModel.containerTypes`,
39
+ `propertyDefs`, `getFunction`, `functions`, `features`, `tierFeatures`,
40
+ `policy`, and `revokeTierFeature`.
41
+ - **Management admin reads/mutations**: `client.users.{get, paginated, setOperator,
42
+ setSuperAdmin, setEarlyAccessOverride, updateType, forceLogout, updateState,
43
+ freePlayWindow}`; `client.organizations.memberRoles`;
44
+ `client.appAccess.{runtimePermissions, grantMemberCandidates, claimFree, grantMine}`;
45
+ `client.apps.marketplace`; `client.billing.{buddyTiers, graphqlTiers,
46
+ postgresTiers}`; `client.environments.updateBillingTiers`;
47
+ `client.payments.{capturePaypal, events}`; `client.usage.playerPulse`.
48
+ - **Relay `*Connection` variants** (preferred over the deprecated offset lists):
49
+ `client.actors.listConnection`, `client.voxels.historyConnection`,
50
+ `client.gameModel.eventsConnection`, `client.users.listConnection`,
51
+ `client.apps.marketplaceConnection`, `client.appAccess.usersByAppConnection`,
52
+ `client.billing.walletTransactionsConnection`,
53
+ `client.payments.{mineConnection, allConnection, eventsConnection}`.
54
+ - **New fields on existing operations**: `environmentQuote` /
55
+ `orgEnvironment(s)` now return `environmentClass` + `singleBoxFlavor`;
56
+ `appUsageSummary` now returns `automationRuns` / `automationInvocations` /
57
+ `automationComputeUnits`.
58
+
59
+ ## Server compatibility
60
+
61
+ `deleteGrid` requires a server on release **v0.1.33+** (`cks-game-api >= v0.12.3`).
62
+ The new management fields require `cks-management-api` recent enough to expose them.
63
+ All additions are backward compatible at the SDK API level.
64
+
65
+ # CrowdyJS v6 Notes
66
+
67
+ v6 is **additive** at the SDK API level (new sub-clients only) but is a **scope
68
+ change**: CrowdyJS now wraps the **full** public management-api + game-api surface
69
+ instead of just the game-client subset. Existing sub-clients (`auth`, `users`,
70
+ `udp`, `world`, `chunks`/`voxels`/`actors`/`avatars`/`state`/`teleport`/`channels`/
71
+ `teams`/`gameModel`) are unchanged — no migration needed for existing code.
72
+
73
+ ## Added
74
+
75
+ - **Studio-admin sub-clients** (target `managementUrl`): `client.organizations`,
76
+ `client.appAccess`, `client.billing`, `client.payments`, `client.quotas`,
77
+ `client.environments`, `client.usage`, `client.sharedEnvironment`, and
78
+ `client.gameApps` (grid admin, game-api). All are also grouped under a
79
+ `client.admin` facade for discoverability (`client.admin.organizations`, …,
80
+ `client.admin.grids`).
81
+ - **Operator surface**: `client.operator` (control plane — environments, change
82
+ orders, secrets, release management, audit). Requires `users.is_operator`.
83
+ - **Game-side**: `client.avatars` (durable avatars + per-app avatar state — the
84
+ README previously referenced this before it existed) and `client.host`
85
+ (game-host election + actor `heartbeat`).
86
+
87
+ ## Security note
88
+
89
+ These admin/operator operations are **privileged**. The SDK only provides typed
90
+ wrappers; the server still enforces the org/app permission (or `is_operator`) on
91
+ every call. Drive `client.admin.*` from a studio backend with an org-scoped/admin
92
+ token and `client.operator` from internal tooling — **not** from an untrusted
93
+ browser. The game-client surface remains browser-safe with an end-user token.
94
+
95
+ ---
96
+
97
+ # CrowdyJS v5.2.1 Notes
98
+
99
+ v5.2.1 is **documentation-only** — no API, type, or behavior changes.
100
+
101
+ ## Changed
102
+
103
+ - Comprehensive TSDoc across the entire public surface (every sub-client class
104
+ and method, the error classes, the realtime types, the token store, and the
105
+ config). Descriptions mirror the GraphQL schema's field semantics and add
106
+ SDK-specific notes — auth/permission requirements, the stable
107
+ `extensions.code`s each call can throw, encoding/units conventions (`BigInt`
108
+ as decimal strings, base64 blobs, 32-char actor ids, chunk-unit distances),
109
+ idempotency-key replay/`IDEMPOTENCY_CONFLICT` behavior, and realtime
110
+ `...AndWait` echo/timeout semantics. These now show up on hover in your IDE
111
+ and in the published `.d.ts`.
112
+ - Two doc-accuracy fixes: `...AndWait` echo timeouts reject with
113
+ `CrowdyRealtimeError` (`code === 'UDP_SEQUENCE_TIMEOUT'`), not
114
+ `CrowdyTimeoutError`; and only actor/voxel sends echo to the sender, so the
115
+ audio/text/event `...AndWait` variants are documented as fire-and-forget-with-error-wait.
116
+
117
+ ---
118
+
119
+ # CrowdyJS v5.2 Notes
120
+
121
+ v5.2 is additive at the SDK API level (new optional parameters only) and
122
+ refreshes the bundled schema, but it **raises the minimum server version**.
123
+
124
+ ## Added
125
+
126
+ - **Idempotency keys on destructive mutations.** The four destructive
127
+ game-client mutations now accept an optional idempotency key. Replaying the
128
+ same call with the same key returns the first result instead of re-applying
129
+ the side effect; the same key with different arguments returns an
130
+ `IDEMPOTENCY_CONFLICT` error. Keys expire server-side after 24h.
131
+
132
+ ```ts
133
+ const key = crypto.randomUUID();
134
+ await client.actors.delete(uuid, key); // first call deletes
135
+ await client.actors.delete(uuid, key); // retry replays the first result
136
+ await client.teams.remove(groupId, key);
137
+ await client.teams.leave(groupId, key);
138
+ await client.voxels.rollback({ ...input, idempotencyKey: key }); // input field
139
+ ```
140
+
141
+ All four parameters are optional and trailing, so existing call sites are
142
+ unchanged.
143
+
144
+ - **Refreshed bundled schema.** Re-synced against `cks-management-api` and
145
+ `cks-game-api` so generated types now include the new Relay-style `*Connection`
146
+ queries (offset `limit`/`offset` args are now marked `@deprecated`), the
147
+ machine-readable `@requiresPermission` directive metadata, and the enumerated
148
+ error codes. `CrowdyGraphQLError` already surfaces these via `extensions.code`,
149
+ `extensions.remediation`, and `extensions.requiredPermission` — no new error
150
+ class is needed.
151
+
152
+ ## Requires
153
+
154
+ - `cks-game-api >= v0.10.3` and `cks-management-api >= v0.1.70`. The destructive
155
+ mutation documents now send the `idempotencyKey` argument, so those four
156
+ operations require a server that defines it. Point the SDK at an environment
157
+ running release **v0.1.19** or later.
158
+
159
+ ---
160
+
161
+ # CrowdyJS v5.1 Notes
162
+
163
+ v5.1 is additive and non-breaking.
164
+
165
+ ## Added
166
+
167
+ - **`client.teams`** — the Teams API is now a first-class sub-client, mirroring
168
+ `client.channels`. Create / update / delete teams, manage membership and
169
+ roles, set the per-app team policy, and read `mine` (`myTeams`), `list`
170
+ (`teams`), `get`, `members`, `roles`, and `policy`. Teams are app-scoped
171
+ player groups with roles and delegated management (no realtime messaging
172
+ path — that is Channels).
173
+
174
+ ```ts
175
+ const team = await client.teams.create({ appId: '1', name: 'Red Squad' });
176
+ await client.teams.join(team.groupId);
177
+ const mine = await client.teams.mine('1');
178
+ ```
179
+
180
+ ## Removed
181
+
182
+ - The `gameModelEventStream` GraphQL subscription has been removed from the Game
183
+ API and the bundled schema. It was never wrapped by a CrowdyJS method, so no
184
+ SDK call sites change. To react to game-model changes, have the mutating
185
+ client send a lightweight notification over the realtime UDP path — a channel
186
+ message (`client.udp.sendChannelMessage`, recommended) or a spatial client
187
+ event (`client.udp.sendClientEvent`) — and have peers re-pull authoritative
188
+ state via `client.gameModel.containerState(...)` / `client.gameModel.events(...)`.
189
+
190
+ ---
191
+
192
+ # CrowdyJS v5 Migration Notes
193
+
194
+ CrowdyJS v5 makes the realtime subscription **app-scoped** to fix a cross-app
195
+ notification leak: a single game token is app-agnostic and one UDP proxy
196
+ session is shared by every subscription on that token, so a token reused across
197
+ apps (e.g. a player in multiple tabs/apps) used to receive other apps' spatial
198
+ fan-out.
199
+
200
+ ## Breaking change
201
+
202
+ - `client.udp.subscribe(handlers, appId)` — **`appId` is now required**. The
203
+ game-api fences `udpNotifications` by app and **rejects app-agnostic
204
+ subscriptions** with a `RealtimeConnectionEvent` `code = 'APP_ID_REQUIRED'`.
205
+
206
+ ```ts
207
+ // Before (v4):
208
+ client.udp.subscribe({ actorUpdate });
209
+ // After (v5):
210
+ client.udp.subscribe({ actorUpdate }, '1');
211
+ // Or use the world helper, which passes its appId automatically:
212
+ client.world('1').subscribe({ actorUpdate });
213
+ ```
214
+
215
+ Run one client per app (sharing the same `tokenStore`) when a player is in
216
+ multiple apps at once.
217
+
218
+ - Requires a game-api that enforces the app fence (`cks-game-api >= v0.9.0`).
219
+
220
+ ---
221
+
222
+ # CrowdyJS v3 Migration Notes
223
+
224
+ CrowdyJS v3 is a breaking rewrite focused on browser game clients.
225
+
226
+ ## Main Changes
227
+
228
+ - Use `createCrowdyClient()` or `new CrowdyClient()` with `httpUrl` and `wsUrl`.
229
+ - Use `client.auth.login({ email, password })` instead of `client.login(email, password)`.
230
+ - Use `client.udp.subscribe({ actorUpdate })` instead of `client.onActorUpdate(...)`.
231
+ - Use `client.udp.sendActorUpdate(...)` or `client.udp.sendActorUpdateAndWait(...)` instead of root-level send methods.
232
+ - Use `client.udp.disconnect()` instead of `client.disconnectUdpProxy()`.
233
+ - Use `client.session` for token restore, manual token injection, and token persistence.
234
+ - Use `client.realtime.onStatus()` for connection state and reconnect visibility.
235
+ - Import generated operation documents from `@crowdedkingdoms/crowdyjs/generated`.
236
+
237
+ ## API Field Renames
238
+
239
+ - `CreateGridInput.app_id` is now `CreateGridInput.appId`.
240
+ - `TeleportRequestInput.UUID` is now `TeleportRequestInput.uuid`.
241
+ - `connectUdpProxy` takes no input.
242
+
243
+ ## Error Handling
244
+
245
+ GraphQL failures now throw `CrowdyGraphQLError`, preserving every GraphQL error
246
+ including `path` and `extensions.code`. Realtime failures use
247
+ `CrowdyRealtimeError` and subscription-level `RealtimeConnectionEvent` payloads.
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # CrowdyJS
2
+
3
+ The official browser-first TypeScript SDK for **Crowded Kingdoms**. CrowdyJS gives you one typed client that handles auth, the world/replication GraphQL API, and the UDP proxy subscription stream behind a single shared session.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @crowdedkingdoms/crowdyjs
9
+ ```
10
+
11
+ > **Renamed package.** This SDK moved to the `@crowdedkingdoms` npm org. `@crowdedkingdoms/crowdyjs@1.0.0` is the **same code** as the former `@crowdedkingdomstudios/crowdyjs@6.1.0` — only the package name changed. See [MIGRATION.md](MIGRATION.md).
12
+
13
+ CrowdyJS v4 targets browsers by default and uses native `fetch`, `WebSocket`, `crypto`, `btoa`, and `atob`. Node tools can still use the SDK, but must provide browser-compatible globals when opening realtime connections.
14
+
15
+ > **Server compatibility:** v5.2+ targets environments on release **v0.1.19 or later** (`cks-game-api >= v0.10.3`, `cks-management-api >= v0.1.70`). The destructive mutations send an `idempotencyKey` argument that older servers don't define. v6.1's `client.gameApps.deleteGrid` additionally requires release **v0.1.33+** (`cks-game-api >= v0.12.3`).
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import {
21
+ BrowserLocalStorageTokenStore,
22
+ createCrowdyClient,
23
+ } from '@crowdedkingdoms/crowdyjs';
24
+
25
+ const client = createCrowdyClient({
26
+ // Game API (world data + UDP proxy)
27
+ httpUrl: 'https://game.example.com',
28
+ wsUrl: 'wss://game.example.com',
29
+ // Management API (login, register, profile)
30
+ managementUrl: 'https://management.example.com',
31
+ tokenStore: new BrowserLocalStorageTokenStore(),
32
+ realtime: {
33
+ retryAttempts: 8,
34
+ waitTimeoutMs: 5000,
35
+ },
36
+ });
37
+
38
+ // Restore a previous session if there is one, otherwise log in.
39
+ await client.session.restore();
40
+ if (!client.session.getToken()) {
41
+ await client.auth.login({ email: 'player@example.com', password: 'secret' });
42
+ }
43
+
44
+ // Fetch the per-app bootstrap (version requirements, UDP availability, spatial limits).
45
+ const bootstrap = await client.serverStatus.gameClientBootstrap('1');
46
+ console.log(bootstrap.versionInfo.minimumClientVersion);
47
+ ```
48
+
49
+ Both endpoints share a single `AuthState`, so once `client.auth.login()` returns, every subsequent SDK call (against either endpoint) carries the bearer token automatically.
50
+
51
+ If `managementUrl` is omitted, the SDK falls back to `httpUrl` for backwards-compat with the single-endpoint deployment.
52
+
53
+ ## Sub-clients at a glance
54
+
55
+ **Game-client surface** (end-user, browser-safe):
56
+
57
+ | Sub-client | What it does |
58
+ |---|---|
59
+ | `client.auth` | Register, log in, log out, password reset, email confirmation. |
60
+ | `client.users` | `me`, `updateGamertag`, profile reads. |
61
+ | `client.session` | Token store, `restore()`, `getToken()`, manual `setToken()`. |
62
+ | `client.serverStatus` | `gameClientBootstrap(appId)` — per-app version info, UDP status, spatial limits. |
63
+ | `client.chunks`, `client.voxels`, `client.actors`, `client.avatars`, `client.state` | World data reads + writes. |
64
+ | `client.host` | Game-host election + actor liveness `heartbeat`. |
65
+ | `client.teleport` | Teleport requests. |
66
+ | `client.channels`, `client.teams` | Messaging channels and app-scoped player teams (membership + roles). |
67
+ | `client.gameModel` | Abstract game model: containers, properties, functions (incl. model-driven `notify_*` effects), sessions, and **automations / NPCs** (`upsertAutomation`, `runAutomation`, `automationRuns`, `automationStats`, …). |
68
+ | `client.udp` | UDP proxy subscriptions + spatial mutations (`sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`, `sendTextPacket`, `sendClientEvent`). |
69
+ | `client.realtime` | Connection status, manual `connect()` / `disconnect()`, `onStatus()` listener. |
70
+ | `client.world(appId)` | Higher-level helpers for browser games (`actor.join`, `actor.sendState`, `actor.sendText`). |
71
+
72
+ **Studio-admin surface** (privileged; drive with a server-side / studio token, grouped under `client.admin`):
73
+
74
+ | Sub-client | What it does |
75
+ |---|---|
76
+ | `client.organizations` | Orgs, members, RBAC roles, org API tokens. |
77
+ | `client.apps` | App discovery + routing (`createApp` etc. via the management API directly). |
78
+ | `client.appAccess` | Access tiers + per-user grants. |
79
+ | `client.billing` | Org wallet + per-app spend budgets. |
80
+ | `client.payments` | Payment checkouts (wallet top-ups, plan purchases). |
81
+ | `client.quotas` | Usage quotas at the org/app scope. |
82
+ | `client.environments` | Dedicated environments: quote, provision, scale, deploy, link apps. |
83
+ | `client.usage` | Replication + GraphQL usage reporting. |
84
+ | `client.sharedEnvironment` | Publish to shared, runtime gating, spend caps, auto-billing. |
85
+ | `client.gameApps` | App grids (`createGrid` / `deleteGrid`) + grid runtime-permission administration. |
86
+
87
+ **Operator surface** (platform operations; requires `is_operator`):
88
+
89
+ | Sub-client | What it does |
90
+ |---|---|
91
+ | `client.operator` | Control plane: cross-org environments, change orders, secrets, release management, audit. |
92
+
93
+ Auth, user reads, and the studio-admin / operator surfaces target `managementUrl`; the game-client world/UDP surfaces target `httpUrl` / `wsUrl`. A single shared `AuthState` carries the bearer token to whichever endpoint serves each call.
94
+
95
+ ## Game-loop lifecycle
96
+
97
+ 1. Authenticate with `client.auth.login()` or restore a previous token through `client.session.restore()`.
98
+ 2. Subscribe to UDP proxy notifications with `client.udp.subscribe(handlers, appId)` — `appId` is **required** (the SDK opens the realtime socket on demand and scopes it to that app).
99
+ 3. Join a chunk by sending an initial actor update.
100
+ 4. Send actor, voxel, text, audio, and client-event updates through `client.udp` or the higher-level `client.world(appId)` helpers.
101
+ 5. Call `client.udp.disconnect()` when leaving the world.
102
+ 6. Call `client.close()` when disposing the SDK instance.
103
+
104
+ ## Per-app routing
105
+
106
+ When a player is about to join an app, query its routing fields on the management API first:
107
+
108
+ ```graphql
109
+ query AppForRouting($appId: BigInt!) {
110
+ app(appId: $appId) {
111
+ appId
112
+ splitMode
113
+ deploymentTarget
114
+ gameApiUrl
115
+ }
116
+ }
117
+ ```
118
+
119
+ `gameApiUrl` is populated for **both** dedicated (`splitMode`) and shared
120
+ (`deploymentTarget: "shared"`) apps. When it's set, build a **second**
121
+ `CrowdyClient` with `httpUrl: gameApiUrl` (and the matching `wsUrl`) **sharing the
122
+ same `tokenStore` as the first client**, then drive gameplay through that client.
123
+ Apps with no `gameApiUrl` keep working against the default `httpUrl` you
124
+ configured.
125
+
126
+ ## Realtime notifications
127
+
128
+ `subscribe` takes the handlers **and a required `appId`** (second argument). The
129
+ Game API scopes the realtime session to that app and rejects an app-agnostic
130
+ subscription with a `RealtimeConnectionEvent` (`code: 'APP_ID_REQUIRED'`). Run
131
+ one client per app (sharing the same `tokenStore`) when a player is in multiple
132
+ apps at once.
133
+
134
+ ```ts
135
+ const appId = '1';
136
+
137
+ const unsubscribe = client.udp.subscribe(
138
+ {
139
+ actorUpdate: (event) => {
140
+ console.log(event.uuid, event.state);
141
+ },
142
+ voxelUpdate: (event) => { /* ... */ },
143
+ text: (event) => { /* ... */ },
144
+ audio: (event) => { /* ... */ },
145
+ clientEvent: (event) => { /* ... */ },
146
+ serverEvent: (event) => { /* ... */ },
147
+ singleActorMessage: (event) => {
148
+ // A direct actor-to-actor message addressed to you.
149
+ console.log(event.uuid, event.payload); // payload is base64
150
+ },
151
+ genericError: (event) => {
152
+ console.warn(event.sequenceNumber, event.errorCode);
153
+ },
154
+ connectionEvent: (event) => {
155
+ console.warn(event.code, event.message);
156
+ },
157
+ error: (error) => {
158
+ console.error(error.code, error.message);
159
+ },
160
+ },
161
+ appId,
162
+ );
163
+
164
+ // Or use the world helper, which passes its appId automatically:
165
+ // client.world(appId).subscribe(handlers);
166
+
167
+ client.realtime.onStatus((status) => {
168
+ console.log('realtime:', status);
169
+ });
170
+
171
+ // Later:
172
+ unsubscribe();
173
+ ```
174
+
175
+ The SDK uses the `graphql-transport-ws` protocol through `graphql-ws`, reconnects with backoff, re-reads the current token before reconnecting, and resubscribes automatically.
176
+
177
+ ## Spatial sends
178
+
179
+ ```ts
180
+ const response = await client.udp.sendActorUpdateAndWait({
181
+ appId: '1',
182
+ chunk: { x: '0', y: '0', z: '0' },
183
+ uuid: '0123456789abcdef0123456789abcdef',
184
+ state: 'AA==', // base64-encoded payload
185
+ distance: 8,
186
+ decayRate: 1,
187
+ });
188
+
189
+ console.log(response.__typename, response.sequenceNumber);
190
+ ```
191
+
192
+ The plain `sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`, `sendTextPacket`, and `sendClientEvent` methods return the GraphQL mutation result immediately. The `AndWait` variants allocate a `sequenceNumber` when one is missing and wait for either a matching notification or `GenericErrorResponse`.
193
+
194
+ ### Actor-to-actor messages
195
+
196
+ ```ts
197
+ // Delivered only to the actor whose UUID matches `targetUuid`; you must know
198
+ // that actor's current chunk. Fire-and-forget — the sender gets no echo, so
199
+ // there is no `AndWait` variant. The target receives a
200
+ // `SingleActorMessageNotification` on its subscription.
201
+ await client.udp.sendSingleActorMessage({
202
+ appId: '1',
203
+ chunk: { x: '7', y: '1', z: '2' }, // the TARGET actor's chunk
204
+ targetUuid: '0123456789abcdef0123456789abcdef',
205
+ payload: 'aGVsbG8=', // base64; embed sender identity here if you need it
206
+ });
207
+ ```
208
+
209
+ ## World helpers
210
+
211
+ ```ts
212
+ const world = client.world('1');
213
+ const actor = world.actor();
214
+
215
+ await actor.join({ x: '0', y: '0', z: '0' });
216
+ await actor.sendState('AA==');
217
+ await actor.sendText('hello nearby players');
218
+
219
+ // Direct message to one other actor (you supply its UUID + current chunk):
220
+ await actor.sendToActor(
221
+ '0123456789abcdef0123456789abcdef',
222
+ 'aGVsbG8=', // base64 payload
223
+ { x: '7', y: '1', z: '2' },
224
+ );
225
+ ```
226
+
227
+ The world helpers are thin wrappers over `client.udp.*` with the appId pre-bound — convenient for browser games. Advanced callers can always use `client.udp.*` with the generated GraphQL input types directly.
228
+
229
+ ## Errors
230
+
231
+ Transport and protocol failures throw structured error classes:
232
+
233
+ - `CrowdyHttpError` — non-2xx response from a GraphQL endpoint.
234
+ - `CrowdyGraphQLError` — preserves every GraphQL error including `path` and `extensions.code`.
235
+ - `CrowdyNetworkError` — network-level failure (DNS, TLS, connection refused).
236
+ - `CrowdyTimeoutError` — request or `AndWait` timed out.
237
+ - `CrowdyRealtimeError` — realtime subscription couldn't be established or was dropped.
238
+ - `CrowdyProtocolError` — server response failed schema validation.
239
+
240
+ GraphQL errors carry a stable `extensions.code` (e.g. `UNAUTHENTICATED`, `SCOPE_MISSING`, `FORBIDDEN`, `IDEMPOTENCY_CONFLICT`) plus, where applicable, `extensions.remediation` and `extensions.requiredPermission`. Branch on `error.extensions?.code` rather than parsing messages.
241
+
242
+ ## Idempotent retries
243
+
244
+ Destructive game-client mutations accept an optional **idempotency key**. Pass a stable key (e.g. `crypto.randomUUID()`) and a network retry replays the first result instead of applying the side effect twice. Reusing a key with different arguments throws a `CrowdyGraphQLError` with `extensions.code === 'IDEMPOTENCY_CONFLICT'`. Keys expire server-side after 24h.
245
+
246
+ ```ts
247
+ const key = crypto.randomUUID();
248
+ await client.actors.delete(uuid, key); // first call deletes
249
+ await client.actors.delete(uuid, key); // retry → replays the first result
250
+ await client.teams.remove(groupId, key); // deleteTeam
251
+ await client.teams.leave(groupId, key); // leaveTeam
252
+ await client.voxels.rollback({ ...input, idempotencyKey: key }); // input field
253
+ ```
254
+
255
+ The key parameter is optional and trailing, so it's safe to omit. Requires a server on release v0.1.19+ (see Server compatibility above).
256
+
257
+ ## Auth notes
258
+
259
+ - Use `client.auth.setToken(token)` if you need to seed a token externally (e.g. when restoring auth from a non-default storage).
260
+ - `client.session.restore()` reads from the configured `tokenStore`. `BrowserLocalStorageTokenStore` is provided; bring your own for SSR or Node usage.
261
+ - A single `AuthState` is observed by both the HTTP client and the realtime socket, so HTTP and WebSocket auth can never drift.
262
+
263
+ ## Surface scope & security
264
+
265
+ As of v6 (completed in v6.1), CrowdyJS wraps the **full** management-api + game-api
266
+ public surface, not just the game-client subset — every non-deprecated public root
267
+ field has a typed method, with Relay `*Connection` cursor-pagination variants
268
+ alongside the legacy offset lists. The surfaces are namespaced by audience:
269
+
270
+ - **Game-client** (`client.auth`, `client.users`, `client.udp`, `client.world(...)`,
271
+ `client.chunks`/`voxels`/`actors`/`avatars`/`state`/`teleport`/`channels`/`teams`/
272
+ `gameModel`/`host`) — safe for untrusted browser clients with an end-user token.
273
+ - **Studio-admin** (`client.admin.*` — also reachable at the top level, e.g.
274
+ `client.billing`) — privileged organization/app administration. Drive these from a
275
+ **studio backend** with an org-scoped or admin token, **not** from an untrusted
276
+ browser; the server still enforces the relevant org/app permission on every call.
277
+ - **Operator** (`client.operator`) — platform control-plane operations that require
278
+ `users.is_operator`. For internal operator tooling only.
279
+
280
+ The SDK never relaxes server-side authorization — exposing an operation here just
281
+ gives you a typed wrapper; the caller still needs the right token and permission. For
282
+ any brand-new server field not yet wrapped, the low-level escape hatch
283
+ (`client.graphql.request(...)` / `client.management.request(...)`) always works.
284
+
285
+ ## Low-level GraphQL access
286
+
287
+ Game-client methods are first-class, but generated operation documents are also available through a transport escape hatch:
288
+
289
+ ```ts
290
+ import { VersionInfoDocument } from '@crowdedkingdoms/crowdyjs/generated';
291
+
292
+ const data = await client.graphql.request(VersionInfoDocument);
293
+ ```
294
+
295
+ Most consumers should prefer the typed methods on `client.auth`, `client.users`, `client.udp`, `client.serverStatus`, and `client.world()`.
296
+
297
+ ## Migration
298
+
299
+ See [MIGRATION.md](MIGRATION.md) for breaking changes between SDK majors.
300
+
301
+ ## License
302
+
303
+ MIT
@@ -0,0 +1,11 @@
1
+ import { SessionStore, type SessionListener, type TokenStore } from './session.js';
2
+ export type AuthStateListener = SessionListener;
3
+ /**
4
+ * Backwards-compatible internal name for the v3 SessionStore. Public callers
5
+ * should use `client.session`; older domain wrappers still accept AuthState.
6
+ */
7
+ export declare class AuthState extends SessionStore {
8
+ constructor(tokenStore?: TokenStore);
9
+ subscribe(listener: AuthStateListener): () => void;
10
+ }
11
+ //# sourceMappingURL=auth-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-state.d.ts","sourceRoot":"","sources":["../src/auth-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAEnF,MAAM,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAEhD;;;GAGG;AACH,qBAAa,SAAU,SAAQ,YAAY;gBAC7B,UAAU,CAAC,EAAE,UAAU;IAInC,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;CAGnD"}
@@ -0,0 +1,13 @@
1
+ import { SessionStore } from './session.js';
2
+ /**
3
+ * Backwards-compatible internal name for the v3 SessionStore. Public callers
4
+ * should use `client.session`; older domain wrappers still accept AuthState.
5
+ */
6
+ export class AuthState extends SessionStore {
7
+ constructor(tokenStore) {
8
+ super(tokenStore);
9
+ }
10
+ subscribe(listener) {
11
+ return this.onChange(listener);
12
+ }
13
+ }