@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.
- package/LICENSE +21 -0
- package/MIGRATION.md +247 -0
- package/README.md +303 -0
- package/dist/auth-state.d.ts +11 -0
- package/dist/auth-state.d.ts.map +1 -0
- package/dist/auth-state.js +13 -0
- package/dist/client.d.ts +135 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +150 -0
- package/dist/crowdy-client.d.ts +182 -0
- package/dist/crowdy-client.d.ts.map +1 -0
- package/dist/crowdy-client.js +146 -0
- package/dist/domains/actors.d.ts +117 -0
- package/dist/domains/actors.d.ts.map +1 -0
- package/dist/domains/actors.js +140 -0
- package/dist/domains/admin.d.ts +61 -0
- package/dist/domains/admin.d.ts.map +1 -0
- package/dist/domains/admin.js +33 -0
- package/dist/domains/appAccess.d.ts +141 -0
- package/dist/domains/appAccess.d.ts.map +1 -0
- package/dist/domains/appAccess.js +198 -0
- package/dist/domains/apps.d.ts +192 -0
- package/dist/domains/apps.d.ts.map +1 -0
- package/dist/domains/apps.js +217 -0
- package/dist/domains/auth.d.ts +163 -0
- package/dist/domains/auth.d.ts.map +1 -0
- package/dist/domains/auth.js +208 -0
- package/dist/domains/avatars.d.ts +94 -0
- package/dist/domains/avatars.d.ts.map +1 -0
- package/dist/domains/avatars.js +137 -0
- package/dist/domains/billing.d.ts +97 -0
- package/dist/domains/billing.d.ts.map +1 -0
- package/dist/domains/billing.js +131 -0
- package/dist/domains/channels.d.ts +293 -0
- package/dist/domains/channels.d.ts.map +1 -0
- package/dist/domains/channels.js +353 -0
- package/dist/domains/chunks.d.ts +133 -0
- package/dist/domains/chunks.d.ts.map +1 -0
- package/dist/domains/chunks.js +153 -0
- package/dist/domains/controlPlane.d.ts +174 -0
- package/dist/domains/controlPlane.d.ts.map +1 -0
- package/dist/domains/controlPlane.js +252 -0
- package/dist/domains/environments.d.ts +155 -0
- package/dist/domains/environments.d.ts.map +1 -0
- package/dist/domains/environments.js +223 -0
- package/dist/domains/gameApps.d.ts +114 -0
- package/dist/domains/gameApps.d.ts.map +1 -0
- package/dist/domains/gameApps.js +169 -0
- package/dist/domains/gameModel.d.ts +668 -0
- package/dist/domains/gameModel.d.ts.map +1 -0
- package/dist/domains/gameModel.js +816 -0
- package/dist/domains/host.d.ts +35 -0
- package/dist/domains/host.d.ts.map +1 -0
- package/dist/domains/host.js +40 -0
- package/dist/domains/organizations.d.ts +179 -0
- package/dist/domains/organizations.d.ts.map +1 -0
- package/dist/domains/organizations.js +269 -0
- package/dist/domains/payments.d.ts +104 -0
- package/dist/domains/payments.d.ts.map +1 -0
- package/dist/domains/payments.js +129 -0
- package/dist/domains/platform.d.ts +49 -0
- package/dist/domains/platform.d.ts.map +1 -0
- package/dist/domains/platform.js +50 -0
- package/dist/domains/quotas.d.ts +62 -0
- package/dist/domains/quotas.d.ts.map +1 -0
- package/dist/domains/quotas.js +79 -0
- package/dist/domains/serverStatus.d.ts +90 -0
- package/dist/domains/serverStatus.d.ts.map +1 -0
- package/dist/domains/serverStatus.js +104 -0
- package/dist/domains/sharedEnvironment.d.ts +133 -0
- package/dist/domains/sharedEnvironment.d.ts.map +1 -0
- package/dist/domains/sharedEnvironment.js +179 -0
- package/dist/domains/state.d.ts +64 -0
- package/dist/domains/state.d.ts.map +1 -0
- package/dist/domains/state.js +75 -0
- package/dist/domains/teams.d.ts +292 -0
- package/dist/domains/teams.d.ts.map +1 -0
- package/dist/domains/teams.js +352 -0
- package/dist/domains/teleport.d.ts +41 -0
- package/dist/domains/teleport.d.ts.map +1 -0
- package/dist/domains/teleport.js +43 -0
- package/dist/domains/udp.d.ts +405 -0
- package/dist/domains/udp.d.ts.map +1 -0
- package/dist/domains/udp.js +457 -0
- package/dist/domains/usage.d.ts +76 -0
- package/dist/domains/usage.d.ts.map +1 -0
- package/dist/domains/usage.js +110 -0
- package/dist/domains/users.d.ts +147 -0
- package/dist/domains/users.d.ts.map +1 -0
- package/dist/domains/users.js +195 -0
- package/dist/domains/voxels.d.ts +136 -0
- package/dist/domains/voxels.d.ts.map +1 -0
- package/dist/domains/voxels.js +153 -0
- package/dist/errors.d.ts +158 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +142 -0
- package/dist/generated/graphql.d.ts +12206 -0
- package/dist/generated/graphql.d.ts.map +1 -0
- package/dist/generated/graphql.js +474 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +1 -0
- package/dist/realtime.d.ts +319 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +390 -0
- package/dist/session.d.ts +73 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +96 -0
- package/dist/subscriptions.d.ts +2 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +1 -0
- package/dist/types.d.ts +658 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +61 -0
- package/dist/utils.d.ts +98 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +136 -0
- package/dist/world.d.ts +236 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +275 -0
- 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
|
+
}
|