@hydranium/protocol 1.0.0-next.363 → 1.0.0-next.365
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/lib/client/data-connection.d.ts +2 -2
- package/lib/client/data-connection.js +2 -2
- package/lib/errors.d.ts +19 -41
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +73 -75
- package/lib/errors.js.map +1 -1
- package/package.json +1 -1
- package/src/client/data-connection.ts +2 -2
- package/src/errors.ts +82 -79
|
@@ -171,8 +171,8 @@ export declare class DataConnection<TTransfer extends TransferElement, TServer e
|
|
|
171
171
|
* its process, and every call of that session then rejects with an error
|
|
172
172
|
* `isDuplicateClientIdError` recognises; an id the server reserves beyond
|
|
173
173
|
* {@link FRAMEWORK_CLIENT_IDS}, such as the integrity author, with one
|
|
174
|
-
* `isReservedClientIdError` recognises.
|
|
175
|
-
*
|
|
174
|
+
* `isReservedClientIdError` recognises. Test with those guards rather than
|
|
175
|
+
* `instanceof`, which misses an error from another copy of this package.
|
|
176
176
|
*
|
|
177
177
|
* Throws a {@link ReservedClientIdError} for an id in
|
|
178
178
|
* {@link FRAMEWORK_CLIENT_IDS} — those are authors the SERVER emits rather
|
|
@@ -146,8 +146,8 @@ class DataConnection extends rpc_connection_1.RpcConnection {
|
|
|
146
146
|
* its process, and every call of that session then rejects with an error
|
|
147
147
|
* `isDuplicateClientIdError` recognises; an id the server reserves beyond
|
|
148
148
|
* {@link FRAMEWORK_CLIENT_IDS}, such as the integrity author, with one
|
|
149
|
-
* `isReservedClientIdError` recognises.
|
|
150
|
-
*
|
|
149
|
+
* `isReservedClientIdError` recognises. Test with those guards rather than
|
|
150
|
+
* `instanceof`, which misses an error from another copy of this package.
|
|
151
151
|
*
|
|
152
152
|
* Throws a {@link ReservedClientIdError} for an id in
|
|
153
153
|
* {@link FRAMEWORK_CLIENT_IDS} — those are authors the SERVER emits rather
|
package/lib/errors.d.ts
CHANGED
|
@@ -13,17 +13,11 @@ import { type ModelVersion, type TextVersion } from './model-service/base-versio
|
|
|
13
13
|
* The catalogue declaration behind {@link ConflictError}'s sentence. It words
|
|
14
14
|
* the version mismatch rather than stating it: the two numbers mean nothing to
|
|
15
15
|
* an end user, and they stay in {@link ConflictErrorData}.
|
|
16
|
-
*
|
|
17
|
-
* Its English must keep containing {@link CONFLICT_ERROR_MESSAGE_MARKER}: the
|
|
18
|
-
* marker is tier 3 of {@link isConflictError}'s ladder, and it matches on text.
|
|
19
|
-
* That tier only ever works untranslated, which is why it is the last resort
|
|
20
|
-
* behind the numeric code rather than the primary check.
|
|
21
16
|
*/
|
|
22
17
|
export declare const STALE_BASE_VERSION_UPDATE: import("./messages/primitives").MessageDefinition<"The edit to {uri} was not applied: it was made to an older version of the document.">;
|
|
23
18
|
/**
|
|
24
19
|
* {@link STALE_BASE_VERSION_UPDATE}'s counterpart for an editor's save the
|
|
25
|
-
* server refused, since the editor's buffer is not the text it holds.
|
|
26
|
-
* English keeps the same marker.
|
|
20
|
+
* server refused, since the editor's buffer is not the text it holds.
|
|
27
21
|
*/
|
|
28
22
|
export declare const STALE_BASE_VERSION_SAVE: import("./messages/primitives").MessageDefinition<"The save of {uri} was not applied: it was made to an older version of the document.">;
|
|
29
23
|
/**
|
|
@@ -31,10 +25,11 @@ export declare const STALE_BASE_VERSION_SAVE: import("./messages/primitives").Me
|
|
|
31
25
|
* the error it identifies.
|
|
32
26
|
*
|
|
33
27
|
* The framework reserves the block 42000 to 42999 for these, and an adopter's
|
|
34
|
-
* own codes stay outside it: after an RPC
|
|
35
|
-
* an adopter's error carrying one of these
|
|
36
|
-
* takes the next unused number in the block, whichever
|
|
37
|
-
* is declared here, where tests hold every code distinct
|
|
28
|
+
* own codes stay outside it: after an RPC the proxy revives an error by its
|
|
29
|
+
* code, so an adopter's error carrying one of these can read as the
|
|
30
|
+
* framework's. A new code takes the next unused number in the block, whichever
|
|
31
|
+
* package raises it, and is declared here, where tests hold every code distinct
|
|
32
|
+
* and inside the block.
|
|
38
33
|
* The block sits clear of the range JSON-RPC 2.0 reserves for itself, -32768
|
|
39
34
|
* to -32000.
|
|
40
35
|
*/
|
|
@@ -80,7 +75,8 @@ export interface ConflictErrorData extends HydraniumMessageData {
|
|
|
80
75
|
* error envelope (`code`, `message`, `data`) — all three fields are
|
|
81
76
|
* preserved by RPC reconstruction. `createRpcProxy` revives it into
|
|
82
77
|
* this class on the receiving side; a path that does not leaves a
|
|
83
|
-
* plain `ResponseError`, readable through `err.data` only
|
|
78
|
+
* plain `ResponseError`, readable through `err.data` only and refused
|
|
79
|
+
* by {@link isConflictError}.
|
|
84
80
|
*
|
|
85
81
|
* Detection is driven by the required `baseVersion` field of every write
|
|
86
82
|
* request; a caller with no meaningful base version passes
|
|
@@ -254,40 +250,22 @@ export declare function reviveProtocolError(error: unknown): unknown;
|
|
|
254
250
|
*/
|
|
255
251
|
export declare function reviveProtocolErrorJson(json: string): ResponseError<unknown> | undefined;
|
|
256
252
|
/**
|
|
257
|
-
* Whether `error` is a {@link SessionClosedError}
|
|
258
|
-
*
|
|
259
|
-
*
|
|
253
|
+
* Whether `error` is a {@link SessionClosedError} whose getters read valid
|
|
254
|
+
* fields: one thrown, or one {@link reviveProtocolError revived}, from any
|
|
255
|
+
* copy of this package, or a subclass. A bare `ResponseError` is not one,
|
|
256
|
+
* whatever its name and fields, since it lacks the class's brand.
|
|
257
|
+
* `createRpcProxy` revives every rejection, so only a caller on a connection
|
|
258
|
+
* of its own needs to revive first. Each guard below works the same way.
|
|
260
259
|
*/
|
|
261
260
|
export declare function isSessionClosedError(error: unknown): error is SessionClosedError;
|
|
262
|
-
/** Whether `error` is a {@link DocumentNotOpenError}
|
|
261
|
+
/** Whether `error` is a {@link DocumentNotOpenError}. See {@link isSessionClosedError}. */
|
|
263
262
|
export declare function isDocumentNotOpenError(error: unknown): error is DocumentNotOpenError;
|
|
264
|
-
/** Whether `error` is a {@link DuplicateClientIdError}
|
|
263
|
+
/** Whether `error` is a {@link DuplicateClientIdError}. See {@link isSessionClosedError}. */
|
|
265
264
|
export declare function isDuplicateClientIdError(error: unknown): error is DuplicateClientIdError;
|
|
266
|
-
/** Whether `error` is a {@link ReservedClientIdError}
|
|
265
|
+
/** Whether `error` is a {@link ReservedClientIdError}. See {@link isSessionClosedError}. */
|
|
267
266
|
export declare function isReservedClientIdError(error: unknown): error is ReservedClientIdError;
|
|
268
|
-
/**
|
|
269
|
-
* Whether `error` is a {@link SaveFailedError} that holds its `failures`: one
|
|
270
|
-
* thrown, or one {@link reviveProtocolError revived}. Unlike the other guards,
|
|
271
|
-
* a bare `ResponseError` with its code is not one, since it has no `failures`
|
|
272
|
-
* to read. Revive it first.
|
|
273
|
-
*/
|
|
267
|
+
/** Whether `error` is a {@link SaveFailedError}. See {@link isSessionClosedError}. */
|
|
274
268
|
export declare function isSaveFailedError(error: unknown): error is SaveFailedError;
|
|
275
|
-
/**
|
|
276
|
-
* Type guard for {@link ConflictError}. Detection ladder:
|
|
277
|
-
*
|
|
278
|
-
* 1. `error.name === 'ConflictError'` — direct in-process throw, no
|
|
279
|
-
* RPC round-trip.
|
|
280
|
-
* 2. `(error as ResponseError).code === CONFLICT_ERROR_CODE` — the
|
|
281
|
-
* canonical wire-side check; the JSON-RPC `code` field is preserved
|
|
282
|
-
* across reconstruction, so any adopter catching after an RPC call
|
|
283
|
-
* hits this branch.
|
|
284
|
-
* 3. `error.message` contains the marker the English of
|
|
285
|
-
* {@link STALE_BASE_VERSION_UPDATE} and
|
|
286
|
-
* {@link STALE_BASE_VERSION_SAVE} carries — fallback for transports
|
|
287
|
-
* that re-wrap the message and drop the code (rare).
|
|
288
|
-
*
|
|
289
|
-
* `instanceof ConflictError` alone returns `false` on a rejection that was
|
|
290
|
-
* not {@link reviveProtocolError revived}, so callers do not use it.
|
|
291
|
-
*/
|
|
269
|
+
/** Whether `error` is a {@link ConflictError}. See {@link isSessionClosedError}. */
|
|
292
270
|
export declare function isConflictError(error: unknown): error is ConflictError;
|
|
293
271
|
//# sourceMappingURL=errors.d.ts.map
|
package/lib/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAiB,KAAK,oBAAoB,EAAe,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAkB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAcnG
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/lib/errors.js
CHANGED
|
@@ -36,17 +36,11 @@ const base_version_1 = require("./model-service/base-version");
|
|
|
36
36
|
* The catalogue declaration behind {@link ConflictError}'s sentence. It words
|
|
37
37
|
* the version mismatch rather than stating it: the two numbers mean nothing to
|
|
38
38
|
* an end user, and they stay in {@link ConflictErrorData}.
|
|
39
|
-
*
|
|
40
|
-
* Its English must keep containing {@link CONFLICT_ERROR_MESSAGE_MARKER}: the
|
|
41
|
-
* marker is tier 3 of {@link isConflictError}'s ladder, and it matches on text.
|
|
42
|
-
* That tier only ever works untranslated, which is why it is the last resort
|
|
43
|
-
* behind the numeric code rather than the primary check.
|
|
44
39
|
*/
|
|
45
40
|
exports.STALE_BASE_VERSION_UPDATE = (0, primitives_1.defineMessage)('hydranium/protocol/stale-base-version-update', 'The edit to {uri} was not applied: it was made to an older version of the document.');
|
|
46
41
|
/**
|
|
47
42
|
* {@link STALE_BASE_VERSION_UPDATE}'s counterpart for an editor's save the
|
|
48
|
-
* server refused, since the editor's buffer is not the text it holds.
|
|
49
|
-
* English keeps the same marker.
|
|
43
|
+
* server refused, since the editor's buffer is not the text it holds.
|
|
50
44
|
*/
|
|
51
45
|
exports.STALE_BASE_VERSION_SAVE = (0, primitives_1.defineMessage)('hydranium/protocol/stale-base-version-save', 'The save of {uri} was not applied: it was made to an older version of the document.');
|
|
52
46
|
/**
|
|
@@ -54,10 +48,11 @@ exports.STALE_BASE_VERSION_SAVE = (0, primitives_1.defineMessage)('hydranium/pro
|
|
|
54
48
|
* the error it identifies.
|
|
55
49
|
*
|
|
56
50
|
* The framework reserves the block 42000 to 42999 for these, and an adopter's
|
|
57
|
-
* own codes stay outside it: after an RPC
|
|
58
|
-
* an adopter's error carrying one of these
|
|
59
|
-
* takes the next unused number in the block, whichever
|
|
60
|
-
* is declared here, where tests hold every code distinct
|
|
51
|
+
* own codes stay outside it: after an RPC the proxy revives an error by its
|
|
52
|
+
* code, so an adopter's error carrying one of these can read as the
|
|
53
|
+
* framework's. A new code takes the next unused number in the block, whichever
|
|
54
|
+
* package raises it, and is declared here, where tests hold every code distinct
|
|
55
|
+
* and inside the block.
|
|
61
56
|
* The block sits clear of the range JSON-RPC 2.0 reserves for itself, -32768
|
|
62
57
|
* to -32000.
|
|
63
58
|
*/
|
|
@@ -91,7 +86,8 @@ exports.CONFLICT_ERROR_CODE = exports.HYDRANIUM_ERROR_CODES.conflict;
|
|
|
91
86
|
* error envelope (`code`, `message`, `data`) — all three fields are
|
|
92
87
|
* preserved by RPC reconstruction. `createRpcProxy` revives it into
|
|
93
88
|
* this class on the receiving side; a path that does not leaves a
|
|
94
|
-
* plain `ResponseError`, readable through `err.data` only
|
|
89
|
+
* plain `ResponseError`, readable through `err.data` only and refused
|
|
90
|
+
* by {@link isConflictError}.
|
|
95
91
|
*
|
|
96
92
|
* Detection is driven by the required `baseVersion` field of every write
|
|
97
93
|
* request; a caller with no meaningful base version passes
|
|
@@ -112,8 +108,8 @@ class ConflictError extends vscode_jsonrpc_1.ResponseError {
|
|
|
112
108
|
/** `message` words what was refused: an edit, or with {@link STALE_BASE_VERSION_SAVE} a save. */
|
|
113
109
|
constructor(uri, baseVersion, actualVersion, message = exports.STALE_BASE_VERSION_UPDATE) {
|
|
114
110
|
// The identity rides alongside the typed payload rather than replacing
|
|
115
|
-
// it
|
|
116
|
-
// adding the identity widens the payload rather than reshaping it.
|
|
111
|
+
// it. The getters, the guard and the reviver read the payload's fields,
|
|
112
|
+
// so adding the identity widens the payload rather than reshaping it.
|
|
117
113
|
super(exports.CONFLICT_ERROR_CODE, message.format({ uri }), {
|
|
118
114
|
uri,
|
|
119
115
|
baseVersion,
|
|
@@ -294,19 +290,31 @@ exports.SaveFailedError = SaveFailedError;
|
|
|
294
290
|
const PROTOCOL_ERROR_REVIVERS = new Map([
|
|
295
291
|
[
|
|
296
292
|
exports.CONFLICT_ERROR_CODE,
|
|
297
|
-
|
|
298
|
-
? new ConflictError(uri, (0, base_version_1.asModelVersion)(baseVersion), actualVersion)
|
|
299
|
-
: undefined
|
|
300
|
-
],
|
|
301
|
-
[exports.SESSION_CLOSED_ERROR_CODE, ({ clientId }) => (typeof clientId === 'string' ? new SessionClosedError(clientId) : undefined)],
|
|
302
|
-
[
|
|
303
|
-
exports.DOCUMENT_NOT_OPEN_ERROR_CODE,
|
|
304
|
-
({ uri, clientId }) => (typeof uri === 'string' && typeof clientId === 'string' ? new DocumentNotOpenError(uri, clientId) : undefined)
|
|
293
|
+
data => (hasConflictFields(data) ? new ConflictError(data.uri, (0, base_version_1.asModelVersion)(data.baseVersion), data.actualVersion) : undefined)
|
|
305
294
|
],
|
|
306
|
-
[exports.
|
|
307
|
-
[exports.
|
|
308
|
-
[exports.
|
|
295
|
+
[exports.SESSION_CLOSED_ERROR_CODE, data => (hasClientId(data) ? new SessionClosedError(data.clientId) : undefined)],
|
|
296
|
+
[exports.DOCUMENT_NOT_OPEN_ERROR_CODE, data => (hasDocumentNotOpenFields(data) ? new DocumentNotOpenError(data.uri, data.clientId) : undefined)],
|
|
297
|
+
[exports.DUPLICATE_CLIENT_ID_ERROR_CODE, data => (hasClientId(data) ? new DuplicateClientIdError(data.clientId) : undefined)],
|
|
298
|
+
[exports.RESERVED_CLIENT_ID_ERROR_CODE, data => (hasClientId(data) ? new ReservedClientIdError(data.clientId) : undefined)],
|
|
299
|
+
[exports.SAVE_FAILED_ERROR_CODE, data => (hasSaveFailures(data) ? new SaveFailedError(data.failures) : undefined)]
|
|
309
300
|
]);
|
|
301
|
+
// One check per class of the fields its getters read, run by its reviver on
|
|
302
|
+
// `data` and by its guard on the error, through the getters.
|
|
303
|
+
function hasConflictFields(value) {
|
|
304
|
+
return (isRecord(value) && typeof value.uri === 'string' && typeof value.baseVersion === 'number' && typeof value.actualVersion === 'number');
|
|
305
|
+
}
|
|
306
|
+
function hasClientId(value) {
|
|
307
|
+
return isRecord(value) && typeof value.clientId === 'string';
|
|
308
|
+
}
|
|
309
|
+
function hasDocumentNotOpenFields(value) {
|
|
310
|
+
return isRecord(value) && typeof value.uri === 'string' && typeof value.clientId === 'string';
|
|
311
|
+
}
|
|
312
|
+
function hasSaveFailures(value) {
|
|
313
|
+
return isRecord(value) && isSaveFailureList(value.failures);
|
|
314
|
+
}
|
|
315
|
+
function isRecord(value) {
|
|
316
|
+
return typeof value === 'object' && value !== null;
|
|
317
|
+
}
|
|
310
318
|
function isSaveFailureList(value) {
|
|
311
319
|
return (Array.isArray(value) &&
|
|
312
320
|
value.every((failure) => typeof failure === 'object' &&
|
|
@@ -366,69 +374,59 @@ function reviveProtocolErrorJson(json) {
|
|
|
366
374
|
return isResponseError(revived) ? revived : undefined;
|
|
367
375
|
}
|
|
368
376
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
377
|
+
* Mark `errorClass`'s prototype with a brand its guard checks, so every
|
|
378
|
+
* instance and every subclass carries it, and nothing else does. `Symbol.for`
|
|
379
|
+
* gives each copy of this package the same symbol, which `instanceof` does
|
|
380
|
+
* not, so the key must stay the same across releases.
|
|
381
|
+
*/
|
|
382
|
+
function brand(errorClass, name) {
|
|
383
|
+
const key = Symbol.for(`hydranium/protocol/${name}`);
|
|
384
|
+
Object.defineProperty(errorClass.prototype, key, { value: true });
|
|
385
|
+
return key;
|
|
386
|
+
}
|
|
387
|
+
const CONFLICT_BRAND = brand(ConflictError, 'ConflictError');
|
|
388
|
+
const SESSION_CLOSED_BRAND = brand(SessionClosedError, 'SessionClosedError');
|
|
389
|
+
const DOCUMENT_NOT_OPEN_BRAND = brand(DocumentNotOpenError, 'DocumentNotOpenError');
|
|
390
|
+
const DUPLICATE_CLIENT_ID_BRAND = brand(DuplicateClientIdError, 'DuplicateClientIdError');
|
|
391
|
+
const RESERVED_CLIENT_ID_BRAND = brand(ReservedClientIdError, 'ReservedClientIdError');
|
|
392
|
+
const SAVE_FAILED_BRAND = brand(SaveFailedError, 'SaveFailedError');
|
|
393
|
+
/**
|
|
394
|
+
* Whether `error` is a {@link SessionClosedError} whose getters read valid
|
|
395
|
+
* fields: one thrown, or one {@link reviveProtocolError revived}, from any
|
|
396
|
+
* copy of this package, or a subclass. A bare `ResponseError` is not one,
|
|
397
|
+
* whatever its name and fields, since it lacks the class's brand.
|
|
398
|
+
* `createRpcProxy` revives every rejection, so only a caller on a connection
|
|
399
|
+
* of its own needs to revive first. Each guard below works the same way.
|
|
372
400
|
*/
|
|
373
401
|
function isSessionClosedError(error) {
|
|
374
|
-
return
|
|
402
|
+
return hasBrand(error, SESSION_CLOSED_BRAND) && hasClientId(error);
|
|
375
403
|
}
|
|
376
|
-
/** Whether `error` is a {@link DocumentNotOpenError}
|
|
404
|
+
/** Whether `error` is a {@link DocumentNotOpenError}. See {@link isSessionClosedError}. */
|
|
377
405
|
function isDocumentNotOpenError(error) {
|
|
378
|
-
return
|
|
406
|
+
return hasBrand(error, DOCUMENT_NOT_OPEN_BRAND) && hasDocumentNotOpenFields(error);
|
|
379
407
|
}
|
|
380
|
-
/** Whether `error` is a {@link DuplicateClientIdError}
|
|
408
|
+
/** Whether `error` is a {@link DuplicateClientIdError}. See {@link isSessionClosedError}. */
|
|
381
409
|
function isDuplicateClientIdError(error) {
|
|
382
|
-
return
|
|
410
|
+
return hasBrand(error, DUPLICATE_CLIENT_ID_BRAND) && hasClientId(error);
|
|
383
411
|
}
|
|
384
|
-
/** Whether `error` is a {@link ReservedClientIdError}
|
|
412
|
+
/** Whether `error` is a {@link ReservedClientIdError}. See {@link isSessionClosedError}. */
|
|
385
413
|
function isReservedClientIdError(error) {
|
|
386
|
-
return
|
|
414
|
+
return hasBrand(error, RESERVED_CLIENT_ID_BRAND) && hasClientId(error);
|
|
387
415
|
}
|
|
388
|
-
/**
|
|
389
|
-
* Whether `error` is a {@link SaveFailedError} that holds its `failures`: one
|
|
390
|
-
* thrown, or one {@link reviveProtocolError revived}. Unlike the other guards,
|
|
391
|
-
* a bare `ResponseError` with its code is not one, since it has no `failures`
|
|
392
|
-
* to read. Revive it first.
|
|
393
|
-
*/
|
|
416
|
+
/** Whether `error` is a {@link SaveFailedError}. See {@link isSessionClosedError}. */
|
|
394
417
|
function isSaveFailedError(error) {
|
|
395
|
-
return (
|
|
418
|
+
return hasBrand(error, SAVE_FAILED_BRAND) && hasSaveFailures(error);
|
|
396
419
|
}
|
|
397
|
-
|
|
398
|
-
|
|
420
|
+
/** Whether `error` is a {@link ConflictError}. See {@link isSessionClosedError}. */
|
|
421
|
+
function isConflictError(error) {
|
|
422
|
+
return hasBrand(error, CONFLICT_BRAND) && hasConflictFields(error);
|
|
399
423
|
}
|
|
400
|
-
/** Marker substring present in every {@link ConflictError} message, used by
|
|
401
|
-
* {@link isConflictError} as a fallback when a transport re-wraps the error
|
|
402
|
-
* and drops the JSON-RPC code. */
|
|
403
|
-
const CONFLICT_ERROR_MESSAGE_MARKER = ': it was made to an older version of the document';
|
|
404
424
|
/**
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* RPC round-trip.
|
|
409
|
-
* 2. `(error as ResponseError).code === CONFLICT_ERROR_CODE` — the
|
|
410
|
-
* canonical wire-side check; the JSON-RPC `code` field is preserved
|
|
411
|
-
* across reconstruction, so any adopter catching after an RPC call
|
|
412
|
-
* hits this branch.
|
|
413
|
-
* 3. `error.message` contains the marker the English of
|
|
414
|
-
* {@link STALE_BASE_VERSION_UPDATE} and
|
|
415
|
-
* {@link STALE_BASE_VERSION_SAVE} carries — fallback for transports
|
|
416
|
-
* that re-wrap the message and drop the code (rare).
|
|
417
|
-
*
|
|
418
|
-
* `instanceof ConflictError` alone returns `false` on a rejection that was
|
|
419
|
-
* not {@link reviveProtocolError revived}, so callers do not use it.
|
|
425
|
+
* Whether `error` carries the brand `key` and the `data` record its getters
|
|
426
|
+
* read, so a guard can check the fields through them. A copy whose class
|
|
427
|
+
* lacks a getter fails that check, since the brand alone is public.
|
|
420
428
|
*/
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
if (error.name === 'ConflictError') {
|
|
426
|
-
return true;
|
|
427
|
-
}
|
|
428
|
-
const code = error.code;
|
|
429
|
-
if (code === exports.CONFLICT_ERROR_CODE) {
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
return typeof error.message === 'string' && error.message.includes(CONFLICT_ERROR_MESSAGE_MARKER);
|
|
429
|
+
function hasBrand(error, key) {
|
|
430
|
+
return isResponseError(error) && Reflect.get(error, key) === true && isRecord(error.data);
|
|
433
431
|
}
|
|
434
432
|
//# sourceMappingURL=errors.js.map
|
package/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;AAElF,mDAA+C;AAC/C,sDAA8F;AAC9F,+DAAmG;AAEnG;;;;;;;;;;GAUG;AAEH
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -245,8 +245,8 @@ export class DataConnection<
|
|
|
245
245
|
* its process, and every call of that session then rejects with an error
|
|
246
246
|
* `isDuplicateClientIdError` recognises; an id the server reserves beyond
|
|
247
247
|
* {@link FRAMEWORK_CLIENT_IDS}, such as the integrity author, with one
|
|
248
|
-
* `isReservedClientIdError` recognises.
|
|
249
|
-
*
|
|
248
|
+
* `isReservedClientIdError` recognises. Test with those guards rather than
|
|
249
|
+
* `instanceof`, which misses an error from another copy of this package.
|
|
250
250
|
*
|
|
251
251
|
* Throws a {@link ReservedClientIdError} for an id in
|
|
252
252
|
* {@link FRAMEWORK_CLIENT_IDS} — those are authors the SERVER emits rather
|
package/src/errors.ts
CHANGED
|
@@ -27,11 +27,6 @@ import { asModelVersion, type ModelVersion, type TextVersion } from './model-ser
|
|
|
27
27
|
* The catalogue declaration behind {@link ConflictError}'s sentence. It words
|
|
28
28
|
* the version mismatch rather than stating it: the two numbers mean nothing to
|
|
29
29
|
* an end user, and they stay in {@link ConflictErrorData}.
|
|
30
|
-
*
|
|
31
|
-
* Its English must keep containing {@link CONFLICT_ERROR_MESSAGE_MARKER}: the
|
|
32
|
-
* marker is tier 3 of {@link isConflictError}'s ladder, and it matches on text.
|
|
33
|
-
* That tier only ever works untranslated, which is why it is the last resort
|
|
34
|
-
* behind the numeric code rather than the primary check.
|
|
35
30
|
*/
|
|
36
31
|
export const STALE_BASE_VERSION_UPDATE = defineMessage(
|
|
37
32
|
'hydranium/protocol/stale-base-version-update',
|
|
@@ -40,8 +35,7 @@ export const STALE_BASE_VERSION_UPDATE = defineMessage(
|
|
|
40
35
|
|
|
41
36
|
/**
|
|
42
37
|
* {@link STALE_BASE_VERSION_UPDATE}'s counterpart for an editor's save the
|
|
43
|
-
* server refused, since the editor's buffer is not the text it holds.
|
|
44
|
-
* English keeps the same marker.
|
|
38
|
+
* server refused, since the editor's buffer is not the text it holds.
|
|
45
39
|
*/
|
|
46
40
|
export const STALE_BASE_VERSION_SAVE = defineMessage(
|
|
47
41
|
'hydranium/protocol/stale-base-version-save',
|
|
@@ -53,10 +47,11 @@ export const STALE_BASE_VERSION_SAVE = defineMessage(
|
|
|
53
47
|
* the error it identifies.
|
|
54
48
|
*
|
|
55
49
|
* The framework reserves the block 42000 to 42999 for these, and an adopter's
|
|
56
|
-
* own codes stay outside it: after an RPC
|
|
57
|
-
* an adopter's error carrying one of these
|
|
58
|
-
* takes the next unused number in the block, whichever
|
|
59
|
-
* is declared here, where tests hold every code distinct
|
|
50
|
+
* own codes stay outside it: after an RPC the proxy revives an error by its
|
|
51
|
+
* code, so an adopter's error carrying one of these can read as the
|
|
52
|
+
* framework's. A new code takes the next unused number in the block, whichever
|
|
53
|
+
* package raises it, and is declared here, where tests hold every code distinct
|
|
54
|
+
* and inside the block.
|
|
60
55
|
* The block sits clear of the range JSON-RPC 2.0 reserves for itself, -32768
|
|
61
56
|
* to -32000.
|
|
62
57
|
*/
|
|
@@ -105,7 +100,8 @@ export interface ConflictErrorData extends HydraniumMessageData {
|
|
|
105
100
|
* error envelope (`code`, `message`, `data`) — all three fields are
|
|
106
101
|
* preserved by RPC reconstruction. `createRpcProxy` revives it into
|
|
107
102
|
* this class on the receiving side; a path that does not leaves a
|
|
108
|
-
* plain `ResponseError`, readable through `err.data` only
|
|
103
|
+
* plain `ResponseError`, readable through `err.data` only and refused
|
|
104
|
+
* by {@link isConflictError}.
|
|
109
105
|
*
|
|
110
106
|
* Detection is driven by the required `baseVersion` field of every write
|
|
111
107
|
* request; a caller with no meaningful base version passes
|
|
@@ -131,8 +127,8 @@ export class ConflictError extends ResponseError<ConflictErrorData> {
|
|
|
131
127
|
message: typeof STALE_BASE_VERSION_UPDATE | typeof STALE_BASE_VERSION_SAVE = STALE_BASE_VERSION_UPDATE
|
|
132
128
|
) {
|
|
133
129
|
// The identity rides alongside the typed payload rather than replacing
|
|
134
|
-
// it
|
|
135
|
-
// adding the identity widens the payload rather than reshaping it.
|
|
130
|
+
// it. The getters, the guard and the reviver read the payload's fields,
|
|
131
|
+
// so adding the identity widens the payload rather than reshaping it.
|
|
136
132
|
super(CONFLICT_ERROR_CODE, message.format({ uri }), {
|
|
137
133
|
uri,
|
|
138
134
|
baseVersion,
|
|
@@ -366,21 +362,39 @@ type ProtocolErrorReviver = (data: Readonly<Record<string, unknown>>) => Respons
|
|
|
366
362
|
const PROTOCOL_ERROR_REVIVERS: ReadonlyMap<number, ProtocolErrorReviver> = new Map<number, ProtocolErrorReviver>([
|
|
367
363
|
[
|
|
368
364
|
CONFLICT_ERROR_CODE,
|
|
369
|
-
(
|
|
370
|
-
typeof uri === 'string' && typeof baseVersion === 'number' && typeof actualVersion === 'number'
|
|
371
|
-
? new ConflictError(uri, asModelVersion(baseVersion), actualVersion)
|
|
372
|
-
: undefined
|
|
373
|
-
],
|
|
374
|
-
[SESSION_CLOSED_ERROR_CODE, ({ clientId }) => (typeof clientId === 'string' ? new SessionClosedError(clientId) : undefined)],
|
|
375
|
-
[
|
|
376
|
-
DOCUMENT_NOT_OPEN_ERROR_CODE,
|
|
377
|
-
({ uri, clientId }) => (typeof uri === 'string' && typeof clientId === 'string' ? new DocumentNotOpenError(uri, clientId) : undefined)
|
|
365
|
+
data => (hasConflictFields(data) ? new ConflictError(data.uri, asModelVersion(data.baseVersion), data.actualVersion) : undefined)
|
|
378
366
|
],
|
|
379
|
-
[
|
|
380
|
-
[
|
|
381
|
-
[
|
|
367
|
+
[SESSION_CLOSED_ERROR_CODE, data => (hasClientId(data) ? new SessionClosedError(data.clientId) : undefined)],
|
|
368
|
+
[DOCUMENT_NOT_OPEN_ERROR_CODE, data => (hasDocumentNotOpenFields(data) ? new DocumentNotOpenError(data.uri, data.clientId) : undefined)],
|
|
369
|
+
[DUPLICATE_CLIENT_ID_ERROR_CODE, data => (hasClientId(data) ? new DuplicateClientIdError(data.clientId) : undefined)],
|
|
370
|
+
[RESERVED_CLIENT_ID_ERROR_CODE, data => (hasClientId(data) ? new ReservedClientIdError(data.clientId) : undefined)],
|
|
371
|
+
[SAVE_FAILED_ERROR_CODE, data => (hasSaveFailures(data) ? new SaveFailedError(data.failures) : undefined)]
|
|
382
372
|
]);
|
|
383
373
|
|
|
374
|
+
// One check per class of the fields its getters read, run by its reviver on
|
|
375
|
+
// `data` and by its guard on the error, through the getters.
|
|
376
|
+
function hasConflictFields(value: unknown): value is { uri: string; baseVersion: number; actualVersion: TextVersion } {
|
|
377
|
+
return (
|
|
378
|
+
isRecord(value) && typeof value.uri === 'string' && typeof value.baseVersion === 'number' && typeof value.actualVersion === 'number'
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function hasClientId(value: unknown): value is { clientId: string } {
|
|
383
|
+
return isRecord(value) && typeof value.clientId === 'string';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function hasDocumentNotOpenFields(value: unknown): value is { uri: string; clientId: string } {
|
|
387
|
+
return isRecord(value) && typeof value.uri === 'string' && typeof value.clientId === 'string';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function hasSaveFailures(value: unknown): value is { failures: SaveFailure[] } {
|
|
391
|
+
return isRecord(value) && isSaveFailureList(value.failures);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
395
|
+
return typeof value === 'object' && value !== null;
|
|
396
|
+
}
|
|
397
|
+
|
|
384
398
|
function isSaveFailureList(value: unknown): value is SaveFailure[] {
|
|
385
399
|
return (
|
|
386
400
|
Array.isArray(value) &&
|
|
@@ -450,77 +464,66 @@ export function reviveProtocolErrorJson(json: string): ResponseError<unknown> |
|
|
|
450
464
|
}
|
|
451
465
|
|
|
452
466
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
467
|
+
* Mark `errorClass`'s prototype with a brand its guard checks, so every
|
|
468
|
+
* instance and every subclass carries it, and nothing else does. `Symbol.for`
|
|
469
|
+
* gives each copy of this package the same symbol, which `instanceof` does
|
|
470
|
+
* not, so the key must stay the same across releases.
|
|
471
|
+
*/
|
|
472
|
+
function brand(errorClass: { readonly prototype: object }, name: string): symbol {
|
|
473
|
+
const key = Symbol.for(`hydranium/protocol/${name}`);
|
|
474
|
+
Object.defineProperty(errorClass.prototype, key, { value: true });
|
|
475
|
+
return key;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const CONFLICT_BRAND = brand(ConflictError, 'ConflictError');
|
|
479
|
+
const SESSION_CLOSED_BRAND = brand(SessionClosedError, 'SessionClosedError');
|
|
480
|
+
const DOCUMENT_NOT_OPEN_BRAND = brand(DocumentNotOpenError, 'DocumentNotOpenError');
|
|
481
|
+
const DUPLICATE_CLIENT_ID_BRAND = brand(DuplicateClientIdError, 'DuplicateClientIdError');
|
|
482
|
+
const RESERVED_CLIENT_ID_BRAND = brand(ReservedClientIdError, 'ReservedClientIdError');
|
|
483
|
+
const SAVE_FAILED_BRAND = brand(SaveFailedError, 'SaveFailedError');
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Whether `error` is a {@link SessionClosedError} whose getters read valid
|
|
487
|
+
* fields: one thrown, or one {@link reviveProtocolError revived}, from any
|
|
488
|
+
* copy of this package, or a subclass. A bare `ResponseError` is not one,
|
|
489
|
+
* whatever its name and fields, since it lacks the class's brand.
|
|
490
|
+
* `createRpcProxy` revives every rejection, so only a caller on a connection
|
|
491
|
+
* of its own needs to revive first. Each guard below works the same way.
|
|
456
492
|
*/
|
|
457
493
|
export function isSessionClosedError(error: unknown): error is SessionClosedError {
|
|
458
|
-
return
|
|
494
|
+
return hasBrand(error, SESSION_CLOSED_BRAND) && hasClientId(error);
|
|
459
495
|
}
|
|
460
496
|
|
|
461
|
-
/** Whether `error` is a {@link DocumentNotOpenError}
|
|
497
|
+
/** Whether `error` is a {@link DocumentNotOpenError}. See {@link isSessionClosedError}. */
|
|
462
498
|
export function isDocumentNotOpenError(error: unknown): error is DocumentNotOpenError {
|
|
463
|
-
return
|
|
499
|
+
return hasBrand(error, DOCUMENT_NOT_OPEN_BRAND) && hasDocumentNotOpenFields(error);
|
|
464
500
|
}
|
|
465
501
|
|
|
466
|
-
/** Whether `error` is a {@link DuplicateClientIdError}
|
|
502
|
+
/** Whether `error` is a {@link DuplicateClientIdError}. See {@link isSessionClosedError}. */
|
|
467
503
|
export function isDuplicateClientIdError(error: unknown): error is DuplicateClientIdError {
|
|
468
|
-
return
|
|
504
|
+
return hasBrand(error, DUPLICATE_CLIENT_ID_BRAND) && hasClientId(error);
|
|
469
505
|
}
|
|
470
506
|
|
|
471
|
-
/** Whether `error` is a {@link ReservedClientIdError}
|
|
507
|
+
/** Whether `error` is a {@link ReservedClientIdError}. See {@link isSessionClosedError}. */
|
|
472
508
|
export function isReservedClientIdError(error: unknown): error is ReservedClientIdError {
|
|
473
|
-
return
|
|
509
|
+
return hasBrand(error, RESERVED_CLIENT_ID_BRAND) && hasClientId(error);
|
|
474
510
|
}
|
|
475
511
|
|
|
476
|
-
/**
|
|
477
|
-
* Whether `error` is a {@link SaveFailedError} that holds its `failures`: one
|
|
478
|
-
* thrown, or one {@link reviveProtocolError revived}. Unlike the other guards,
|
|
479
|
-
* a bare `ResponseError` with its code is not one, since it has no `failures`
|
|
480
|
-
* to read. Revive it first.
|
|
481
|
-
*/
|
|
512
|
+
/** Whether `error` is a {@link SaveFailedError}. See {@link isSessionClosedError}. */
|
|
482
513
|
export function isSaveFailedError(error: unknown): error is SaveFailedError {
|
|
483
|
-
return (
|
|
484
|
-
hasErrorIdentity(error, 'SaveFailedError', SAVE_FAILED_ERROR_CODE) && isSaveFailureList((error as Partial<SaveFailedError>).failures)
|
|
485
|
-
);
|
|
514
|
+
return hasBrand(error, SAVE_FAILED_BRAND) && hasSaveFailures(error);
|
|
486
515
|
}
|
|
487
516
|
|
|
488
|
-
|
|
489
|
-
|
|
517
|
+
/** Whether `error` is a {@link ConflictError}. See {@link isSessionClosedError}. */
|
|
518
|
+
export function isConflictError(error: unknown): error is ConflictError {
|
|
519
|
+
return hasBrand(error, CONFLICT_BRAND) && hasConflictFields(error);
|
|
490
520
|
}
|
|
491
521
|
|
|
492
|
-
/** Marker substring present in every {@link ConflictError} message, used by
|
|
493
|
-
* {@link isConflictError} as a fallback when a transport re-wraps the error
|
|
494
|
-
* and drops the JSON-RPC code. */
|
|
495
|
-
const CONFLICT_ERROR_MESSAGE_MARKER = ': it was made to an older version of the document';
|
|
496
|
-
|
|
497
522
|
/**
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
* RPC round-trip.
|
|
502
|
-
* 2. `(error as ResponseError).code === CONFLICT_ERROR_CODE` — the
|
|
503
|
-
* canonical wire-side check; the JSON-RPC `code` field is preserved
|
|
504
|
-
* across reconstruction, so any adopter catching after an RPC call
|
|
505
|
-
* hits this branch.
|
|
506
|
-
* 3. `error.message` contains the marker the English of
|
|
507
|
-
* {@link STALE_BASE_VERSION_UPDATE} and
|
|
508
|
-
* {@link STALE_BASE_VERSION_SAVE} carries — fallback for transports
|
|
509
|
-
* that re-wrap the message and drop the code (rare).
|
|
510
|
-
*
|
|
511
|
-
* `instanceof ConflictError` alone returns `false` on a rejection that was
|
|
512
|
-
* not {@link reviveProtocolError revived}, so callers do not use it.
|
|
523
|
+
* Whether `error` carries the brand `key` and the `data` record its getters
|
|
524
|
+
* read, so a guard can check the fields through them. A copy whose class
|
|
525
|
+
* lacks a getter fails that check, since the brand alone is public.
|
|
513
526
|
*/
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
if (error.name === 'ConflictError') {
|
|
519
|
-
return true;
|
|
520
|
-
}
|
|
521
|
-
const code = (error as Partial<ResponseError<unknown>>).code;
|
|
522
|
-
if (code === CONFLICT_ERROR_CODE) {
|
|
523
|
-
return true;
|
|
524
|
-
}
|
|
525
|
-
return typeof error.message === 'string' && error.message.includes(CONFLICT_ERROR_MESSAGE_MARKER);
|
|
527
|
+
function hasBrand(error: unknown, key: symbol): error is ResponseError<unknown> {
|
|
528
|
+
return isResponseError(error) && Reflect.get(error, key) === true && isRecord(error.data);
|
|
526
529
|
}
|