@crowdedkingdomstudios/crowdyjs 5.1.0 → 5.2.1

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 (75) hide show
  1. package/MIGRATION.md +64 -0
  2. package/README.md +19 -0
  3. package/dist/client.d.ts +98 -5
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +74 -5
  6. package/dist/crowdy-client.d.ts +31 -0
  7. package/dist/crowdy-client.d.ts.map +1 -1
  8. package/dist/crowdy-client.js +8 -0
  9. package/dist/domains/actors.d.ts +88 -5
  10. package/dist/domains/actors.d.ts.map +1 -1
  11. package/dist/domains/actors.js +89 -6
  12. package/dist/domains/apps.d.ts +95 -41
  13. package/dist/domains/apps.d.ts.map +1 -1
  14. package/dist/domains/apps.js +80 -33
  15. package/dist/domains/auth.d.ts +139 -19
  16. package/dist/domains/auth.d.ts.map +1 -1
  17. package/dist/domains/auth.js +137 -17
  18. package/dist/domains/channels.d.ts +264 -5
  19. package/dist/domains/channels.d.ts.map +1 -1
  20. package/dist/domains/channels.js +264 -5
  21. package/dist/domains/chunks.d.ts +116 -3
  22. package/dist/domains/chunks.d.ts.map +1 -1
  23. package/dist/domains/chunks.js +116 -3
  24. package/dist/domains/gameModel.d.ts +412 -6
  25. package/dist/domains/gameModel.d.ts.map +1 -1
  26. package/dist/domains/gameModel.js +412 -6
  27. package/dist/domains/platform.d.ts +36 -20
  28. package/dist/domains/platform.d.ts.map +1 -1
  29. package/dist/domains/platform.js +29 -18
  30. package/dist/domains/serverStatus.d.ts +74 -6
  31. package/dist/domains/serverStatus.d.ts.map +1 -1
  32. package/dist/domains/serverStatus.js +74 -6
  33. package/dist/domains/state.d.ts +50 -2
  34. package/dist/domains/state.d.ts.map +1 -1
  35. package/dist/domains/state.js +50 -2
  36. package/dist/domains/teams.d.ts +265 -7
  37. package/dist/domains/teams.d.ts.map +1 -1
  38. package/dist/domains/teams.js +267 -9
  39. package/dist/domains/teleport.d.ts +30 -2
  40. package/dist/domains/teleport.d.ts.map +1 -1
  41. package/dist/domains/teleport.js +30 -2
  42. package/dist/domains/udp.d.ts +341 -5
  43. package/dist/domains/udp.d.ts.map +1 -1
  44. package/dist/domains/udp.js +341 -5
  45. package/dist/domains/users.d.ts +42 -11
  46. package/dist/domains/users.d.ts.map +1 -1
  47. package/dist/domains/users.js +41 -10
  48. package/dist/domains/voxels.d.ts +107 -2
  49. package/dist/domains/voxels.d.ts.map +1 -1
  50. package/dist/domains/voxels.js +107 -2
  51. package/dist/errors.d.ts +116 -0
  52. package/dist/errors.d.ts.map +1 -1
  53. package/dist/errors.js +100 -0
  54. package/dist/generated/graphql.d.ts +1787 -110
  55. package/dist/generated/graphql.d.ts.map +1 -1
  56. package/dist/generated/graphql.js +75 -9
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -1
  60. package/dist/realtime.d.ts +226 -0
  61. package/dist/realtime.d.ts.map +1 -1
  62. package/dist/realtime.js +90 -0
  63. package/dist/session.d.ts +46 -0
  64. package/dist/session.d.ts.map +1 -1
  65. package/dist/session.js +35 -0
  66. package/dist/types.d.ts +429 -0
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js +53 -0
  69. package/dist/utils.d.ts +86 -0
  70. package/dist/utils.d.ts.map +1 -1
  71. package/dist/utils.js +86 -0
  72. package/dist/world.d.ts +192 -0
  73. package/dist/world.d.ts.map +1 -1
  74. package/dist/world.js +170 -0
  75. package/package.json +1 -1
@@ -1,40 +1,123 @@
1
1
  import { ActorDocument, ActorsDocument, BatchLookupActorsDocument, CreateActorDocument, UpdateActorDocument, DeleteActorDocument, UpdateActorStateDocument, } from '../generated/graphql.js';
2
2
  /**
3
- * Actor (player / NPC) CRUD and filtering.
3
+ * Persisted-actor (player / NPC) CRUD and filtering on the **game-api**.
4
+ * Exposed as `client.actors`.
4
5
  *
5
- * Note: this is the persisted-actor API. For high-frequency replication see
6
- * the existing `client.sendActorUpdate()` UDP path (unchanged).
6
+ * An "actor" here is the durable, server-stored record of a participant
7
+ * (identity, owning app, optional avatar, last-known chunk, and a state blob)
8
+ * not the high-frequency spatial replication stream. For real-time position
9
+ * and state fan-out use the UDP path instead (`client.udp.sendActorUpdate(...)`
10
+ * or the ergonomic `client.world(appId).actor()` helper), which is unchanged
11
+ * and far cheaper per update.
7
12
  *
8
- * Exposed as `client.actors`.
13
+ * Actor ids are exactly **32 ASCII characters** (the UDP-wire actor id), **not**
14
+ * a hyphenated RFC-4122 UUID. Use {@link generateCrowdyUuid} to mint one.
15
+ * `BigInt` values (`appId`, `avatarId`, `userId`) are sent and received as
16
+ * decimal strings.
17
+ *
18
+ * Every method requires an authenticated session (a Bearer token set via
19
+ * `client.auth.login()` or `client.setToken()`) and that the caller is
20
+ * entitled to the target app, or it throws {@link CrowdyGraphQLError}
21
+ * (`UNAUTHENTICATED` / `FORBIDDEN`).
9
22
  */
10
23
  export class ActorsAPI {
11
24
  constructor(gql) {
12
25
  this.gql = gql;
13
26
  }
27
+ /**
28
+ * Fetch a single persisted actor by its 32-character actor id.
29
+ *
30
+ * @param uuid - The actor's 32-ASCII-character id.
31
+ * @returns The {@link Actor}, or `null` if no actor with that id exists.
32
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` / `FORBIDDEN` if the caller
33
+ * isn't entitled to the actor's app.
34
+ */
14
35
  async get(uuid) {
15
36
  const data = await this.gql.request(ActorDocument, { uuid });
16
37
  return data.actor;
17
38
  }
39
+ /**
40
+ * List persisted actors, optionally narrowed by an {@link ActorFilterInput}
41
+ * (e.g. by app, owning user, or avatar). Omit the filter to use its defaults.
42
+ *
43
+ * @param filter - Optional filter; fields are ANDed together.
44
+ * @returns The matching actors.
45
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
46
+ */
18
47
  async list(filter) {
19
48
  const data = await this.gql.request(ActorsDocument, { filter });
20
49
  return data.actors;
21
50
  }
51
+ /**
52
+ * Resolve many actors in one round-trip by id (and/or the other keys the
53
+ * {@link BatchActorLookupInput} accepts). Prefer this over calling
54
+ * {@link get} in a loop — it avoids N requests and N auth checks.
55
+ *
56
+ * @param input - The batch lookup keys.
57
+ * @returns The actors that were found (missing ids are simply omitted).
58
+ */
22
59
  async batchLookup(input) {
23
60
  const data = await this.gql.request(BatchLookupActorsDocument, { input });
24
61
  return data.batchLookupActors;
25
62
  }
63
+ /**
64
+ * Create a persisted actor. `input.uuid` must be a unique 32-ASCII-character
65
+ * id and `input.appId` the owning app; `chunk` is the initial grid position.
66
+ * `avatarId`, `privateState`, and `publicState` are optional (state blobs are
67
+ * base64-encoded binary).
68
+ *
69
+ * @param input - {@link CreateActorInput}.
70
+ * @returns The newly created {@link Actor}.
71
+ * @throws {CrowdyGraphQLError} `BAD_USER_INPUT` (e.g. malformed/duplicate
72
+ * uuid), `FORBIDDEN` if not entitled to the app, or `UNAUTHENTICATED`.
73
+ */
26
74
  async create(input) {
27
75
  const data = await this.gql.request(CreateActorDocument, { input });
28
76
  return data.createActor;
29
77
  }
78
+ /**
79
+ * Patch an existing actor. Only the fields present on `input` change; omitted
80
+ * fields are left untouched.
81
+ *
82
+ * @param uuid - The actor's 32-character id.
83
+ * @param input - Fields to change ({@link UpdateActorInput}).
84
+ * @returns The updated {@link Actor}.
85
+ * @throws {CrowdyGraphQLError} if the actor doesn't exist or the caller lacks
86
+ * access.
87
+ */
30
88
  async update(uuid, input) {
31
89
  const data = await this.gql.request(UpdateActorDocument, { uuid, input });
32
90
  return data.updateActor;
33
91
  }
34
- async delete(uuid) {
35
- const data = await this.gql.request(DeleteActorDocument, { uuid });
92
+ /**
93
+ * Delete a persisted actor.
94
+ *
95
+ * Pass an `idempotencyKey` to make retries safe: replaying with the same key
96
+ * returns the first result instead of re-applying, while the same key with a
97
+ * **different** `uuid` throws {@link CrowdyGraphQLError} with
98
+ * `code === 'IDEMPOTENCY_CONFLICT'`. Keys expire server-side after 24h.
99
+ * Requires game-api ≥ v0.10.3.
100
+ *
101
+ * @param uuid - The actor's 32-character id.
102
+ * @param idempotencyKey - Optional client-supplied key for safe retries.
103
+ * @returns The deleted {@link Actor} (its identifying fields).
104
+ * @throws {CrowdyGraphQLError} `IDEMPOTENCY_CONFLICT`, `FORBIDDEN`, or
105
+ * `UNAUTHENTICATED`.
106
+ */
107
+ async delete(uuid, idempotencyKey) {
108
+ const data = await this.gql.request(DeleteActorDocument, { uuid, idempotencyKey });
36
109
  return data.deleteActor;
37
110
  }
111
+ /**
112
+ * Replace just an actor's state blob(s) without touching its other fields —
113
+ * a lighter write than {@link update} when only `privateState`/`publicState`
114
+ * change. State is base64-encoded binary.
115
+ *
116
+ * @param uuid - The actor's 32-character id.
117
+ * @param input - {@link UpdateActorStateInput}.
118
+ * @returns The updated {@link Actor}.
119
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
120
+ */
38
121
  async updateState(uuid, input) {
39
122
  const data = await this.gql.request(UpdateActorStateDocument, { uuid, input });
40
123
  return data.updateActorState;
@@ -1,65 +1,119 @@
1
- /**
2
- * Apps sub-client. Targets `cks-management-api` (where the apps catalog
3
- * lives). After the DB split each app may be served by its own per-tenant
4
- * cks-game-api; the marketplace returns `gameApiUrl` for those rows so the
5
- * caller can build a per-app `CrowdyClient` against the correct endpoint.
6
- *
7
- * Typical pattern:
8
- *
9
- * const baseClient = createCrowdyClient({
10
- * managementUrl: 'https://api.example.com',
11
- * httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
12
- * });
13
- * await baseClient.auth.login({ email, password });
14
- *
15
- * const route = await baseClient.apps.routeFor(appId);
16
- * if (route.gameApiUrl) {
17
- * // Set for both dedicated (split-mode) AND shared-environment apps.
18
- * const perAppClient = createCrowdyClient({
19
- * managementUrl: 'https://api.example.com',
20
- * httpUrl: route.gameApiUrl,
21
- * wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
22
- * tokenStore: baseClient.session.tokenStore,
23
- * });
24
- * // drive gameplay through perAppClient
25
- * }
26
- */
27
1
  import type { GraphQLClient } from '../client.js';
28
2
  import { type AppQuery, type AppBySlugQuery, type MyAppsQuery } from '../generated/graphql.js';
29
3
  /**
30
- * Subset of an `App` row that the SDK exposes for routing decisions. The
31
- * fields are typed loosely as `unknown` because the generated types lag
32
- * behind the `splitMode` / `gameApiUrl` selection until codegen runs.
4
+ * The minimal routing tuple the SDK derives from an `App` row: just enough to
5
+ * decide which game-api endpoint should serve a given app. Returned by
6
+ * {@link AppsAPI.routeFor}.
33
7
  */
34
8
  export interface AppRoute {
9
+ /** Numeric id of the app (`BigInt` as a decimal string). */
35
10
  appId: string;
11
+ /**
12
+ * `true` when the app's runtime data lives in a dedicated per-tenant game-api
13
+ * database rather than the shared game-api. Used together with `gameApiUrl` to
14
+ * route gameplay calls.
15
+ */
36
16
  splitMode: boolean;
37
17
  /**
38
- * 'none' (draft), 'shared' (the shared game-api), or 'dedicated' (a
39
- * per-tenant environment). Populated once the schema/codegen expose it.
18
+ * Where the app runs: `'none'` (draft / not deployed), `'shared'` (the shared
19
+ * game-api), or `'dedicated'` (a provisioned per-tenant environment). `null`
20
+ * until the schema/codegen expose it.
40
21
  */
41
22
  deploymentTarget: string | null;
42
23
  /**
43
- * The game-api URL to route gameplay to. Set for BOTH dedicated (split-mode)
44
- * and shared-environment apps. When non-null, build a game-api client
45
- * against it; when null, fall back to the constructor `httpUrl`.
24
+ * The game-api base URL to route gameplay to. Set for BOTH dedicated
25
+ * (split-mode) and shared-environment apps. When non-null, build a game-api
26
+ * client against it; when `null`, fall back to the constructor `httpUrl`.
46
27
  */
47
28
  gameApiUrl: string | null;
48
29
  }
30
+ /**
31
+ * App discovery & game-api routing — exposed as `client.apps`.
32
+ *
33
+ * Targets the **management-api** (every call routes to `managementUrl`), where
34
+ * the apps catalog lives. After the database split an app may be served by its
35
+ * own per-tenant cks-game-api; the catalog returns each app's `gameApiUrl` so
36
+ * you can build a per-app `CrowdyClient` against the correct endpoint (see
37
+ * {@link routeFor} / {@link AppRoute}).
38
+ *
39
+ * Auth: {@link appBySlug} is **public** (no session; resolves unlisted or draft
40
+ * apps when the exact slugs are known). {@link app}, {@link myApps}, and
41
+ * {@link routeFor} require authentication (any signed-in user) and otherwise
42
+ * throw {@link CrowdyGraphQLError} with `UNAUTHENTICATED`; note {@link app} does
43
+ * not enforce org/app permissions. `BigInt` ids such as `appId` and `orgId` are
44
+ * decimal strings.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const base = createCrowdyClient({
49
+ * managementUrl: 'https://api.example.com',
50
+ * httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
51
+ * });
52
+ * await base.auth.login({ email, password });
53
+ *
54
+ * const route = await base.apps.routeFor(appId);
55
+ * if (route.gameApiUrl) {
56
+ * // route gameplay to the app's resolved game-api endpoint
57
+ * const perAppClient = createCrowdyClient({
58
+ * managementUrl: 'https://api.example.com',
59
+ * httpUrl: route.gameApiUrl,
60
+ * wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
61
+ * tokenStore: base.session.tokenStore,
62
+ * });
63
+ * }
64
+ * ```
65
+ */
49
66
  export declare class AppsAPI {
50
67
  private readonly management;
51
68
  constructor(management: GraphQLClient);
52
- /** Fetch a single app by id. */
69
+ /**
70
+ * Fetch a single app by its numeric id. Requires authentication (any signed-in
71
+ * user); does **not** enforce org/app permissions, so it can read apps the
72
+ * caller does not own, of any visibility/status. Prefer {@link appBySlug} for
73
+ * slug-based marketplace lookups.
74
+ *
75
+ * @param appId - Numeric id of the app (`BigInt` as a decimal string).
76
+ * @returns The {@link App}, or `null` if the id does not exist.
77
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
78
+ */
53
79
  app(appId: string): Promise<AppQuery['app']>;
54
- /** Fetch by org slug + app slug (marketplace links). */
80
+ /**
81
+ * Look up a single app by its org slug + app slug (the marketplace URL path).
82
+ * **Public**: no authentication required, and not filtered by visibility or
83
+ * status — it can resolve unlisted or draft apps when the exact slugs are
84
+ * known.
85
+ *
86
+ * @param orgSlug - URL slug of the owning organization (e.g. `"acme"` in the
87
+ * path `/acme/my-game`).
88
+ * @param appSlug - URL slug of the app within the org (e.g. `"my-game"`);
89
+ * unique per org.
90
+ * @returns The {@link App}, or `null` if no matching app exists.
91
+ * @throws {CrowdyGraphQLError} on transport/validation failures.
92
+ */
55
93
  appBySlug(orgSlug: string, appSlug: string): Promise<AppBySlugQuery['appBySlug']>;
56
- /** Apps the caller can play (org membership OR active access). */
94
+ /**
95
+ * List the apps the authenticated caller can see in their account: those owned
96
+ * by an org they are an active member of, OR those where they hold an active
97
+ * access grant. Includes apps of any visibility/status (e.g. accessible
98
+ * drafts), ordered newest-first. Requires authentication.
99
+ *
100
+ * @returns The caller's accessible {@link App}s (an empty array if none).
101
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
102
+ */
57
103
  myApps(): Promise<MyAppsQuery['myApps']>;
58
104
  /**
59
- * Convenience: returns just the routing tuple for a given app. If the
60
- * app row is missing or the API does not expose split-mode fields yet,
61
- * returns `{ appId, splitMode: false, gameApiUrl: null }` so the caller
62
- * keeps using the legacy single-endpoint deployment.
105
+ * Convenience wrapper over {@link app} that returns just the {@link AppRoute}
106
+ * routing tuple for an app i.e. which game-api endpoint should serve it. If
107
+ * the app row is missing or the API does not expose the split-mode fields yet,
108
+ * returns a safe default (`{ appId, splitMode: false, deploymentTarget: null,
109
+ * gameApiUrl: null }`) so the caller keeps using the legacy single-endpoint
110
+ * deployment.
111
+ *
112
+ * @param appId - Numeric id of the app (`BigInt` as a decimal string).
113
+ * @returns The {@link AppRoute}; route gameplay to `gameApiUrl` when non-null,
114
+ * otherwise fall back to the constructor `httpUrl`.
115
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` (it calls {@link app} under
116
+ * the hood).
63
117
  */
64
118
  routeFor(appId: string): Promise<AppRoute>;
65
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/domains/apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAIL,KAAK,QAAQ,EAEb,KAAK,cAAc,EAEnB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAgBD,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,aAAa;IAEtD,gCAAgC;IAC1B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAQlD,wDAAwD;IAClD,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAQvC,kEAAkE;IAC5D,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAQ9C;;;;;OAKG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAWjD"}
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/domains/apps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAIL,KAAK,QAAQ,EAEb,KAAK,cAAc,EAEnB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,aAAa;IAEtD;;;;;;;;;OASG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAQlD;;;;;;;;;;;;OAYG;IACG,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAQvC;;;;;;;;OAQG;IACG,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAQ9C;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAWjD"}
@@ -1,29 +1,3 @@
1
- /**
2
- * Apps sub-client. Targets `cks-management-api` (where the apps catalog
3
- * lives). After the DB split each app may be served by its own per-tenant
4
- * cks-game-api; the marketplace returns `gameApiUrl` for those rows so the
5
- * caller can build a per-app `CrowdyClient` against the correct endpoint.
6
- *
7
- * Typical pattern:
8
- *
9
- * const baseClient = createCrowdyClient({
10
- * managementUrl: 'https://api.example.com',
11
- * httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
12
- * });
13
- * await baseClient.auth.login({ email, password });
14
- *
15
- * const route = await baseClient.apps.routeFor(appId);
16
- * if (route.gameApiUrl) {
17
- * // Set for both dedicated (split-mode) AND shared-environment apps.
18
- * const perAppClient = createCrowdyClient({
19
- * managementUrl: 'https://api.example.com',
20
- * httpUrl: route.gameApiUrl,
21
- * wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
22
- * tokenStore: baseClient.session.tokenStore,
23
- * });
24
- * // drive gameplay through perAppClient
25
- * }
26
- */
27
1
  import { AppDocument, AppBySlugDocument, MyAppsDocument, } from '../generated/graphql.js';
28
2
  function appRouteFromAppRow(row) {
29
3
  if (!row || typeof row !== 'object')
@@ -38,30 +12,103 @@ function appRouteFromAppRow(row) {
38
12
  gameApiUrl: typeof r.gameApiUrl === 'string' && r.gameApiUrl ? r.gameApiUrl : null,
39
13
  };
40
14
  }
15
+ /**
16
+ * App discovery & game-api routing — exposed as `client.apps`.
17
+ *
18
+ * Targets the **management-api** (every call routes to `managementUrl`), where
19
+ * the apps catalog lives. After the database split an app may be served by its
20
+ * own per-tenant cks-game-api; the catalog returns each app's `gameApiUrl` so
21
+ * you can build a per-app `CrowdyClient` against the correct endpoint (see
22
+ * {@link routeFor} / {@link AppRoute}).
23
+ *
24
+ * Auth: {@link appBySlug} is **public** (no session; resolves unlisted or draft
25
+ * apps when the exact slugs are known). {@link app}, {@link myApps}, and
26
+ * {@link routeFor} require authentication (any signed-in user) and otherwise
27
+ * throw {@link CrowdyGraphQLError} with `UNAUTHENTICATED`; note {@link app} does
28
+ * not enforce org/app permissions. `BigInt` ids such as `appId` and `orgId` are
29
+ * decimal strings.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const base = createCrowdyClient({
34
+ * managementUrl: 'https://api.example.com',
35
+ * httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
36
+ * });
37
+ * await base.auth.login({ email, password });
38
+ *
39
+ * const route = await base.apps.routeFor(appId);
40
+ * if (route.gameApiUrl) {
41
+ * // route gameplay to the app's resolved game-api endpoint
42
+ * const perAppClient = createCrowdyClient({
43
+ * managementUrl: 'https://api.example.com',
44
+ * httpUrl: route.gameApiUrl,
45
+ * wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
46
+ * tokenStore: base.session.tokenStore,
47
+ * });
48
+ * }
49
+ * ```
50
+ */
41
51
  export class AppsAPI {
42
52
  constructor(management) {
43
53
  this.management = management;
44
54
  }
45
- /** Fetch a single app by id. */
55
+ /**
56
+ * Fetch a single app by its numeric id. Requires authentication (any signed-in
57
+ * user); does **not** enforce org/app permissions, so it can read apps the
58
+ * caller does not own, of any visibility/status. Prefer {@link appBySlug} for
59
+ * slug-based marketplace lookups.
60
+ *
61
+ * @param appId - Numeric id of the app (`BigInt` as a decimal string).
62
+ * @returns The {@link App}, or `null` if the id does not exist.
63
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
64
+ */
46
65
  async app(appId) {
47
66
  const data = await this.management.request(AppDocument, { appId });
48
67
  return data.app;
49
68
  }
50
- /** Fetch by org slug + app slug (marketplace links). */
69
+ /**
70
+ * Look up a single app by its org slug + app slug (the marketplace URL path).
71
+ * **Public**: no authentication required, and not filtered by visibility or
72
+ * status — it can resolve unlisted or draft apps when the exact slugs are
73
+ * known.
74
+ *
75
+ * @param orgSlug - URL slug of the owning organization (e.g. `"acme"` in the
76
+ * path `/acme/my-game`).
77
+ * @param appSlug - URL slug of the app within the org (e.g. `"my-game"`);
78
+ * unique per org.
79
+ * @returns The {@link App}, or `null` if no matching app exists.
80
+ * @throws {CrowdyGraphQLError} on transport/validation failures.
81
+ */
51
82
  async appBySlug(orgSlug, appSlug) {
52
83
  const data = await this.management.request(AppBySlugDocument, { orgSlug, appSlug });
53
84
  return data.appBySlug;
54
85
  }
55
- /** Apps the caller can play (org membership OR active access). */
86
+ /**
87
+ * List the apps the authenticated caller can see in their account: those owned
88
+ * by an org they are an active member of, OR those where they hold an active
89
+ * access grant. Includes apps of any visibility/status (e.g. accessible
90
+ * drafts), ordered newest-first. Requires authentication.
91
+ *
92
+ * @returns The caller's accessible {@link App}s (an empty array if none).
93
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
94
+ */
56
95
  async myApps() {
57
96
  const data = await this.management.request(MyAppsDocument, {});
58
97
  return data.myApps;
59
98
  }
60
99
  /**
61
- * Convenience: returns just the routing tuple for a given app. If the
62
- * app row is missing or the API does not expose split-mode fields yet,
63
- * returns `{ appId, splitMode: false, gameApiUrl: null }` so the caller
64
- * keeps using the legacy single-endpoint deployment.
100
+ * Convenience wrapper over {@link app} that returns just the {@link AppRoute}
101
+ * routing tuple for an app i.e. which game-api endpoint should serve it. If
102
+ * the app row is missing or the API does not expose the split-mode fields yet,
103
+ * returns a safe default (`{ appId, splitMode: false, deploymentTarget: null,
104
+ * gameApiUrl: null }`) so the caller keeps using the legacy single-endpoint
105
+ * deployment.
106
+ *
107
+ * @param appId - Numeric id of the app (`BigInt` as a decimal string).
108
+ * @returns The {@link AppRoute}; route gameplay to `gameApiUrl` when non-null,
109
+ * otherwise fall back to the constructor `httpUrl`.
110
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` (it calls {@link app} under
111
+ * the hood).
65
112
  */
66
113
  async routeFor(appId) {
67
114
  const row = await this.app(appId);
@@ -1,43 +1,163 @@
1
- /**
2
- * Auth sub-client. Talks to `cks-management-api` (NOT the game API) because
3
- * the management API owns the `game_tokens` table that backs every login /
4
- * register / password / email-confirm flow.
5
- *
6
- * The returned `token` is a `game_tokens` row that game-api will validate
7
- * against the same shared Postgres. Once the SDK has a token, the rest of
8
- * the sub-clients (chunks, voxels, actors, udp, ...) use it transparently
9
- * via the shared `AuthState`.
10
- */
11
1
  import type { GraphQLClient } from '../client.js';
12
2
  import type { AuthState } from '../auth-state.js';
13
3
  import { type LoginMutation, type LoginUserInput, type RegisterMutation, type RegisterUserInput, type ResetPasswordInput } from '../generated/graphql.js';
4
+ /**
5
+ * Authentication and account-lifecycle flows — exposed as `client.auth`.
6
+ *
7
+ * Targets the **management-api**: every call routes to `managementUrl` (falling
8
+ * back to the game-api endpoint only in legacy single-endpoint mode). The
9
+ * management API owns the `game_tokens` table that backs every login / register
10
+ * / password / email-confirmation flow; the `token` it returns is a
11
+ * `game_tokens` row that game-api validates against the same shared Postgres.
12
+ *
13
+ * {@link login} and {@link register} mint that session token **and** store it on
14
+ * the shared session state automatically, so every later call on *either*
15
+ * endpoint (auth, users, apps, actors, chunks, udp, ...) is authenticated
16
+ * without you threading the token through by hand. Use {@link setToken} to
17
+ * rehydrate a saved token and {@link getToken} to read the current one. `BigInt`
18
+ * ids on the returned user (e.g. `userId`, `orgId`) are decimal strings.
19
+ *
20
+ * **Public — no session required:** {@link login}, {@link register},
21
+ * {@link confirmEmail}, {@link requestPasswordReset}, {@link resetPassword}, and
22
+ * {@link resendConfirmationEmail}. **Require a valid session:** {@link logout},
23
+ * {@link logoutAllDevices}, and {@link changePassword}, which otherwise throw
24
+ * {@link CrowdyGraphQLError} with `UNAUTHENTICATED` when the bearer token is
25
+ * missing, expired, or revoked.
26
+ */
14
27
  export declare class AuthAPI {
15
28
  private readonly graphql;
16
29
  private readonly session;
17
30
  constructor(graphql: GraphQLClient, session: AuthState);
18
31
  /**
19
- * Log in and persist the resulting `game_tokens` bearer token via the
20
- * shared `AuthState`. All subsequent SDK calls (game-api or
21
- * management-api) include it automatically.
32
+ * Authenticate with email + password and start a new session. **Public** no
33
+ * existing session required.
34
+ *
35
+ * On success the returned `token` is minted **and** stored on the shared
36
+ * session state, so subsequent calls on any sub-client (management-api or
37
+ * game-api) carry it automatically — no need to call {@link setToken}.
38
+ *
39
+ * @param input - Credentials ({@link LoginUserInput}): `email` and `password`
40
+ * (min 8 characters).
41
+ * @returns An {@link AuthResponse}: the opaque session `token` (sent as
42
+ * `Authorization: Bearer <token>`), `gameTokenId` (the session row id, a
43
+ * string), and the authenticated `user`.
44
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` on invalid credentials, or
45
+ * `BAD_USER_INPUT` on malformed input.
46
+ * @example
47
+ * ```ts
48
+ * const { user } = await client.auth.login({ email, password });
49
+ * // the session token is now stored; later calls are authenticated for you
50
+ * await client.users.me();
51
+ * ```
22
52
  */
23
53
  login(input: LoginUserInput): Promise<LoginMutation['login']>;
24
- /** Register a new user. Same token-persistence behaviour as `login`. */
54
+ /**
55
+ * Create a new (initially unconfirmed) account, send a confirmation email, and
56
+ * return a session for immediate login. **Public** — no existing session
57
+ * required. Same token-persistence behaviour as {@link login}: the new `token`
58
+ * is stored on the shared session state automatically.
59
+ *
60
+ * @param input - New-account details ({@link RegisterUserInput}): `email`
61
+ * (where the confirmation email is sent), `password` (min 8 characters), and
62
+ * an optional initial `gamertag` (min 3 characters; can also be set later via
63
+ * `client.users.updateGamertag`).
64
+ * @returns An {@link AuthResponse} (session `token`, `gameTokenId`, and the new
65
+ * `user`).
66
+ * @throws {CrowdyGraphQLError} `BAD_USER_INPUT` if the email already exists or
67
+ * the input is invalid.
68
+ */
25
69
  register(input: RegisterUserInput): Promise<RegisterMutation['register']>;
26
70
  /**
27
- * Single-device logout. Clears the in-memory token after a successful
28
- * server-side revoke so other sub-clients don't keep using it.
71
+ * Single-device logout: revoke the `game_tokens` row that authenticated this
72
+ * request; other devices/tokens are unaffected. After a successful server-side
73
+ * revoke the in-memory token is cleared from the shared session state so the
74
+ * other sub-clients stop using it. Requires a valid session.
75
+ *
76
+ * @returns `true` if a token was revoked, or `false` if the request carried no
77
+ * game token.
78
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the session is invalid.
29
79
  */
30
80
  logout(): Promise<boolean>;
31
- /** Revoke every `game_tokens` row for the current user. */
81
+ /**
82
+ * Revoke **every** active session for the authenticated user (deletes all
83
+ * their `game_tokens` rows and records revocations). Requires a valid session;
84
+ * use {@link logout} to end only the current one.
85
+ *
86
+ * Note: unlike {@link logout}, this does not clear the SDK's in-memory token —
87
+ * call {@link setToken}`(null)` afterwards if you also want to drop it locally.
88
+ *
89
+ * @returns `true` on success.
90
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the session is invalid.
91
+ */
32
92
  logoutAllDevices(): Promise<boolean>;
93
+ /**
94
+ * Confirm a user's email address using the token from the confirmation email.
95
+ * **Public** — the token itself authorizes the call.
96
+ *
97
+ * @param token - The confirmation token from the emailed link.
98
+ * @returns `true` on success, or `false` if the token is invalid or expired.
99
+ * @throws {CrowdyGraphQLError} on transport/validation failures (invalid or
100
+ * expired tokens resolve to `false` rather than throwing).
101
+ */
33
102
  confirmEmail(token: string): Promise<boolean>;
103
+ /**
104
+ * Start the password-reset flow by emailing a reset link to the address.
105
+ * **Public**. Always returns `true` regardless of whether the email exists or
106
+ * is confirmed, to prevent account enumeration.
107
+ *
108
+ * @param email - Email address to send the password-reset link to.
109
+ * @returns `true` (always, even when no such account exists).
110
+ * @throws {CrowdyGraphQLError} on transport/validation failures.
111
+ */
34
112
  requestPasswordReset(email: string): Promise<boolean>;
113
+ /**
114
+ * Complete a password reset using the reset token and a new password.
115
+ * **Public** — the reset token authorizes the call. Existing sessions are
116
+ * **not** revoked.
117
+ *
118
+ * @param input - {@link ResetPasswordInput}: the `token` from the emailed reset
119
+ * link and the `newPassword` to set (min 8 characters).
120
+ * @returns `true` on success.
121
+ * @throws {CrowdyGraphQLError} `BAD_USER_INPUT` if the token is invalid or
122
+ * expired.
123
+ */
35
124
  resetPassword(input: ResetPasswordInput): Promise<boolean>;
125
+ /**
126
+ * Re-send the email-confirmation link. **Public**. Always returns `true`
127
+ * regardless of whether the account exists or is already confirmed (prevents
128
+ * enumeration); the email is only actually sent for existing unconfirmed
129
+ * accounts.
130
+ *
131
+ * @param email - Email address of the account to re-send confirmation to.
132
+ * @returns `true` (always).
133
+ * @throws {CrowdyGraphQLError} on transport/validation failures.
134
+ */
36
135
  resendConfirmationEmail(email: string): Promise<boolean>;
136
+ /**
137
+ * Change the authenticated user's password after verifying the current one.
138
+ * Requires a valid session. Existing sessions are **not** revoked.
139
+ *
140
+ * @param currentPassword - The user's current password, for verification.
141
+ * @param newPassword - The new password to set (min 8 characters).
142
+ * @returns `true` on success.
143
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` without a valid session, or
144
+ * `BAD_USER_INPUT` if the current password is wrong.
145
+ */
37
146
  changePassword(currentPassword: string, newPassword: string): Promise<boolean>;
38
- /** Imperatively replace the in-memory bearer token (e.g. on rehydrate). */
147
+ /**
148
+ * Imperatively replace the in-memory bearer token on the shared session state
149
+ * (e.g. to rehydrate a token persisted to disk). Affects every sub-client.
150
+ * Local only — performs no network call. Pass `null` to clear it.
151
+ *
152
+ * @param token - The bearer token to use, or `null` to clear the session.
153
+ */
39
154
  setToken(token: string | null): void;
40
- /** Read the current bearer token. */
155
+ /**
156
+ * Read the current in-memory bearer token from the shared session state. Local
157
+ * only — performs no network call.
158
+ *
159
+ * @returns The current bearer token, or `null` if none is set.
160
+ */
41
161
  getToken(): string | null;
42
162
  }
43
163
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/domains/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAUL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,OAAO;IAEhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,SAAS;IAGrC;;;;OAIG;IACG,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAQnE,wEAAwE;IAClE,QAAQ,CACZ,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAQxC;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAMhC,2DAA2D;IACrD,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAKpC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7C,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOrD,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAO1D,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOxD,cAAc,CAClB,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC;IAQnB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIpC,qCAAqC;IACrC,QAAQ,IAAI,MAAM,GAAG,IAAI;CAG1B"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/domains/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAUL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,OAAO;IAEhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,SAAS;IAGrC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAQnE;;;;;;;;;;;;;;OAcG;IACG,QAAQ,CACZ,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAQxC;;;;;;;;;OASG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAMhC;;;;;;;;;;OAUG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1C;;;;;;;;OAQG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnD;;;;;;;;OAQG;IACG,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO3D;;;;;;;;;;OAUG;IACG,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhE;;;;;;;;;OASG;IACG,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO9D;;;;;;;;;OASG;IACG,cAAc,CAClB,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC;IAQnB;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIpC;;;;;OAKG;IACH,QAAQ,IAAI,MAAM,GAAG,IAAI;CAG1B"}