@interop/was-client 0.48.0 → 0.49.0
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/README.md +13 -3
- package/dist/sync/index.d.ts +19 -8
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +18 -7
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/port.d.ts +5 -0
- package/dist/sync/port.d.ts.map +1 -1
- package/dist/sync/port.js +76 -34
- package/dist/sync/port.js.map +1 -1
- package/dist/sync/predicates.d.ts +67 -0
- package/dist/sync/predicates.d.ts.map +1 -0
- package/dist/sync/predicates.js +85 -0
- package/dist/sync/predicates.js.map +1 -0
- package/dist/sync/types.d.ts +9 -1
- package/dist/sync/types.d.ts.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -840,7 +840,17 @@ plugs into:
|
|
|
840
840
|
itself never touches keys. A rejected precondition throws
|
|
841
841
|
`WasSyncConflictError` (412); a delete of an already-gone resource throws
|
|
842
842
|
`WasSyncNotFoundError` (404) -- both catchable subtypes of the core
|
|
843
|
-
`PreconditionFailedError` / `NotFoundError`.
|
|
843
|
+
`PreconditionFailedError` / `NotFoundError`. Every other failure arrives as
|
|
844
|
+
the typed `WasError` subclass for its status, carrying the server's
|
|
845
|
+
`problem+json` fields.
|
|
846
|
+
- **`isSyncConflictError` / `isSyncNotFoundError` / `isSyncAuthError` /
|
|
847
|
+
`isUnknownEpochError`** classify those signals by `err.name`. Use them rather
|
|
848
|
+
than `instanceof`: the port and the `DocCipher` are seams your app injects,
|
|
849
|
+
and a tree that resolves two copies of this package makes an `instanceof`
|
|
850
|
+
check quietly false. Read what you need off the matched value, such as
|
|
851
|
+
`err.status` on an auth error to tell a 401 from the masked 404.
|
|
852
|
+
- **`SyncStatus`** (`'idle' | 'syncing' | 'synced' | 'error'`) is the closed
|
|
853
|
+
vocabulary a replication driver reports one feed's state through.
|
|
844
854
|
- **`DocCipher`** is the per-collection encrypt/decrypt seam sitting above the
|
|
845
855
|
port: it turns a JSON document into its stored body (minting the resource id)
|
|
846
856
|
and back. `createPlaintextDocCipher(...)` is the crypto-free identity
|
|
@@ -869,7 +879,7 @@ import {
|
|
|
869
879
|
createPlaintextDocCipher,
|
|
870
880
|
deriveSpaceId,
|
|
871
881
|
ensureSpaceAndCollection,
|
|
872
|
-
|
|
882
|
+
isSyncConflictError
|
|
873
883
|
} from '@interop/was-client/sync'
|
|
874
884
|
|
|
875
885
|
const spaceId = deriveSpaceId(controllerDid) // same Space on every device
|
|
@@ -889,7 +899,7 @@ const { id, envelope } = await cipher.encrypt({ data: { note: 'hello' } })
|
|
|
889
899
|
try {
|
|
890
900
|
await port.putContent({ id, data: envelope, ifNoneMatch: true })
|
|
891
901
|
} catch (err) {
|
|
892
|
-
if (!(err
|
|
902
|
+
if (!isSyncConflictError(err)) {
|
|
893
903
|
throw err
|
|
894
904
|
}
|
|
895
905
|
// 412 on a content-addressed insert: another replica already wrote this
|
package/dist/sync/index.d.ts
CHANGED
|
@@ -16,18 +16,28 @@
|
|
|
16
16
|
* - `ensureSpaceAndCollection` / `ensureSpace` -- idempotent Space +
|
|
17
17
|
* Collection provisioning, and its Space half alone (ensure the Space once,
|
|
18
18
|
* then thread the returned description through every collection).
|
|
19
|
+
* - `isSyncConflictError` / `isSyncNotFoundError` / `isSyncAuthError` /
|
|
20
|
+
* `isUnknownEpochError` -- the classification contract for the signals
|
|
21
|
+
* below. A consumer matches by `err.name` and never with `instanceof`,
|
|
22
|
+
* because every one of these errors is raised inside a seam the app injects
|
|
23
|
+
* (the port, the caller's `DocCipher`) and that seam can resolve to a second
|
|
24
|
+
* copy of this package. Reading a property off the matched value, such as
|
|
25
|
+
* `status` on an auth error, is the intended shape.
|
|
19
26
|
*
|
|
20
27
|
* The 412 conflict / 404 not-found port signals (`WasSyncConflictError` /
|
|
21
28
|
* `WasSyncNotFoundError`), and the opt-in revoked-access signal
|
|
22
29
|
* (`WasSyncAuthError`, raised only under `mapAuthErrors`), live in the client's
|
|
23
30
|
* typed error hierarchy and are re-exported here for convenience -- as are the
|
|
24
|
-
* two decrypt-routing signals, so a crypto-free sync consumer can
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* two decrypt-routing signals, so a crypto-free sync consumer can classify them
|
|
32
|
+
* without importing the `/edv` entry that throws them: the stale-descriptor
|
|
33
|
+
* signal (`UnknownEpochError`: the envelope's epoch is not on the descriptor at
|
|
34
|
+
* all, so re-read it and rebuild the cipher) and the membership signal
|
|
35
|
+
* (`KeyUnwrapError`: the epoch is on the descriptor but this reader holds no
|
|
36
|
+
* key for it, so a refresh cannot help). `EncryptionError`, the fail-closed
|
|
37
|
+
* umbrella `KeyUnwrapError` falls under, rides along. The classes are exported
|
|
38
|
+
* for construction and for a caller inside one resolved copy; across a package
|
|
39
|
+
* boundary the predicates are the contract
|
|
40
|
+
* (`decisions/0001-cross-package-errors-match-by-name.md`).
|
|
31
41
|
*/
|
|
32
42
|
export { createWasSyncPort, KEY_EPOCH_HEADER, formatEtag, parseEtag } from './port.js';
|
|
33
43
|
export { httpStatus as errorStatus, errorMessage } from '../errors.js';
|
|
@@ -35,6 +45,7 @@ export { contentCid, cidFrom, deriveSpaceId } from './cid.js';
|
|
|
35
45
|
export { isEncryptedEnvelope } from './envelope.js';
|
|
36
46
|
export { createPlaintextDocCipher } from './plaintextCipher.js';
|
|
37
47
|
export { ensureSpace, ensureSpaceAndCollection } from './provisioning.js';
|
|
48
|
+
export { isSyncAuthError, isSyncConflictError, isSyncNotFoundError, isUnknownEpochError } from './predicates.js';
|
|
38
49
|
export { EncryptionError, KeyUnwrapError, UnknownEpochError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
39
|
-
export type { Json, SyncCheckpoint, WireDoc, SyncPage, MasterState, WasSyncPort, DocCipher } from './types.js';
|
|
50
|
+
export type { Json, SyncCheckpoint, WireDoc, SyncPage, MasterState, SyncStatus, WasSyncPort, DocCipher } from './types.js';
|
|
40
51
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/sync/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACV,MAAM,WAAW,CAAA;AAIlB,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,IAAI,EACJ,cAAc,EACd,OAAO,EACP,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,SAAS,EACV,MAAM,YAAY,CAAA"}
|
package/dist/sync/index.js
CHANGED
|
@@ -16,18 +16,28 @@
|
|
|
16
16
|
* - `ensureSpaceAndCollection` / `ensureSpace` -- idempotent Space +
|
|
17
17
|
* Collection provisioning, and its Space half alone (ensure the Space once,
|
|
18
18
|
* then thread the returned description through every collection).
|
|
19
|
+
* - `isSyncConflictError` / `isSyncNotFoundError` / `isSyncAuthError` /
|
|
20
|
+
* `isUnknownEpochError` -- the classification contract for the signals
|
|
21
|
+
* below. A consumer matches by `err.name` and never with `instanceof`,
|
|
22
|
+
* because every one of these errors is raised inside a seam the app injects
|
|
23
|
+
* (the port, the caller's `DocCipher`) and that seam can resolve to a second
|
|
24
|
+
* copy of this package. Reading a property off the matched value, such as
|
|
25
|
+
* `status` on an auth error, is the intended shape.
|
|
19
26
|
*
|
|
20
27
|
* The 412 conflict / 404 not-found port signals (`WasSyncConflictError` /
|
|
21
28
|
* `WasSyncNotFoundError`), and the opt-in revoked-access signal
|
|
22
29
|
* (`WasSyncAuthError`, raised only under `mapAuthErrors`), live in the client's
|
|
23
30
|
* typed error hierarchy and are re-exported here for convenience -- as are the
|
|
24
|
-
* two decrypt-routing signals, so a crypto-free sync consumer can
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* two decrypt-routing signals, so a crypto-free sync consumer can classify them
|
|
32
|
+
* without importing the `/edv` entry that throws them: the stale-descriptor
|
|
33
|
+
* signal (`UnknownEpochError`: the envelope's epoch is not on the descriptor at
|
|
34
|
+
* all, so re-read it and rebuild the cipher) and the membership signal
|
|
35
|
+
* (`KeyUnwrapError`: the epoch is on the descriptor but this reader holds no
|
|
36
|
+
* key for it, so a refresh cannot help). `EncryptionError`, the fail-closed
|
|
37
|
+
* umbrella `KeyUnwrapError` falls under, rides along. The classes are exported
|
|
38
|
+
* for construction and for a caller inside one resolved copy; across a package
|
|
39
|
+
* boundary the predicates are the contract
|
|
40
|
+
* (`decisions/0001-cross-package-errors-match-by-name.md`).
|
|
31
41
|
*/
|
|
32
42
|
export { createWasSyncPort, KEY_EPOCH_HEADER, formatEtag, parseEtag } from './port.js';
|
|
33
43
|
// The sync subpath's names for the client's own error accessors, so a
|
|
@@ -38,5 +48,6 @@ export { contentCid, cidFrom, deriveSpaceId } from './cid.js';
|
|
|
38
48
|
export { isEncryptedEnvelope } from './envelope.js';
|
|
39
49
|
export { createPlaintextDocCipher } from './plaintextCipher.js';
|
|
40
50
|
export { ensureSpace, ensureSpaceAndCollection } from './provisioning.js';
|
|
51
|
+
export { isSyncAuthError, isSyncConflictError, isSyncNotFoundError, isUnknownEpochError } from './predicates.js';
|
|
41
52
|
export { EncryptionError, KeyUnwrapError, UnknownEpochError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
42
53
|
//# sourceMappingURL=index.js.map
|
package/dist/sync/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACV,MAAM,WAAW,CAAA;AAClB,sEAAsE;AACtE,6EAA6E;AAC7E,SAAS;AACT,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,cAAc,CAAA"}
|
package/dist/sync/port.d.ts
CHANGED
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
* plaintext-vs-encrypted fork. `putContent`/`deleteContent` return the server-
|
|
25
25
|
* acked `version` (parsed from the write's `ETag`, re-read only if the backend
|
|
26
26
|
* sent none), so a caller can record acked revisions immediately.
|
|
27
|
+
*
|
|
28
|
+
* Bypassing the codec is not bypassing the error mapper. Every failure caught
|
|
29
|
+
* here goes through the client's own `mapError` first, so the port's signals
|
|
30
|
+
* carry the server's `problem+json` fields and a `cause`, and a status the
|
|
31
|
+
* port has no signal for still leaves this subpath as a typed `WasError`.
|
|
27
32
|
*/
|
|
28
33
|
import type { WasClient } from '../WasClient.js';
|
|
29
34
|
import { KEY_EPOCH_HEADER } from '../internal/conditional.js';
|
package/dist/sync/port.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/sync/port.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/sync/port.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EACL,gBAAgB,EAGjB,MAAM,4BAA4B,CAAA;AAWnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAAqB,WAAW,EAAE,MAAM,YAAY,CAAA;AAEhE;;;;;GAKG;AACH,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAyG3B;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAM7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAqB,EACtB,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,GAAG,WAAW,CAuMd"}
|
package/dist/sync/port.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KEY_EPOCH_HEADER, readEtag, writeHeaders } from '../internal/conditional.js';
|
|
2
2
|
import { resourceMeta, resourcePath } from '../internal/paths.js';
|
|
3
|
-
import {
|
|
3
|
+
import { mapError, NotFoundError, PreconditionFailedError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
4
4
|
/**
|
|
5
5
|
* The request header the server reads a content write's key-epoch id from,
|
|
6
6
|
* stamping it onto the Resource's metadata. Defined next to the header
|
|
@@ -28,34 +28,75 @@ function isAuthStatus(status) {
|
|
|
28
28
|
return status === 401 || status === 403 || status === 404;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
31
|
+
* Carries a mapped error's `problem+json` fields onto the port signal built
|
|
32
|
+
* from it. The `cause` is the raw transport error `mapError` recorded, or the
|
|
33
|
+
* mapped error itself when there was none. A port signal is a narrowing of the
|
|
34
|
+
* classification `mapError` already made, so it must not lose what the server
|
|
35
|
+
* said.
|
|
36
|
+
*
|
|
37
|
+
* @param mapped {WasError} the classified error
|
|
38
|
+
* @returns {WasErrorOptions}
|
|
39
|
+
*/
|
|
40
|
+
function signalOptions(mapped) {
|
|
41
|
+
return {
|
|
42
|
+
status: mapped.status,
|
|
43
|
+
type: mapped.type,
|
|
44
|
+
title: mapped.title,
|
|
45
|
+
details: mapped.details,
|
|
46
|
+
requestUrl: mapped.requestUrl,
|
|
47
|
+
cause: mapped.cause ?? mapped
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether a mapped error is already one of the port's own signals, which a
|
|
52
|
+
* nested port call (the acked-version re-read inside a write) can raise
|
|
53
|
+
* through a write's catch block. Re-wrapping one would drop its cause for no
|
|
54
|
+
* gain.
|
|
55
|
+
*
|
|
56
|
+
* @param mapped {WasError}
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
function isPortSignal(mapped) {
|
|
60
|
+
return (mapped instanceof WasSyncConflictError ||
|
|
61
|
+
mapped instanceof WasSyncNotFoundError ||
|
|
62
|
+
mapped instanceof WasSyncAuthError);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Maps a caught write error onto the port's typed signals, rethrowing anything
|
|
66
|
+
* else as the classified {@link WasError} the client's own `mapError` builds
|
|
67
|
+
* (so a status outside this list -- a `500`, a `507` quota-exceeded -- still
|
|
68
|
+
* leaves the sync subpath typed and carrying the server's problem details).
|
|
69
|
+
* A rejected precondition becomes a {@link WasSyncConflictError} for every
|
|
70
|
+
* write. `notFound` opts in to the not-found mapping, which only
|
|
71
|
+
* `deleteContent` performs (an already-gone target is a settled outcome for a
|
|
72
|
+
* delete, but a hard error for a content or metadata write). `authErrors` is
|
|
73
|
+
* the port's `mapAuthErrors` option: it maps `401` / `403` / the masked `404`
|
|
74
|
+
* to a {@link WasSyncAuthError}. `notFound` is checked first, so a port that
|
|
75
|
+
* asked for both still gets the delete-specific signal.
|
|
39
76
|
*
|
|
40
77
|
* @param err {unknown} the caught error
|
|
41
78
|
* @param [options] {object}
|
|
42
|
-
* @param [options.notFound] {boolean} map
|
|
79
|
+
* @param [options.notFound] {boolean} map a not-found to
|
|
80
|
+
* {@link WasSyncNotFoundError}
|
|
43
81
|
* @param [options.authErrors] {boolean} map `401`/`403`/`404` to
|
|
44
82
|
* {@link WasSyncAuthError}
|
|
45
83
|
* @returns {never} always throws
|
|
46
84
|
*/
|
|
47
85
|
function mapWriteError(err, { notFound = false, authErrors = false } = {}) {
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
throw
|
|
86
|
+
const mapped = mapError(err);
|
|
87
|
+
if (isPortSignal(mapped)) {
|
|
88
|
+
throw mapped;
|
|
89
|
+
}
|
|
90
|
+
if (notFound && mapped instanceof NotFoundError) {
|
|
91
|
+
throw new WasSyncNotFoundError(mapped.message, signalOptions(mapped));
|
|
51
92
|
}
|
|
52
|
-
if (
|
|
53
|
-
throw new WasSyncConflictError();
|
|
93
|
+
if (mapped instanceof PreconditionFailedError) {
|
|
94
|
+
throw new WasSyncConflictError(mapped.message, signalOptions(mapped));
|
|
54
95
|
}
|
|
55
|
-
if (authErrors && isAuthStatus(status)) {
|
|
56
|
-
throw new WasSyncAuthError(status);
|
|
96
|
+
if (authErrors && isAuthStatus(mapped.status)) {
|
|
97
|
+
throw new WasSyncAuthError(mapped.status, signalOptions(mapped));
|
|
57
98
|
}
|
|
58
|
-
throw
|
|
99
|
+
throw mapped;
|
|
59
100
|
}
|
|
60
101
|
/**
|
|
61
102
|
* Formats a numeric content `version` as the quoted strong `ETag` an
|
|
@@ -136,17 +177,17 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
136
177
|
});
|
|
137
178
|
}
|
|
138
179
|
catch (err) {
|
|
139
|
-
const
|
|
180
|
+
const mapped = mapError(err);
|
|
140
181
|
// A read's `404` stays "absent or tombstoned" even under
|
|
141
182
|
// `mapAuthErrors`: it is a modeled outcome here, and the callers read it
|
|
142
183
|
// as deletion-wins.
|
|
143
|
-
if (
|
|
184
|
+
if (mapped instanceof NotFoundError) {
|
|
144
185
|
return null;
|
|
145
186
|
}
|
|
146
|
-
if (mapAuthErrors && isAuthStatus(status)) {
|
|
147
|
-
throw new WasSyncAuthError(status);
|
|
187
|
+
if (mapAuthErrors && isAuthStatus(mapped.status)) {
|
|
188
|
+
throw new WasSyncAuthError(mapped.status, signalOptions(mapped));
|
|
148
189
|
}
|
|
149
|
-
throw
|
|
190
|
+
throw mapped;
|
|
150
191
|
}
|
|
151
192
|
return {
|
|
152
193
|
version: parseEtag(readEtag(response)) ?? 0,
|
|
@@ -171,11 +212,11 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
171
212
|
// The pull path is where revoked access surfaces reliably: unlike a
|
|
172
213
|
// read or a delete, a `404` on the collection's own query endpoint has
|
|
173
214
|
// no benign reading once the collection is known to exist.
|
|
174
|
-
const
|
|
175
|
-
if (mapAuthErrors && isAuthStatus(status)) {
|
|
176
|
-
throw new WasSyncAuthError(status);
|
|
215
|
+
const mapped = mapError(err);
|
|
216
|
+
if (mapAuthErrors && isAuthStatus(mapped.status)) {
|
|
217
|
+
throw new WasSyncAuthError(mapped.status, signalOptions(mapped));
|
|
177
218
|
}
|
|
178
|
-
throw
|
|
219
|
+
throw mapped;
|
|
179
220
|
}
|
|
180
221
|
},
|
|
181
222
|
async putContent({ id, data, ifMatch, ifNoneMatch, epoch }) {
|
|
@@ -213,10 +254,11 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
213
254
|
// holds and the batch must not be retried forever. A masked
|
|
214
255
|
// authorization `404` is swallowed with it -- indistinguishable by
|
|
215
256
|
// design -- but revoked access still surfaces on the next `query`.
|
|
216
|
-
|
|
257
|
+
const mapped = mapError(err);
|
|
258
|
+
if (mapAuthErrors && mapped instanceof NotFoundError) {
|
|
217
259
|
return undefined;
|
|
218
260
|
}
|
|
219
|
-
mapWriteError(
|
|
261
|
+
mapWriteError(mapped, {
|
|
220
262
|
notFound: !mapAuthErrors,
|
|
221
263
|
authErrors: mapAuthErrors
|
|
222
264
|
});
|
|
@@ -260,17 +302,17 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
260
302
|
// yet 404s here; only a hard error propagates.
|
|
261
303
|
const meta = await metaRead;
|
|
262
304
|
if (!meta.ok) {
|
|
263
|
-
const
|
|
305
|
+
const mapped = mapError(meta.err);
|
|
264
306
|
// A `/meta` `404` is routine (the resource has no metadata document
|
|
265
307
|
// yet), so it stays benign under `mapAuthErrors` -- only `401`/`403`
|
|
266
308
|
// map there.
|
|
267
|
-
if (
|
|
309
|
+
if (mapped instanceof NotFoundError) {
|
|
268
310
|
return master;
|
|
269
311
|
}
|
|
270
|
-
if (mapAuthErrors && isAuthStatus(status)) {
|
|
271
|
-
throw new WasSyncAuthError(status);
|
|
312
|
+
if (mapAuthErrors && isAuthStatus(mapped.status)) {
|
|
313
|
+
throw new WasSyncAuthError(mapped.status, signalOptions(mapped));
|
|
272
314
|
}
|
|
273
|
-
throw
|
|
315
|
+
throw mapped;
|
|
274
316
|
}
|
|
275
317
|
const metaBody = meta.response.data;
|
|
276
318
|
if (metaBody?.updatedAt) {
|
package/dist/sync/port.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/sync/port.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/sync/port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `err.name` predicates that classify the errors a replication path can
|
|
6
|
+
* meet: the port's two wire signals (`WasSyncConflictError` /
|
|
7
|
+
* `WasSyncNotFoundError`), its opt-in revoked-access signal
|
|
8
|
+
* (`WasSyncAuthError`), and the cipher's stale-descriptor signal
|
|
9
|
+
* (`UnknownEpochError`).
|
|
10
|
+
*
|
|
11
|
+
* They live beside the classes that assign the names they match (`errors.ts`)
|
|
12
|
+
* because every one of those errors is raised inside a seam the consuming app
|
|
13
|
+
* injects -- the port for the wire signals, the caller's `DocCipher` for the
|
|
14
|
+
* unknown epoch -- and that seam can resolve to a SECOND copy of this package
|
|
15
|
+
* (a `link:` dev setup, a dedupe miss through a dependency tree). So the match
|
|
16
|
+
* is on the `name` string alone and never `instanceof`, which cannot see across
|
|
17
|
+
* that boundary. The cost of a miss is silent and expensive: every push 412
|
|
18
|
+
* becomes a fatal cycle error, and a create-loss re-mint rethrows instead of
|
|
19
|
+
* re-minting. Each class assigns its `name` explicitly, which is what makes the
|
|
20
|
+
* string a contract (`decisions/0001-cross-package-errors-match-by-name.md`).
|
|
21
|
+
*
|
|
22
|
+
* Each returns a plain boolean rather than a type guard: the guard would narrow
|
|
23
|
+
* to this copy's class, the very identity the rule declines to depend on. A
|
|
24
|
+
* caller reading a property after the match (`err.status` on an auth error)
|
|
25
|
+
* reads it off the value itself.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Whether an error is the replication port's rejected-conditional-write signal
|
|
29
|
+
* (`WasSyncConflictError`, HTTP 412): a lost-update `If-Match` mismatch, or a
|
|
30
|
+
* create-if-absent whose target already exists. The push loop's one settle-and-
|
|
31
|
+
* reconcile branch; everything else propagates to the engine's backoff.
|
|
32
|
+
*
|
|
33
|
+
* @param err {unknown}
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export declare function isSyncConflictError(err: unknown): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether an error is the replication port's absent-target signal
|
|
39
|
+
* (`WasSyncNotFoundError`, HTTP 404). On a delete that is a settled outcome
|
|
40
|
+
* -- already gone, or the write never reached the server -- not a conflict.
|
|
41
|
+
*
|
|
42
|
+
* @param err {unknown}
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
export declare function isSyncNotFoundError(err: unknown): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether an error is the replication port's refused-authorization signal
|
|
48
|
+
* (`WasSyncAuthError`), raised only by a port built with `mapAuthErrors`. It
|
|
49
|
+
* covers `401`, `403`, and the `404` a WAS server returns when it masks an
|
|
50
|
+
* authorization failure as "not found", so a caller that needs the three apart
|
|
51
|
+
* reads `status` off the matched value as a plain property.
|
|
52
|
+
*
|
|
53
|
+
* @param err {unknown}
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
export declare function isSyncAuthError(err: unknown): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Whether an error is the cipher's unknown-epoch signal (`UnknownEpochError`):
|
|
59
|
+
* an envelope naming recipient key ids whose epoch the descriptor this reader
|
|
60
|
+
* holds does not list at all. Distinct from a key the reader simply does not
|
|
61
|
+
* have (`KeyUnwrapError`), which re-reading the descriptor cannot fix.
|
|
62
|
+
*
|
|
63
|
+
* @param err {unknown}
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export declare function isUnknownEpochError(err: unknown): boolean;
|
|
67
|
+
//# sourceMappingURL=predicates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predicates.d.ts","sourceRoot":"","sources":["../../src/sync/predicates.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAaH;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAErD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEzD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `err.name` predicates that classify the errors a replication path can
|
|
6
|
+
* meet: the port's two wire signals (`WasSyncConflictError` /
|
|
7
|
+
* `WasSyncNotFoundError`), its opt-in revoked-access signal
|
|
8
|
+
* (`WasSyncAuthError`), and the cipher's stale-descriptor signal
|
|
9
|
+
* (`UnknownEpochError`).
|
|
10
|
+
*
|
|
11
|
+
* They live beside the classes that assign the names they match (`errors.ts`)
|
|
12
|
+
* because every one of those errors is raised inside a seam the consuming app
|
|
13
|
+
* injects -- the port for the wire signals, the caller's `DocCipher` for the
|
|
14
|
+
* unknown epoch -- and that seam can resolve to a SECOND copy of this package
|
|
15
|
+
* (a `link:` dev setup, a dedupe miss through a dependency tree). So the match
|
|
16
|
+
* is on the `name` string alone and never `instanceof`, which cannot see across
|
|
17
|
+
* that boundary. The cost of a miss is silent and expensive: every push 412
|
|
18
|
+
* becomes a fatal cycle error, and a create-loss re-mint rethrows instead of
|
|
19
|
+
* re-minting. Each class assigns its `name` explicitly, which is what makes the
|
|
20
|
+
* string a contract (`decisions/0001-cross-package-errors-match-by-name.md`).
|
|
21
|
+
*
|
|
22
|
+
* Each returns a plain boolean rather than a type guard: the guard would narrow
|
|
23
|
+
* to this copy's class, the very identity the rule declines to depend on. A
|
|
24
|
+
* caller reading a property after the match (`err.status` on an auth error)
|
|
25
|
+
* reads it off the value itself.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Reads the `name` off an unknown caught value, without raising on a nullish
|
|
29
|
+
* or non-object rejection.
|
|
30
|
+
*
|
|
31
|
+
* @param err {unknown} the caught error
|
|
32
|
+
* @returns {unknown}
|
|
33
|
+
*/
|
|
34
|
+
function nameOf(err) {
|
|
35
|
+
return err?.name;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether an error is the replication port's rejected-conditional-write signal
|
|
39
|
+
* (`WasSyncConflictError`, HTTP 412): a lost-update `If-Match` mismatch, or a
|
|
40
|
+
* create-if-absent whose target already exists. The push loop's one settle-and-
|
|
41
|
+
* reconcile branch; everything else propagates to the engine's backoff.
|
|
42
|
+
*
|
|
43
|
+
* @param err {unknown}
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export function isSyncConflictError(err) {
|
|
47
|
+
return nameOf(err) === 'WasSyncConflictError';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether an error is the replication port's absent-target signal
|
|
51
|
+
* (`WasSyncNotFoundError`, HTTP 404). On a delete that is a settled outcome
|
|
52
|
+
* -- already gone, or the write never reached the server -- not a conflict.
|
|
53
|
+
*
|
|
54
|
+
* @param err {unknown}
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function isSyncNotFoundError(err) {
|
|
58
|
+
return nameOf(err) === 'WasSyncNotFoundError';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether an error is the replication port's refused-authorization signal
|
|
62
|
+
* (`WasSyncAuthError`), raised only by a port built with `mapAuthErrors`. It
|
|
63
|
+
* covers `401`, `403`, and the `404` a WAS server returns when it masks an
|
|
64
|
+
* authorization failure as "not found", so a caller that needs the three apart
|
|
65
|
+
* reads `status` off the matched value as a plain property.
|
|
66
|
+
*
|
|
67
|
+
* @param err {unknown}
|
|
68
|
+
* @returns {boolean}
|
|
69
|
+
*/
|
|
70
|
+
export function isSyncAuthError(err) {
|
|
71
|
+
return nameOf(err) === 'WasSyncAuthError';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Whether an error is the cipher's unknown-epoch signal (`UnknownEpochError`):
|
|
75
|
+
* an envelope naming recipient key ids whose epoch the descriptor this reader
|
|
76
|
+
* holds does not list at all. Distinct from a key the reader simply does not
|
|
77
|
+
* have (`KeyUnwrapError`), which re-reading the descriptor cannot fix.
|
|
78
|
+
*
|
|
79
|
+
* @param err {unknown}
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
export function isUnknownEpochError(err) {
|
|
83
|
+
return nameOf(err) === 'UnknownEpochError';
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=predicates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predicates.js","sourceRoot":"","sources":["../../src/sync/predicates.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAQ,GAAiC,EAAE,IAAI,CAAA;AACjD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,sBAAsB,CAAA;AAC/C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,sBAAsB,CAAA;AAC/C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,kBAAkB,CAAA;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,mBAAmB,CAAA;AAC5C,CAAC"}
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* here under replication-facing names so a sync consumer imports one module.
|
|
10
10
|
* `WasSyncPort` and `MasterState` are the injectable access seam the change
|
|
11
11
|
* engine depends on, and `DocCipher` is the encrypt/decrypt seam a per-
|
|
12
|
-
* collection cipher implements.
|
|
12
|
+
* collection cipher implements. `SyncStatus` is the closed status vocabulary
|
|
13
|
+
* every replication driver reports one feed's state through.
|
|
13
14
|
*/
|
|
14
15
|
import type { ChangeDocument, ChangesCheckpoint, ChangesPage } from '@interop/storage-core';
|
|
15
16
|
import type { Json } from '../types.js';
|
|
@@ -169,4 +170,11 @@ export interface DocCipher {
|
|
|
169
170
|
envelope: Json;
|
|
170
171
|
}): Promise<Json>;
|
|
171
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Per-feed replication status, surfaced to the app's state layer. The
|
|
175
|
+
* vocabulary is closed: a consumer may render a status through a lookup keyed
|
|
176
|
+
* on the string, so a widened value would fail at runtime rather than at
|
|
177
|
+
* compile time.
|
|
178
|
+
*/
|
|
179
|
+
export type SyncStatus = 'idle' | 'syncing' | 'synced' | 'error';
|
|
172
180
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/sync/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interop/was-client",
|
|
3
3
|
"description": "A developer-friendly client for Wallet Attached Storage (WAS) servers.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.49.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "pnpm run clear && tsc",
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
"sideEffects": false,
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@interop/data-integrity-core": "^8.7.1",
|
|
65
|
-
"@interop/ed25519-signature": "^7.2.
|
|
66
|
-
"@interop/edv-client": "^17.8.
|
|
67
|
-
"@interop/ezcap": "^7.4.
|
|
65
|
+
"@interop/ed25519-signature": "^7.2.1",
|
|
66
|
+
"@interop/edv-client": "^17.8.2",
|
|
67
|
+
"@interop/ezcap": "^7.4.6",
|
|
68
68
|
"@interop/http-client": "^1.0.5",
|
|
69
|
-
"@interop/minimal-cipher": "^7.
|
|
69
|
+
"@interop/minimal-cipher": "^7.10.0",
|
|
70
70
|
"@interop/storage-core": "^0.10.0",
|
|
71
71
|
"@interop/vh-resource-log": "^0.4.1",
|
|
72
|
-
"@interop/x25519-key-agreement-key": "^5.
|
|
72
|
+
"@interop/x25519-key-agreement-key": "^5.3.0",
|
|
73
73
|
"@noble/hashes": "^2.3.0",
|
|
74
74
|
"@scure/base": "^2.3.0",
|
|
75
75
|
"json-canonicalize": "3.0.0"
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@eslint/js": "^10.0.1",
|
|
79
79
|
"@interop/data-integrity-proof": "3.4.5",
|
|
80
|
-
"@interop/ed25519-verification-key": "^8.0
|
|
80
|
+
"@interop/ed25519-verification-key": "^8.1.0",
|
|
81
81
|
"@interop/jsonld-signatures": "11.8.5",
|
|
82
82
|
"@interop/zcap": "11.1.0",
|
|
83
83
|
"@playwright/test": "^1.62.1",
|