@crowdedkingdoms/crowdyjs 14.0.0 → 14.2.0-dev.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.
@@ -9,7 +9,7 @@ import { type AppGraphqlOperationsQuery, type AppUsageSummaryQuery, type PlayerP
9
9
  * counters are returned as string counters because they can exceed the 32-bit
10
10
  * Int range. `BigInt` ids are decimal strings.
11
11
  *
12
- * As of the unified galaxy API the per-environment rollups
12
+ * As of the v13 unified API the per-environment rollups
13
13
  * (environmentSummary/orgByEnvironment/environmentByApp) were retired with
14
14
  * dedicated customer environments — usage is org/app-scoped.
15
15
  *
@@ -8,7 +8,7 @@ import { AppGraphqlOperationsDocument, AppUsageSummaryDocument, PlayerPulseDocum
8
8
  * counters are returned as string counters because they can exceed the 32-bit
9
9
  * Int range. `BigInt` ids are decimal strings.
10
10
  *
11
- * As of the unified galaxy API the per-environment rollups
11
+ * As of the v13 unified API the per-environment rollups
12
12
  * (environmentSummary/orgByEnvironment/environmentByApp) were retired with
13
13
  * dedicated customer environments — usage is org/app-scoped.
14
14
  *
package/dist/errors.d.ts CHANGED
@@ -102,6 +102,149 @@ export declare class CrowdyGraphQLError extends CrowdyError {
102
102
  */
103
103
  get extensions(): Record<string, unknown> | undefined;
104
104
  }
105
+ /**
106
+ * Extension code the API uses to say "this app's datacenter is not serving".
107
+ *
108
+ * Distinct from {@link WRONG_DATACENTER_CODE}, and the distinction is the whole
109
+ * point: that one names an endpoint to move to, this one deliberately does not,
110
+ * because there is nowhere to move to.
111
+ */
112
+ export declare const APP_UNAVAILABLE_CODE = "APP_UNAVAILABLE";
113
+ /** Extension code the API uses to say "this app lives somewhere else". */
114
+ export declare const WRONG_DATACENTER_CODE = "WRONG_DATACENTER";
115
+ /**
116
+ * The app's own datacenter is not currently serving clients.
117
+ *
118
+ * WHAT MAKES THIS DIFFERENT FROM EVERY OTHER ERROR HERE: there is nothing the
119
+ * client, the SDK, or the player can do about it. A `WRONG_DATACENTER` is fixed
120
+ * by moving, an `UNAUTHENTICATED` by logging in, a `RATE_LIMITED` by waiting a
121
+ * measurable amount of time. This one is fixed by an operator, and the honest
122
+ * thing for an application to do is stop retrying in a loop and say so.
123
+ *
124
+ * {@link message} is written by the server to be shown to a player as-is: it says
125
+ * what is happening, that it is being worked on, and that there is nothing for
126
+ * them to do. Prefer it over inventing your own wording, because the server knows
127
+ * things the client does not — whether this is a brief drain or a whole
128
+ * datacenter — and its phrasing can improve without an SDK release.
129
+ *
130
+ * It carries NO endpoint, on purpose. Do not fall back to a cached one: the
131
+ * cached one is in the datacenter that is down.
132
+ *
133
+ * ```ts
134
+ * try {
135
+ * await client.grids.chunk(appId, x, y, z);
136
+ * } catch (err) {
137
+ * if (err instanceof CrowdyAppUnavailableError) {
138
+ * showBanner(err.message); // "This app is temporarily offline..."
139
+ * return; // do not retry in a tight loop
140
+ * }
141
+ * throw err;
142
+ * }
143
+ * ```
144
+ */
145
+ export declare class CrowdyAppUnavailableError extends CrowdyGraphQLError {
146
+ /** The app the server refused to serve, when it named one. */
147
+ get appId(): string | undefined;
148
+ /**
149
+ * The datacenter that is not serving. Diagnostic only — do not show a player a
150
+ * datacenter code, and do not try to reach it.
151
+ */
152
+ get appDatacenter(): string | undefined;
153
+ /**
154
+ * Whether it is worth trying again later. True today for every case the server
155
+ * raises this for; read it rather than assuming, so a future permanent variant
156
+ * (an app placed in a datacenter that has been destroyed, say) can say false
157
+ * without the SDK changing.
158
+ */
159
+ get retryable(): boolean;
160
+ }
161
+ /**
162
+ * Whose problem a failure is, as the platform attributes it.
163
+ *
164
+ * The one question a game cannot answer for itself: from inside a client there is no way
165
+ * to tell "your function is too slow" from "we were busy and never ran it" from "this app
166
+ * is out of budget", and until the server started saying so, all three arrived as the
167
+ * same sentence. Presentation is still yours — this says only whose fault it was.
168
+ */
169
+ export type CrowdyFaultBlame = 'PLATFORM' | 'AUTHOR' | 'BUDGET';
170
+ /**
171
+ * The stable reason a call into app-authored code failed.
172
+ *
173
+ * Deliberately coarse. It never names an engine, a module, a function or a limit,
174
+ * because those are the app developer's business and not the player's — the developer
175
+ * reads the full detail in the Crowdy console. Treat it as an open union: the server may
176
+ * add a code, and a client that switches on it should have a default branch.
177
+ */
178
+ export type CrowdyFaultCode = 'USER_CODE_ERROR' | 'USER_CODE_TOO_SLOW' | 'USER_CODE_LIMIT_EXCEEDED' | 'PLATFORM_BUSY' | 'PLATFORM_ERROR' | 'BUDGET_EXCEEDED' | 'RATE_LIMITED' | 'QUOTA_EXHAUSTED' | 'TEMPORARILY_DISABLED' | 'INVALID_REQUEST' | 'NOT_ALLOWED' | 'NOT_FOUND' | 'UNAUTHENTICATED' | 'WRONG_DATACENTER' | 'APP_UNAVAILABLE' | (string & {});
179
+ /** What the platform says about a failure, in the only vocabulary a player is shown. */
180
+ export interface CrowdyPlayerFault {
181
+ /** A stable, enumerated reason. Branch on this, never on a message. */
182
+ code: CrowdyFaultCode;
183
+ /** Whose problem it is. */
184
+ blame: CrowdyFaultBlame;
185
+ /** Whether repeating the identical call could succeed with nothing else changing. */
186
+ retryable: boolean;
187
+ }
188
+ /**
189
+ * A call into code the platform did not write failed, and the platform has said whose
190
+ * fault it was.
191
+ *
192
+ * WHY THIS EXISTS RATHER THAN A MESSAGE. Until 2026-08-11 a failure inside an app's own
193
+ * function reached the client as the server's internal text — `Evaluation timed out` was
194
+ * returned to players of every app for four days while the real cause was a platform
195
+ * query taking 1.2 seconds. A game rendering that string told its players their own game
196
+ * was broken, on the platform's behalf, in the platform's words. Nothing on the wire
197
+ * distinguished the three cases, so no game could have done better.
198
+ *
199
+ * Now the server attributes blame and the game decides what to render. Nothing in
200
+ * {@link message} is written by the app's code or by an engine; it is a platform-authored
201
+ * sentence, safe to show as-is, and you are expected to replace it with your own.
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * try {
206
+ * await client.compute.invoke({ appId, moduleName: 'combat', exportName: 'hit' });
207
+ * } catch (err) {
208
+ * const fault = playerFaultOf(err);
209
+ * if (!fault) throw err;
210
+ * if (fault.blame === 'PLATFORM' && fault.retryable) return retryLater();
211
+ * if (fault.blame === 'BUDGET') return showBanner('The arena is busy — one moment.');
212
+ * return showBanner('That move did not work.'); // AUTHOR: our bug, our wording
213
+ * }
214
+ * ```
215
+ */
216
+ export declare class CrowdyUserCodeFaultError extends CrowdyGraphQLError {
217
+ /** The platform's attribution: `{ code, blame, retryable }`. */
218
+ get fault(): CrowdyPlayerFault;
219
+ /** Whose problem this is. Shorthand for `fault.blame`. */
220
+ get blame(): CrowdyFaultBlame;
221
+ /** Whether repeating the identical call could succeed. Shorthand for `fault.retryable`. */
222
+ get retryable(): boolean;
223
+ }
224
+ /**
225
+ * Read the platform's attribution from EITHER carrier, so a game branches once.
226
+ *
227
+ * There are two, and the split is the server's rather than a wart of this SDK.
228
+ * `computeInvoke` and `playerComputeInvoke` FAIL — they throw, and the fault arrives in
229
+ * the GraphQL error's extensions. `gameModelInvoke` RETURNS — a denial or an evaluation
230
+ * failure is a gameplay verdict that still carries an event id and any writes that did
231
+ * apply, so it comes back as `success: false` with a `fault` field. Forcing either into
232
+ * the other's shape would lose something real, so this function accepts both and gives
233
+ * you one thing to switch on.
234
+ *
235
+ * Returns `null` when there is no fault: a successful result, or an error that is not an
236
+ * attributed one (a network drop, a timeout, an ordinary validation error elsewhere in
237
+ * the API). `null` means "this is not a question about whose code failed" — keep
238
+ * handling it the way you already do.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * const result = await client.gameModel.invoke({ appId, functionName, selfContainerId });
243
+ * const fault = playerFaultOf(result);
244
+ * if (fault?.retryable) scheduleRetry();
245
+ * ```
246
+ */
247
+ export declare function playerFaultOf(value: unknown): CrowdyPlayerFault | null;
105
248
  /**
106
249
  * A network-level failure before any HTTP response was received: DNS failure,
107
250
  * TLS error, connection refused, or an aborted `fetch`. Generally retryable
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,iFAAiF;AACjF,MAAM,WAAW,yBAAyB;IACxC,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACpC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEb,OAAO,EAAE,kBAAkB;CAKxC;AAED;;;;;;GAMG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAKzC;AAED;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;IACjD,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,yBAAyB,EAAE,CAAC;gBAExC,MAAM,EAAE,yBAAyB,EAAE;IAK/C;;;;;;OAMG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IAED;;;;OAIG;IACH,IAAI,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAEpD;CACF;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,KAAK,EAAE,OAAO;CAG3B;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,SAAS,EAAE,MAAM;CAG9B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;IAClD,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAKnG;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;CAAG"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/errors.js CHANGED
@@ -87,6 +87,174 @@ export class CrowdyGraphQLError extends CrowdyError {
87
87
  return this.graphqlErrors[0]?.extensions;
88
88
  }
89
89
  }
90
+ /**
91
+ * Extension code the API uses to say "this app's datacenter is not serving".
92
+ *
93
+ * Distinct from {@link WRONG_DATACENTER_CODE}, and the distinction is the whole
94
+ * point: that one names an endpoint to move to, this one deliberately does not,
95
+ * because there is nowhere to move to.
96
+ */
97
+ export const APP_UNAVAILABLE_CODE = 'APP_UNAVAILABLE';
98
+ /** Extension code the API uses to say "this app lives somewhere else". */
99
+ export const WRONG_DATACENTER_CODE = 'WRONG_DATACENTER';
100
+ /**
101
+ * The app's own datacenter is not currently serving clients.
102
+ *
103
+ * WHAT MAKES THIS DIFFERENT FROM EVERY OTHER ERROR HERE: there is nothing the
104
+ * client, the SDK, or the player can do about it. A `WRONG_DATACENTER` is fixed
105
+ * by moving, an `UNAUTHENTICATED` by logging in, a `RATE_LIMITED` by waiting a
106
+ * measurable amount of time. This one is fixed by an operator, and the honest
107
+ * thing for an application to do is stop retrying in a loop and say so.
108
+ *
109
+ * {@link message} is written by the server to be shown to a player as-is: it says
110
+ * what is happening, that it is being worked on, and that there is nothing for
111
+ * them to do. Prefer it over inventing your own wording, because the server knows
112
+ * things the client does not — whether this is a brief drain or a whole
113
+ * datacenter — and its phrasing can improve without an SDK release.
114
+ *
115
+ * It carries NO endpoint, on purpose. Do not fall back to a cached one: the
116
+ * cached one is in the datacenter that is down.
117
+ *
118
+ * ```ts
119
+ * try {
120
+ * await client.grids.chunk(appId, x, y, z);
121
+ * } catch (err) {
122
+ * if (err instanceof CrowdyAppUnavailableError) {
123
+ * showBanner(err.message); // "This app is temporarily offline..."
124
+ * return; // do not retry in a tight loop
125
+ * }
126
+ * throw err;
127
+ * }
128
+ * ```
129
+ */
130
+ export class CrowdyAppUnavailableError extends CrowdyGraphQLError {
131
+ /** The app the server refused to serve, when it named one. */
132
+ get appId() {
133
+ const value = this.extensions?.appId;
134
+ return typeof value === 'string' ? value : undefined;
135
+ }
136
+ /**
137
+ * The datacenter that is not serving. Diagnostic only — do not show a player a
138
+ * datacenter code, and do not try to reach it.
139
+ */
140
+ get appDatacenter() {
141
+ const value = this.extensions?.appDatacenter;
142
+ return typeof value === 'string' ? value : undefined;
143
+ }
144
+ /**
145
+ * Whether it is worth trying again later. True today for every case the server
146
+ * raises this for; read it rather than assuming, so a future permanent variant
147
+ * (an app placed in a datacenter that has been destroyed, say) can say false
148
+ * without the SDK changing.
149
+ */
150
+ get retryable() {
151
+ return this.extensions?.retryable !== false;
152
+ }
153
+ }
154
+ /**
155
+ * A call into code the platform did not write failed, and the platform has said whose
156
+ * fault it was.
157
+ *
158
+ * WHY THIS EXISTS RATHER THAN A MESSAGE. Until 2026-08-11 a failure inside an app's own
159
+ * function reached the client as the server's internal text — `Evaluation timed out` was
160
+ * returned to players of every app for four days while the real cause was a platform
161
+ * query taking 1.2 seconds. A game rendering that string told its players their own game
162
+ * was broken, on the platform's behalf, in the platform's words. Nothing on the wire
163
+ * distinguished the three cases, so no game could have done better.
164
+ *
165
+ * Now the server attributes blame and the game decides what to render. Nothing in
166
+ * {@link message} is written by the app's code or by an engine; it is a platform-authored
167
+ * sentence, safe to show as-is, and you are expected to replace it with your own.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * try {
172
+ * await client.compute.invoke({ appId, moduleName: 'combat', exportName: 'hit' });
173
+ * } catch (err) {
174
+ * const fault = playerFaultOf(err);
175
+ * if (!fault) throw err;
176
+ * if (fault.blame === 'PLATFORM' && fault.retryable) return retryLater();
177
+ * if (fault.blame === 'BUDGET') return showBanner('The arena is busy — one moment.');
178
+ * return showBanner('That move did not work.'); // AUTHOR: our bug, our wording
179
+ * }
180
+ * ```
181
+ */
182
+ export class CrowdyUserCodeFaultError extends CrowdyGraphQLError {
183
+ /** The platform's attribution: `{ code, blame, retryable }`. */
184
+ get fault() {
185
+ const extensions = this.extensions ?? {};
186
+ return {
187
+ code: extensions.code ?? 'PLATFORM_ERROR',
188
+ blame: extensions.blame ?? 'PLATFORM',
189
+ // Absent means "we did not say", and the safe reading of that is the one that does
190
+ // not send a client into a loop.
191
+ retryable: extensions.retryable === true,
192
+ };
193
+ }
194
+ /** Whose problem this is. Shorthand for `fault.blame`. */
195
+ get blame() {
196
+ return this.fault.blame;
197
+ }
198
+ /** Whether repeating the identical call could succeed. Shorthand for `fault.retryable`. */
199
+ get retryable() {
200
+ return this.fault.retryable;
201
+ }
202
+ }
203
+ /** Extensions carry `blame` only for a fault the platform has attributed. */
204
+ function faultFromExtensions(extensions) {
205
+ if (!extensions || typeof extensions.blame !== 'string')
206
+ return null;
207
+ return {
208
+ code: extensions.code ?? 'PLATFORM_ERROR',
209
+ blame: extensions.blame,
210
+ retryable: extensions.retryable === true,
211
+ };
212
+ }
213
+ /**
214
+ * Read the platform's attribution from EITHER carrier, so a game branches once.
215
+ *
216
+ * There are two, and the split is the server's rather than a wart of this SDK.
217
+ * `computeInvoke` and `playerComputeInvoke` FAIL — they throw, and the fault arrives in
218
+ * the GraphQL error's extensions. `gameModelInvoke` RETURNS — a denial or an evaluation
219
+ * failure is a gameplay verdict that still carries an event id and any writes that did
220
+ * apply, so it comes back as `success: false` with a `fault` field. Forcing either into
221
+ * the other's shape would lose something real, so this function accepts both and gives
222
+ * you one thing to switch on.
223
+ *
224
+ * Returns `null` when there is no fault: a successful result, or an error that is not an
225
+ * attributed one (a network drop, a timeout, an ordinary validation error elsewhere in
226
+ * the API). `null` means "this is not a question about whose code failed" — keep
227
+ * handling it the way you already do.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const result = await client.gameModel.invoke({ appId, functionName, selfContainerId });
232
+ * const fault = playerFaultOf(result);
233
+ * if (fault?.retryable) scheduleRetry();
234
+ * ```
235
+ */
236
+ export function playerFaultOf(value) {
237
+ if (!value || typeof value !== 'object')
238
+ return null;
239
+ if (value instanceof CrowdyGraphQLError) {
240
+ return faultFromExtensions(value.extensions);
241
+ }
242
+ // The in-band carrier: an invoke result that reports its own failure.
243
+ const result = value;
244
+ const fault = result.fault;
245
+ if (fault && typeof fault.blame === 'string') {
246
+ return {
247
+ code: fault.code ?? 'PLATFORM_ERROR',
248
+ blame: fault.blame,
249
+ retryable: fault.retryable === true,
250
+ };
251
+ }
252
+ // A raw GraphQL error payload, e.g. one entry pulled out of `graphqlErrors`.
253
+ const payload = value;
254
+ if (payload.extensions)
255
+ return faultFromExtensions(payload.extensions);
256
+ return null;
257
+ }
90
258
  /**
91
259
  * A network-level failure before any HTTP response was received: DNS failure,
92
260
  * TLS error, connection refused, or an aborted `fetch`. Generally retryable