@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.
- package/MIGRATION.md +6 -4
- package/README.md +43 -0
- package/dist/client.d.ts +27 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +69 -2
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +25 -0
- package/dist/datacenter-redirect.d.ts +49 -0
- package/dist/datacenter-redirect.d.ts.map +1 -0
- package/dist/datacenter-redirect.js +62 -0
- package/dist/domains/apps.d.ts +32 -4
- package/dist/domains/apps.d.ts.map +1 -1
- package/dist/domains/apps.js +35 -4
- package/dist/domains/controlPlane.d.ts +1 -1
- package/dist/domains/controlPlane.js +1 -1
- package/dist/domains/gameModel.d.ts +9 -1
- package/dist/domains/gameModel.d.ts.map +1 -1
- package/dist/domains/usage.d.ts +1 -1
- package/dist/domains/usage.js +1 -1
- package/dist/errors.d.ts +143 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +168 -0
- package/dist/generated/graphql.d.ts +354 -9
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +107 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/shared.d.ts.map +1 -1
- package/dist/kit/shared.js +5 -0
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/realtime.d.ts +26 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +83 -0
- package/package.json +1 -1
package/dist/domains/usage.d.ts
CHANGED
|
@@ -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
|
|
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
|
*
|
package/dist/domains/usage.js
CHANGED
|
@@ -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
|
|
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
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -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
|