@crowdedkingdoms/crowdyjs 17.3.0-dev.1 → 17.4.0-test.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.
@@ -2,7 +2,7 @@ import { createClient as createWsClient } from 'graphql-ws';
2
2
  import { print } from 'graphql';
3
3
  import {
4
4
  // Runtime (player) ops
5
- GameModelCreateSessionDocument, GameModelJoinSessionDocument, GameModelSetSessionTurnDocument, GameModelCreateContainerDocument, GameModelDeleteContainerDocument, GameModelSetPropertyDocument, GameModelAddEdgeDocument, GameModelDeleteEdgeDocument, GameModelInvokeDocument, GameModelContainerDocument, GameModelContainerChangedDocument, GameModelActivePlayerCountDocument, GameModelActivePlayerCountChangedDocument, GameModelContainersDocument, GameModelContainerStateDocument, GameModelTraverseDocument, GameModelSessionDocument, GameModelSessionsDocument, GameModelEventsDocument, GameModelFlowDocument,
5
+ GameModelCreateSessionDocument, GameModelJoinSessionDocument, GameModelSetSessionTurnDocument, GameModelLeaveSessionDocument, GameModelSetSessionAdmissionDocument, GameModelTransferSessionHostDocument, GameModelEndSessionDocument, GameModelSessionSnapshotDocument, GameModelSessionEventsDocument, GameModelSessionInspectDocument, GameModelSessionChangedDocument, GameModelCreateContainerDocument, GameModelDeleteContainerDocument, GameModelSetPropertyDocument, GameModelAddEdgeDocument, GameModelDeleteEdgeDocument, GameModelInvokeDocument, GameModelContainerDocument, GameModelContainerChangedDocument, GameModelActivePlayerCountDocument, GameModelActivePlayerCountChangedDocument, GameModelContainersDocument, GameModelContainerStateDocument, GameModelTraverseDocument, GameModelSessionDocument, GameModelSessionsDocument, GameModelEventsDocument, GameModelFlowDocument,
6
6
  // Studio authoring ops
7
7
  GameModelSeedDocument, GameModelUpsertContainerTypeDocument, GameModelUpsertPropertyDefDocument, GameModelDeletePropertyDefDocument, GameModelDeleteContainerTypeDocument, GameModelUpsertFunctionDocument, GameModelDeleteFunctionDocument, GameModelDefineFeatureDocument, GameModelGrantTierFeatureDocument, GameModelSetPolicyDocument, GameModelTypeSchemaDocument, GameModelContainerTypesDocument, GameModelPropertyDefsDocument, GameModelFunctionDocument, GameModelFunctionsDocument, GameModelFeaturesDocument, GameModelTierFeaturesDocument, GameModelPolicyDocument, GameModelRevokeTierFeatureDocument, GameModelEventsConnectionDocument,
8
8
  // Automations (autonomous processes / NPCs)
@@ -148,6 +148,57 @@ export class GameModelAPI {
148
148
  void client.dispose();
149
149
  };
150
150
  }
151
+ /**
152
+ * **Subscriptions** — stream the revisions of one session as they commit
153
+ * (`gameModelSessionChanged`): joins, rejoins, leaves, presence expiries,
154
+ * host changes, admission changes, turn changes and the end. There is no
155
+ * bootstrap event: pull {@link sessionSnapshot} first, or pass
156
+ * `afterRevision` to replay retained events from there. Revisions are
157
+ * strictly increasing; on a gap or after a reconnect, pull the snapshot
158
+ * again. Cross-replica and best-effort over a bounded buffer; the events
159
+ * table ({@link sessionEvents}) is the record. Requires a `wsUrl` in the
160
+ * client config.
161
+ *
162
+ * @param variables - `{ appId, sessionId, afterRevision? }`.
163
+ * @param handlers - `next` per revision; optional `error` / `complete` /
164
+ * `webSocketImpl`.
165
+ * @returns An unsubscribe function (closes this subscription's socket).
166
+ * @throws {Error} Synchronously when the client config has no `wsUrl`.
167
+ */
168
+ sessionChanged(variables, handlers) {
169
+ if (!this.ws?.wsUrl) {
170
+ throw new Error('sessionChanged requires a wsUrl in the client config (graphql-transport-ws endpoint)');
171
+ }
172
+ const client = createWsClient({
173
+ url: this.ws.wsUrl,
174
+ lazy: false,
175
+ retryAttempts: 8,
176
+ ...(handlers.webSocketImpl
177
+ ? { webSocketImpl: handlers.webSocketImpl }
178
+ : {}),
179
+ connectionParams: () => {
180
+ const token = this.ws?.getToken();
181
+ return token ? { Authorization: `Bearer ${token}` } : {};
182
+ },
183
+ });
184
+ const dispose = client.subscribe({ query: print(GameModelSessionChangedDocument), variables }, {
185
+ next: (msg) => {
186
+ const data = msg.data;
187
+ if (data?.gameModelSessionChanged) {
188
+ handlers.next(data.gameModelSessionChanged);
189
+ }
190
+ else if (msg.errors) {
191
+ handlers.error?.(msg.errors);
192
+ }
193
+ },
194
+ error: (err) => handlers.error?.(err),
195
+ complete: () => handlers.complete?.(),
196
+ });
197
+ return () => {
198
+ dispose();
199
+ void client.dispose();
200
+ };
201
+ }
151
202
  // -- Runtime (player) -------------------------------------------------------
152
203
  /**
153
204
  * **Sessions** — create a runtime session: an isolated instance scope for
@@ -155,12 +206,21 @@ export class GameModelAPI {
155
206
  * session-creation policy ({@link setPolicy}); the caller becomes the creator
156
207
  * and a participant.
157
208
  *
209
+ * **Presence mode.** `presence: 'actor'` (the default) expires a joined
210
+ * participant with no fresh Buddy actor in the app after the join grace
211
+ * window. `presence: 'none'` never expires anybody: use it for turn-based
212
+ * play that talks GraphQL and channel pings and never replicates an actor
213
+ * (`kit.matches` does), where the roster's only exits are
214
+ * {@link leaveSession}, {@link endSession} and the empty timeout once
215
+ * everyone has left. The mode is fixed at creation.
216
+ *
158
217
  * @param input - {@link CreateSessionInput}: `appId` (decimal string), an
159
- * optional `name`, optional `metadataJson` (a JSON-object string), and
218
+ * optional `name`, optional `metadataJson` (a JSON-object string),
160
219
  * optional `participantUserIds` (decimal-string ids of initial participants
161
- * besides the creator).
220
+ * besides the creator), optional `maxParticipants`, `admission`,
221
+ * `emptyTimeoutSec`, `presence` and `idempotencyKey`.
162
222
  * @returns The created {@link GmSession} (`sessionId`, `status`, creator,
163
- * current turn, metadata, …).
223
+ * host, admission, `presence`, revision, …).
164
224
  * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` / `SCOPE_MISSING` if the token
165
225
  * is missing or not scoped to the app, `FORBIDDEN` if the session-creation
166
226
  * policy disallows the caller, or `BAD_USER_INPUT` for malformed input.
@@ -170,15 +230,28 @@ export class GameModelAPI {
170
230
  return data.gameModelCreateSession;
171
231
  }
172
232
  /**
173
- * **Sessions** — join an existing session as a participant, optionally with a
174
- * role. Requires a valid token and access to the app.
233
+ * **Sessions** — join a session, or **reconnect** to one you are already in.
234
+ * A rejoin returns your row with `incarnation + 1` and supersedes any older
235
+ * client of yours; keep the returned `incarnation`, {@link leaveSession}
236
+ * needs it. Pass `actorUuid` (your own Buddy actor, 32 hex chars — the uuid
237
+ * you replicate with) to bind this participant's presence to that actor;
238
+ * without it any fresh actor of yours in the app keeps you present.
239
+ * **Presence is your actor** (in a `presence: 'actor'` session, the default):
240
+ * a participant with no fresh Buddy actor in the app after the join grace
241
+ * window is expired by the server (`participant_expired`); a client that only
242
+ * speaks GraphQL must rejoin to come back. In a `presence: 'none'` session
243
+ * nobody is expired and `actorUuid` is recorded but not judged.
175
244
  *
176
245
  * @param input - {@link JoinSessionInput}: `appId` (decimal string),
177
- * `sessionId`, and an optional participant `role`.
178
- * @returns The {@link GmSessionParticipant} record (`sessionId`, `userId`,
179
- * `role`).
180
- * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` / `SCOPE_MISSING`,
181
- * `NOT_FOUND` if no such session, or `FORBIDDEN` if joining isn't permitted.
246
+ * `sessionId`, optional `role`, optional `actorUuid`, optional
247
+ * `idempotencyKey` (replaying returns the same incarnation).
248
+ * @returns The {@link GmSessionParticipant} row (`state`, `incarnation`,
249
+ * `actorUuid`, `joinedAt`, ...).
250
+ * @throws {CrowdyGraphQLError} `SESSION_FULL` (every seat taken),
251
+ * `SESSION_LOCKED` (admission locked and you never joined),
252
+ * `SESSION_CLOSED`, `SESSION_ENDED`, `FORBIDDEN` (the `actorUuid` is not
253
+ * yours or is already bound by another participant), `NOT_FOUND`,
254
+ * `UNAUTHENTICATED` / `SCOPE_MISSING`.
182
255
  */
183
256
  async joinSession(input) {
184
257
  const data = await this.gql.request(GameModelJoinSessionDocument, { input });
@@ -201,6 +274,78 @@ export class GameModelAPI {
201
274
  const data = await this.gql.request(GameModelSetSessionTurnDocument, { input });
202
275
  return data.gameModelSetSessionTurn;
203
276
  }
277
+ /**
278
+ * **Sessions** — leave a session you are joined to. `incarnation` is
279
+ * **required**: it is the value {@link joinSession} returned to *this*
280
+ * client (a creator starts at 1), so a superseded client can never remove
281
+ * the one that took over. Leaving an already-left row is a no-op. If you
282
+ * were host, the longest-joined present participant succeeds you
283
+ * (`host_changed`).
284
+ *
285
+ * @param input - {@link LeaveSessionInput}: `appId`, `sessionId`,
286
+ * `incarnation`, optional `idempotencyKey`.
287
+ * @returns Your {@link GmSessionParticipant} row, now `state: 'left'` with
288
+ * `leftReason: 'left'`.
289
+ * @throws {CrowdyGraphQLError} `SESSION_INCARNATION_STALE` (a newer client
290
+ * rejoined as you — stop acting from this one, or rejoin),
291
+ * `SESSION_NOT_PARTICIPANT`, `NOT_FOUND`.
292
+ */
293
+ async leaveSession(input) {
294
+ const data = await this.gql.request(GameModelLeaveSessionDocument, { input });
295
+ return data.gameModelLeaveSession;
296
+ }
297
+ /**
298
+ * **Sessions** — change who may still join: `'open'` (anyone the app
299
+ * admits), `'locked'` (only participants who already joined may reconnect —
300
+ * set it when the match starts), `'closed'` (nobody). Session host or app
301
+ * admin (`manage_apps`). Emits `admission_changed`.
302
+ *
303
+ * @param input - {@link SetSessionAdmissionInput}: `appId`, `sessionId`,
304
+ * `admission`, optional `expectedHostTerm` (the `hostTerm` you last read:
305
+ * refused if the host changed since), optional `idempotencyKey`.
306
+ * @returns The updated {@link GmSession}.
307
+ * @throws {CrowdyGraphQLError} `FORBIDDEN` (not host / admin),
308
+ * `SESSION_HOST_TERM_STALE`, `SESSION_ENDED`, `NOT_FOUND`.
309
+ */
310
+ async setSessionAdmission(input) {
311
+ const data = await this.gql.request(GameModelSetSessionAdmissionDocument, { input });
312
+ return data.gameModelSetSessionAdmission;
313
+ }
314
+ /**
315
+ * **Sessions** — hand the host role to another *joined* participant.
316
+ * Session host or app admin. Increments `hostTerm` and emits
317
+ * `host_changed` (reason `transferred`).
318
+ *
319
+ * @param input - {@link TransferSessionHostInput}: `appId`, `sessionId`,
320
+ * `toUserId` (decimal string), optional `expectedHostTerm`, optional
321
+ * `idempotencyKey`.
322
+ * @returns The updated {@link GmSession} (`hostUserId`, `hostTerm`).
323
+ * @throws {CrowdyGraphQLError} `FORBIDDEN`, `SESSION_HOST_TERM_STALE`,
324
+ * `SESSION_TARGET_NOT_PARTICIPANT` (the user named is not joined; refetch
325
+ * the snapshot and pick a participant), `SESSION_ENDED`.
326
+ */
327
+ async transferSessionHost(input) {
328
+ const data = await this.gql.request(GameModelTransferSessionHostDocument, { input });
329
+ return data.gameModelTransferSessionHost;
330
+ }
331
+ /**
332
+ * **Sessions** — end a session as `'completed'` (default) or `'abandoned'`.
333
+ * Every joined participant is marked left (`session_ended`), admission
334
+ * becomes `closed`, `endedAt` / `endReason` are set and an `ended` event
335
+ * closes the revision log. Session host or app admin. (The server also ends a
336
+ * session nobody has been joined to for longer than its `emptyTimeoutSec` as
337
+ * `abandoned` / `empty_timeout`.)
338
+ *
339
+ * @param input - {@link EndSessionInput}: `appId`, `sessionId`, optional
340
+ * `reason`, optional `expectedHostTerm`, optional `idempotencyKey`.
341
+ * @returns The ended {@link GmSession}.
342
+ * @throws {CrowdyGraphQLError} `FORBIDDEN`, `SESSION_HOST_TERM_STALE`,
343
+ * `SESSION_ENDED` (already ended), `NOT_FOUND`.
344
+ */
345
+ async endSession(input) {
346
+ const data = await this.gql.request(GameModelEndSessionDocument, { input });
347
+ return data.gameModelEndSession;
348
+ }
204
349
  /**
205
350
  * **Containers** — instantiate a container (a runtime entity of a given type),
206
351
  * optionally within a session, with an owner and initial property values.
@@ -416,10 +561,13 @@ export class GameModelAPI {
416
561
  return data.gameModelSession;
417
562
  }
418
563
  /**
419
- * **Sessions** — list sessions in an app, optionally filtered by status.
564
+ * **Sessions** — list sessions in an app, newest first, optionally filtered
565
+ * by `status` (`active | completed | abandoned`), `admission`
566
+ * (`open | locked | closed` — `'open'` lists joinable lobbies) and/or
567
+ * `hostUserId`.
420
568
  *
421
- * @param variables - `{ appId, status? }`: `appId` (decimal string) and an
422
- * optional `status` filter (e.g. `'active'`; omit for all statuses).
569
+ * @param variables - `{ appId, status?, admission?, hostUserId?, limit? }`
570
+ * (`limit` defaults to 200, max 1000).
423
571
  * @returns The matching {@link GmSession}s.
424
572
  * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` / `SCOPE_MISSING`.
425
573
  */
@@ -427,6 +575,48 @@ export class GameModelAPI {
427
575
  const data = await this.gql.request(GameModelSessionsDocument, variables);
428
576
  return data.gameModelSessions;
429
577
  }
578
+ /**
579
+ * **Sessions** — the authoritative state of one session at one revision: the
580
+ * session row and every *joined* participant. Pull it when you subscribe
581
+ * with {@link sessionChanged}, after a reconnect, or when the stream shows a
582
+ * revision gap; then apply only events whose `revision` is above
583
+ * `snapshot.revision`.
584
+ *
585
+ * @param variables - `{ appId, sessionId }`.
586
+ * @returns `{ revision, session, participants }`.
587
+ * @throws {CrowdyGraphQLError} `NOT_FOUND`, `UNAUTHENTICATED` / `SCOPE_MISSING`.
588
+ */
589
+ async sessionSnapshot(variables) {
590
+ const data = await this.gql.request(GameModelSessionSnapshotDocument, variables);
591
+ return data.gameModelSessionSnapshot;
592
+ }
593
+ /**
594
+ * **Sessions** — the events of a session with a revision strictly greater
595
+ * than `afterRevision`, oldest first: the gap-fill read behind
596
+ * {@link sessionChanged}. Events of ended sessions are retained for a bounded
597
+ * period, then purged.
598
+ *
599
+ * @param variables - `{ appId, sessionId, afterRevision, limit? }`;
600
+ * `afterRevision` is a decimal string (`'0'` for everything retained).
601
+ * @returns The matching {@link GmSessionEvent}s.
602
+ */
603
+ async sessionEvents(variables) {
604
+ const data = await this.gql.request(GameModelSessionEventsDocument, variables);
605
+ return data.gameModelSessionEvents;
606
+ }
607
+ /**
608
+ * **Sessions (operators)** — the full roster including departed participants
609
+ * (with `leftReason` and `incarnation`), each joined participant's live
610
+ * presence verdict (`fresh | grace | stale | legacy | left`), and the most
611
+ * recent events. Requires app-admin (`manage_apps`).
612
+ *
613
+ * @param variables - `{ appId, sessionId }`.
614
+ * @throws {CrowdyGraphQLError} `FORBIDDEN` without `manage_apps`.
615
+ */
616
+ async sessionInspect(variables) {
617
+ const data = await this.gql.request(GameModelSessionInspectDocument, variables);
618
+ return data.gameModelSessionInspect;
619
+ }
430
620
  /**
431
621
  * **Events** — query the function-invocation event log (audit trail) with
432
622
  * optional filters and pagination. Useful for debugging functions or showing