@interop/wallet-core 0.1.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.
Files changed (55) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +83 -0
  3. package/dist/index.d.ts +15 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +15 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/space/activity.d.ts +175 -0
  8. package/dist/space/activity.d.ts.map +1 -0
  9. package/dist/space/activity.js +209 -0
  10. package/dist/space/activity.js.map +1 -0
  11. package/dist/space/collections.d.ts +65 -0
  12. package/dist/space/collections.d.ts.map +1 -0
  13. package/dist/space/collections.js +65 -0
  14. package/dist/space/collections.js.map +1 -0
  15. package/dist/space/errors.d.ts +14 -0
  16. package/dist/space/errors.d.ts.map +1 -0
  17. package/dist/space/errors.js +18 -0
  18. package/dist/space/errors.js.map +1 -0
  19. package/dist/space/index.d.ts +26 -0
  20. package/dist/space/index.d.ts.map +1 -0
  21. package/dist/space/index.js +23 -0
  22. package/dist/space/index.js.map +1 -0
  23. package/dist/space/publicLink.d.ts +17 -0
  24. package/dist/space/publicLink.d.ts.map +1 -0
  25. package/dist/space/publicLink.js +27 -0
  26. package/dist/space/publicLink.js.map +1 -0
  27. package/dist/space/wasLink.d.ts +37 -0
  28. package/dist/space/wasLink.d.ts.map +1 -0
  29. package/dist/space/wasLink.js +123 -0
  30. package/dist/space/wasLink.js.map +1 -0
  31. package/dist/sync/collections.d.ts +61 -0
  32. package/dist/sync/collections.d.ts.map +1 -0
  33. package/dist/sync/collections.js +2 -0
  34. package/dist/sync/collections.js.map +1 -0
  35. package/dist/sync/engine.d.ts +112 -0
  36. package/dist/sync/engine.d.ts.map +1 -0
  37. package/dist/sync/engine.js +188 -0
  38. package/dist/sync/engine.js.map +1 -0
  39. package/dist/sync/index.d.ts +33 -0
  40. package/dist/sync/index.d.ts.map +1 -0
  41. package/dist/sync/index.js +29 -0
  42. package/dist/sync/index.js.map +1 -0
  43. package/dist/sync/pull.d.ts +68 -0
  44. package/dist/sync/pull.d.ts.map +1 -0
  45. package/dist/sync/pull.js +92 -0
  46. package/dist/sync/pull.js.map +1 -0
  47. package/dist/sync/push.d.ts +55 -0
  48. package/dist/sync/push.d.ts.map +1 -0
  49. package/dist/sync/push.js +191 -0
  50. package/dist/sync/push.js.map +1 -0
  51. package/dist/sync/types.d.ts +123 -0
  52. package/dist/sync/types.d.ts.map +1 -0
  53. package/dist/sync/types.js +25 -0
  54. package/dist/sync/types.js.map +1 -0
  55. package/package.json +94 -0
@@ -0,0 +1,65 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The shared wallet Space layout: the collection ids and descriptive specs both
6
+ * wallet replicas provision, so a credential written by one is found and read by
7
+ * the other. Both replicas MUST agree on every field here -- `collectionId`
8
+ * decides where a document lands on the server, `idDerivation` and `mutable`
9
+ * decide whether it is overwritten in place or only appended, and `encryption` /
10
+ * `isPublic` decide how it is stored and who can read it. A disagreement splits
11
+ * the feed into separate or incompatibly-shaped collections that never converge.
12
+ *
13
+ * The contacts collections (`contacts`, `contacts-history`) are NOT declared
14
+ * here: their ids and specs live in `@interop/social-core`
15
+ * (`CONTACTS_COLLECTION_SPEC` / `CONTACTS_HISTORY_COLLECTION_SPEC`), which apps
16
+ * import directly. The field vocabulary below mirrors that spec.
17
+ */
18
+ /** The immutable, content-addressed, EDV-encrypted credential replica. */
19
+ export const PRIVATE_CREDENTIALS_COLLECTION = 'private-credentials';
20
+ /** The plaintext, world-readable copies of publicly shared credentials. */
21
+ export const PUBLIC_CREDENTIALS_COLLECTION = 'public-credentials';
22
+ /** The append-only, EDV-encrypted wallet activity log. */
23
+ export const WALLET_ACTIVITY_COLLECTION = 'wallet-activity';
24
+ /**
25
+ * The immutable credential replica: each credential stored as an EDV envelope,
26
+ * addressed by the envelope's content hash, never overwritten, never public.
27
+ */
28
+ export const PRIVATE_CREDENTIALS_COLLECTION_SPEC = {
29
+ collectionId: PRIVATE_CREDENTIALS_COLLECTION,
30
+ idDerivation: 'content',
31
+ mutable: false,
32
+ encryption: 'edv',
33
+ isPublic: false
34
+ };
35
+ /**
36
+ * The plaintext, world-readable copies of shared credentials, keyed by the
37
+ * credential's content cid (the same id every replica mints, so a share
38
+ * converges), granted collection-level world read on the server.
39
+ */
40
+ export const PUBLIC_CREDENTIALS_COLLECTION_SPEC = {
41
+ collectionId: PUBLIC_CREDENTIALS_COLLECTION,
42
+ idDerivation: 'content',
43
+ mutable: false,
44
+ encryption: 'plaintext',
45
+ isPublic: true
46
+ };
47
+ /**
48
+ * The append-only activity log: each entry stored as an EDV envelope, addressed
49
+ * by content hash, never overwritten, never public. Shared with the web wallet's
50
+ * `wallet-activity` collection, so each replica reads the other's entries.
51
+ */
52
+ export const WALLET_ACTIVITY_COLLECTION_SPEC = {
53
+ collectionId: WALLET_ACTIVITY_COLLECTION,
54
+ idDerivation: 'content',
55
+ mutable: false,
56
+ encryption: 'edv',
57
+ isPublic: false
58
+ };
59
+ /** The wallet Space's own (non-contacts) collection specs, in provision order. */
60
+ export const WALLET_SPACE_COLLECTION_SPECS = [
61
+ PRIVATE_CREDENTIALS_COLLECTION_SPEC,
62
+ PUBLIC_CREDENTIALS_COLLECTION_SPEC,
63
+ WALLET_ACTIVITY_COLLECTION_SPEC
64
+ ];
65
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.js","sourceRoot":"","sources":["../../src/space/collections.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;GAaG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,8BAA8B,GAAG,qBAAqB,CAAA;AACnE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,6BAA6B,GAAG,oBAAoB,CAAA;AACjE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,0BAA0B,GAAG,iBAAiB,CAAA;AAyB3D;;;GAGG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAwB;IACtE,YAAY,EAAE,8BAA8B;IAC5C,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,KAAK;CAChB,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAwB;IACrE,YAAY,EAAE,6BAA6B;IAC3C,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,WAAW;IACvB,QAAQ,EAAE,IAAI;CACf,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAwB;IAClE,YAAY,EAAE,0BAA0B;IACxC,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,KAAK;CAChB,CAAA;AAED,kFAAkF;AAClF,MAAM,CAAC,MAAM,6BAA6B,GAA0B;IAClE,mCAAmC;IACnC,kCAAkC;IAClC,+BAA+B;CAChC,CAAA"}
@@ -0,0 +1,14 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * A user-facing error whose `message` is safe to show verbatim. Carries the name
6
+ * `'HumanReadableError'` so an app that classifies errors by `name` (rather than
7
+ * by `instanceof`, which fails across module realms) treats a wallet-core error
8
+ * the same as its own. Thrown by `parseWasLinkPayload` for every malformed /
9
+ * wrong-version / non-link input.
10
+ */
11
+ export declare class HumanReadableError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/space/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAK5B"}
@@ -0,0 +1,18 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * A user-facing error whose `message` is safe to show verbatim. Carries the name
6
+ * `'HumanReadableError'` so an app that classifies errors by `name` (rather than
7
+ * by `instanceof`, which fails across module realms) treats a wallet-core error
8
+ * the same as its own. Thrown by `parseWasLinkPayload` for every malformed /
9
+ * wrong-version / non-link input.
10
+ */
11
+ export class HumanReadableError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.message = message;
15
+ this.name = 'HumanReadableError';
16
+ }
17
+ }
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/space/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAA;IAClC,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The `@interop/wallet-core/space` subpath: the wallet Space layout contract two
6
+ * WAS-enabled wallet apps share.
7
+ *
8
+ * - The shared collection ids and descriptive specs (`private-credentials`,
9
+ * `public-credentials`, `wallet-activity`). The contacts collections stay in
10
+ * `@interop/social-core`.
11
+ * - The `wallet-activity` wire shape (`WalletActivity`) and the pure
12
+ * `addHistory*` payload builders.
13
+ * - `publicCredentialUrl`, the world-readable shared-credential URL both
14
+ * replicas derive identically.
15
+ * - The `was-link` QR hand-off contract (`buildWasLinkPayload` /
16
+ * `parseWasLinkPayload` / `encodeWasLinkSecret`).
17
+ */
18
+ export { PRIVATE_CREDENTIALS_COLLECTION, PUBLIC_CREDENTIALS_COLLECTION, WALLET_ACTIVITY_COLLECTION, PRIVATE_CREDENTIALS_COLLECTION_SPEC, PUBLIC_CREDENTIALS_COLLECTION_SPEC, WALLET_ACTIVITY_COLLECTION_SPEC, WALLET_SPACE_COLLECTION_SPECS } from './collections.js';
19
+ export type { SpaceCollectionSpec } from './collections.js';
20
+ export { ACTIVITY_TYPE, addHistoryNewAccount, addHistorySpaceCreated, addHistoryCredentialCreated, addHistoryCredentialDeleted, addHistoryCredentialShared, addHistoryCredentialUnshared, addHistoryLogin, addHistoryAppRevoke } from './activity.js';
21
+ export type { WalletActivity, ActivityGrant } from './activity.js';
22
+ export { publicCredentialUrl } from './publicLink.js';
23
+ export { encodeWasLinkSecret, buildWasLinkPayload, parseWasLinkPayload } from './wasLink.js';
24
+ export type { WasLinkPayload } from './wasLink.js';
25
+ export { HumanReadableError } from './errors.js';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/space/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC7B,0BAA0B,EAC1B,mCAAmC,EACnC,kCAAkC,EAClC,+BAA+B,EAC/B,6BAA6B,EAC9B,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3D,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,4BAA4B,EAC5B,eAAe,EACf,mBAAmB,EACpB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The `@interop/wallet-core/space` subpath: the wallet Space layout contract two
6
+ * WAS-enabled wallet apps share.
7
+ *
8
+ * - The shared collection ids and descriptive specs (`private-credentials`,
9
+ * `public-credentials`, `wallet-activity`). The contacts collections stay in
10
+ * `@interop/social-core`.
11
+ * - The `wallet-activity` wire shape (`WalletActivity`) and the pure
12
+ * `addHistory*` payload builders.
13
+ * - `publicCredentialUrl`, the world-readable shared-credential URL both
14
+ * replicas derive identically.
15
+ * - The `was-link` QR hand-off contract (`buildWasLinkPayload` /
16
+ * `parseWasLinkPayload` / `encodeWasLinkSecret`).
17
+ */
18
+ export { PRIVATE_CREDENTIALS_COLLECTION, PUBLIC_CREDENTIALS_COLLECTION, WALLET_ACTIVITY_COLLECTION, PRIVATE_CREDENTIALS_COLLECTION_SPEC, PUBLIC_CREDENTIALS_COLLECTION_SPEC, WALLET_ACTIVITY_COLLECTION_SPEC, WALLET_SPACE_COLLECTION_SPECS } from './collections.js';
19
+ export { ACTIVITY_TYPE, addHistoryNewAccount, addHistorySpaceCreated, addHistoryCredentialCreated, addHistoryCredentialDeleted, addHistoryCredentialShared, addHistoryCredentialUnshared, addHistoryLogin, addHistoryAppRevoke } from './activity.js';
20
+ export { publicCredentialUrl } from './publicLink.js';
21
+ export { encodeWasLinkSecret, buildWasLinkPayload, parseWasLinkPayload } from './wasLink.js';
22
+ export { HumanReadableError } from './errors.js';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/space/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC7B,0BAA0B,EAC1B,mCAAmC,EACnC,kCAAkC,EAClC,+BAA+B,EAC/B,6BAA6B,EAC9B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,4BAA4B,EAC5B,eAAe,EACf,mBAAmB,EACpB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The absolute, world-readable URL of a credential's shared copy:
3
+ * `{serverUrl}/space/{spaceId}/public-credentials/{cid}`. Takes the server URL,
4
+ * Space id, and credential cid as arguments, with no dependency on app config.
5
+ *
6
+ * @param options {object}
7
+ * @param options.serverUrl {string} the WAS storage server base URL
8
+ * @param options.spaceId {string} the profile's WAS Space id
9
+ * @param options.cid {string} the credential's content cid
10
+ * @returns {string}
11
+ */
12
+ export declare function publicCredentialUrl({ serverUrl, spaceId, cid }: {
13
+ serverUrl: string;
14
+ spaceId: string;
15
+ cid: string;
16
+ }): string;
17
+ //# sourceMappingURL=publicLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicLink.d.ts","sourceRoot":"","sources":["../../src/space/publicLink.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,OAAO,EACP,GAAG,EACJ,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,MAAM,CAKT"}
@@ -0,0 +1,27 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Public-credential URL derivation. Sharing a credential writes a plaintext copy
6
+ * into the profile's `public-credentials` collection, keyed by the credential's
7
+ * content cid; once replication mirrors it to the profile's WAS Space it
8
+ * resolves at the URL below. Both wallet replicas build the identical URL for a
9
+ * given `(serverUrl, spaceId, cid)`, so a link handed out by one resolves the
10
+ * copy replicated by the other.
11
+ */
12
+ import { PUBLIC_CREDENTIALS_COLLECTION } from './collections.js';
13
+ /**
14
+ * The absolute, world-readable URL of a credential's shared copy:
15
+ * `{serverUrl}/space/{spaceId}/public-credentials/{cid}`. Takes the server URL,
16
+ * Space id, and credential cid as arguments, with no dependency on app config.
17
+ *
18
+ * @param options {object}
19
+ * @param options.serverUrl {string} the WAS storage server base URL
20
+ * @param options.spaceId {string} the profile's WAS Space id
21
+ * @param options.cid {string} the credential's content cid
22
+ * @returns {string}
23
+ */
24
+ export function publicCredentialUrl({ serverUrl, spaceId, cid }) {
25
+ return new URL(`/space/${spaceId}/${PUBLIC_CREDENTIALS_COLLECTION}/${cid}`, serverUrl).toString();
26
+ }
27
+ //# sourceMappingURL=publicLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicLink.js","sourceRoot":"","sources":["../../src/space/publicLink.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;GAOG;AACH,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAA;AAEhE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,SAAS,EACT,OAAO,EACP,GAAG,EAKJ;IACC,OAAO,IAAI,GAAG,CACZ,UAAU,OAAO,IAAI,6BAA6B,IAAI,GAAG,EAAE,EAC3D,SAAS,CACV,CAAC,QAAQ,EAAE,CAAA;AACd,CAAC"}
@@ -0,0 +1,37 @@
1
+ /** The decoded link: the WAS server plus the controller secret AS A STRING. */
2
+ export interface WasLinkPayload {
3
+ serverUrl: string;
4
+ /** The controller secret (already decoded back to the passphrase string). */
5
+ secret: string;
6
+ }
7
+ /**
8
+ * Encodes a passphrase into the payload's `secret` field: `base64url(utf8(...))`,
9
+ * no padding.
10
+ *
11
+ * @param passphrase {string}
12
+ * @returns {string}
13
+ */
14
+ export declare function encodeWasLinkSecret(passphrase: string): string;
15
+ /**
16
+ * Builds the full `was-link` payload string a QR encodes.
17
+ *
18
+ * @param options {object}
19
+ * @param options.serverUrl {string}
20
+ * @param options.passphrase {string}
21
+ * @returns {string}
22
+ */
23
+ export declare function buildWasLinkPayload({ serverUrl, passphrase }: {
24
+ serverUrl: string;
25
+ passphrase: string;
26
+ }): string;
27
+ /**
28
+ * Parses and validates a scanned payload, returning the server URL and the
29
+ * decoded string secret. Throws {@link HumanReadableError} for anything that is
30
+ * not a valid current-version `was-link` payload (so the scanner shows a clean
31
+ * message rather than mis-handling an unrelated QR).
32
+ *
33
+ * @param raw {string} the raw scanned code value
34
+ * @returns {WasLinkPayload}
35
+ */
36
+ export declare function parseWasLinkPayload(raw: string): WasLinkPayload;
37
+ //# sourceMappingURL=wasLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasLink.d.ts","sourceRoot":"","sources":["../../src/space/wasLink.ts"],"names":[],"mappings":"AAgBA,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;CACf;AAyCD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,UAAU,EACX,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,MAAM,CAOT;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAyC/D"}
@@ -0,0 +1,123 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The `was-link` QR hand-off payload -- the "Connect mobile wallet" flow where
6
+ * the web wallet displays a QR that carries an account's controller secret and
7
+ * the mobile wallet scans it.
8
+ *
9
+ * The payload is a NON-URL JSON blob (deliberately not a link, so the OS camera
10
+ * app / deep-link handling never routes it and it cannot leak into browser
11
+ * history or link-preview fetchers). It is accepted only by the in-app scanner.
12
+ * Shape: `{ v: 1, t: 'was-link', serverUrl, secret: base64url(utf8(passphrase)) }`.
13
+ */
14
+ import { base64urlnopad } from '@scure/base';
15
+ import { HumanReadableError } from './errors.js';
16
+ const NOT_A_LINK = 'This QR code is not a wallet connection code.';
17
+ const BAD_SERVER = 'Connection code points at an insecure or invalid server and was rejected.';
18
+ /** Strips optional base64 padding so the no-pad decoder accepts either form. */
19
+ function stripPadding(value) {
20
+ return value.replace(/=+$/, '');
21
+ }
22
+ /**
23
+ * Validates the scanned `serverUrl` before the wallet ever signs a request to
24
+ * it. The code carries the controller secret in the clear, so a bad `serverUrl`
25
+ * would let a crafted QR aim capability-signed requests (and the secret) at an
26
+ * attacker-controlled or cleartext host. Requires a parseable `https:` URL;
27
+ * plain `http:` is allowed ONLY for loopback dev hosts.
28
+ *
29
+ * This is a scheme/host rule, not an allowlist: the payload carries its own
30
+ * `serverUrl`, and there is no app-configured set of permitted servers to inject
31
+ * -- a connection code names the storage server the user is joining. If a
32
+ * consuming app later needs to constrain the host further, that check belongs at
33
+ * the call site, on the returned {@link WasLinkPayload.serverUrl}.
34
+ */
35
+ function assertValidServerUrl(serverUrl) {
36
+ let url;
37
+ try {
38
+ url = new URL(serverUrl);
39
+ }
40
+ catch {
41
+ throw new HumanReadableError(BAD_SERVER);
42
+ }
43
+ const isLoopback = url.hostname === 'localhost' ||
44
+ url.hostname === '127.0.0.1' ||
45
+ url.hostname === '::1';
46
+ if (url.protocol === 'https:' || (url.protocol === 'http:' && isLoopback)) {
47
+ return;
48
+ }
49
+ throw new HumanReadableError(BAD_SERVER);
50
+ }
51
+ /**
52
+ * Encodes a passphrase into the payload's `secret` field: `base64url(utf8(...))`,
53
+ * no padding.
54
+ *
55
+ * @param passphrase {string}
56
+ * @returns {string}
57
+ */
58
+ export function encodeWasLinkSecret(passphrase) {
59
+ return base64urlnopad.encode(new TextEncoder().encode(passphrase));
60
+ }
61
+ /**
62
+ * Builds the full `was-link` payload string a QR encodes.
63
+ *
64
+ * @param options {object}
65
+ * @param options.serverUrl {string}
66
+ * @param options.passphrase {string}
67
+ * @returns {string}
68
+ */
69
+ export function buildWasLinkPayload({ serverUrl, passphrase }) {
70
+ return JSON.stringify({
71
+ v: 1,
72
+ t: 'was-link',
73
+ serverUrl,
74
+ secret: encodeWasLinkSecret(passphrase)
75
+ });
76
+ }
77
+ /**
78
+ * Parses and validates a scanned payload, returning the server URL and the
79
+ * decoded string secret. Throws {@link HumanReadableError} for anything that is
80
+ * not a valid current-version `was-link` payload (so the scanner shows a clean
81
+ * message rather than mis-handling an unrelated QR).
82
+ *
83
+ * @param raw {string} the raw scanned code value
84
+ * @returns {WasLinkPayload}
85
+ */
86
+ export function parseWasLinkPayload(raw) {
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(raw);
90
+ }
91
+ catch {
92
+ throw new HumanReadableError(NOT_A_LINK);
93
+ }
94
+ if (typeof parsed !== 'object' || parsed === null) {
95
+ throw new HumanReadableError(NOT_A_LINK);
96
+ }
97
+ const { v, t, serverUrl, secret } = parsed;
98
+ if (t !== 'was-link') {
99
+ throw new HumanReadableError(NOT_A_LINK);
100
+ }
101
+ if (v !== 1) {
102
+ throw new HumanReadableError('This connection code is from a newer app version. Please update.');
103
+ }
104
+ if (typeof serverUrl !== 'string' || serverUrl.length === 0) {
105
+ throw new HumanReadableError('Connection code is missing its server.');
106
+ }
107
+ assertValidServerUrl(serverUrl);
108
+ if (typeof secret !== 'string' || secret.length === 0) {
109
+ throw new HumanReadableError('Connection code is missing its secret.');
110
+ }
111
+ let secretText;
112
+ try {
113
+ secretText = new TextDecoder().decode(base64urlnopad.decode(stripPadding(secret)));
114
+ }
115
+ catch {
116
+ throw new HumanReadableError('Connection code has a malformed secret.');
117
+ }
118
+ if (secretText.length === 0) {
119
+ throw new HumanReadableError('Connection code has an empty secret.');
120
+ }
121
+ return { serverUrl, secret: secretText };
122
+ }
123
+ //# sourceMappingURL=wasLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasLink.js","sourceRoot":"","sources":["../../src/space/wasLink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The generic per-collection spec SHAPE the replication engine + store drive one
6
+ * feed with. It bundles everything a feed needs: its id and id-derivation model,
7
+ * its plaintext-projection transactional writers, its lazy-migration sweep, the
8
+ * read-model refresh to fire after a pull, and (for a mutable collection) its
9
+ * 412 last-write-wins policy.
10
+ *
11
+ * Only the SHAPE lives here. The concrete registry -- which collections a given
12
+ * app replicates and how each writer touches the app's own store -- stays
13
+ * app-side, because the writers bind to the app's transaction handle and its
14
+ * read-model refresh mechanism. The spec is parameterized over those two so a
15
+ * SQLite-backed replica and a future RxDB adapter can both implement it without
16
+ * this module importing either store:
17
+ *
18
+ * - `Tx` is the store's transaction handle passed to `applyUpsertTx` /
19
+ * `applyDeleteTx` (a SQLite database instance in the mobile store; whatever an
20
+ * RxDB writer needs in the browser store). It replaces the mobile store's
21
+ * concrete `SQLiteDatabase` leak with a type parameter.
22
+ * - `RefreshContext` is whatever `onPullApplied` needs to trigger a read-model
23
+ * refresh (a Redux dispatch in one app, a store handle or `void` in another).
24
+ */
25
+ import type { DocCipher, Json, ResolveConflict, SyncStore, WasSyncPort } from './types.js';
26
+ /**
27
+ * One synced collection's full behavior. `applyUpsertTx` / `applyDeleteTx` are
28
+ * the store's plaintext-projection writers, invoked inside the store's pull
29
+ * transaction with the decrypted payload; `migrate` is the unlinked-row sweep;
30
+ * `makeResolveConflict` is present only for a mutable (LWW) collection, absent
31
+ * for insert-only content-addressed collections whose push settlement covers
32
+ * every 412.
33
+ *
34
+ * `encryption` selects the doc cipher and the server-side collection marker:
35
+ * `'edv'` (the default) envelopes every doc, `'plaintext'` ships it verbatim;
36
+ * `isPublic` grants collection-level world read on the server. Both replicas
37
+ * MUST agree on `collectionId` / `idDerivation` / `encryption` / `isPublic` for
38
+ * a collection, or their writes land in separate or differently-shaped
39
+ * collections and never converge.
40
+ */
41
+ export interface SyncedCollectionSpec<Tx = unknown, RefreshContext = void> {
42
+ collectionId: string;
43
+ idDerivation: 'content' | 'random';
44
+ encryption?: 'edv' | 'plaintext';
45
+ isPublic?: boolean;
46
+ applyUpsertTx: (tx: Tx, profileRecordId: string, syncId: string, payload: Json) => Promise<void>;
47
+ applyDeleteTx: (tx: Tx, profileRecordId: string, syncId: string) => Promise<void>;
48
+ onPullApplied: (context: RefreshContext) => void;
49
+ migrate: (options: {
50
+ profileRecordId: string;
51
+ cipher: DocCipher;
52
+ signal?: AbortSignal;
53
+ }) => Promise<void>;
54
+ makeResolveConflict?: (options: {
55
+ profileRecordId: string;
56
+ cipher: DocCipher;
57
+ port: WasSyncPort;
58
+ store: SyncStore;
59
+ }) => ResolveConflict;
60
+ }
61
+ //# sourceMappingURL=collections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../src/sync/collections.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACV,SAAS,EACT,IAAI,EACJ,eAAe,EACf,SAAS,EACT,WAAW,EACZ,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,oBAAoB,CAAC,EAAE,GAAG,OAAO,EAAE,cAAc,GAAG,IAAI;IACvE,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,SAAS,GAAG,QAAQ,CAAA;IAClC,UAAU,CAAC,EAAE,KAAK,GAAG,WAAW,CAAA;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,EAAE,CACb,EAAE,EAAE,EAAE,EACN,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,IAAI,KACV,OAAO,CAAC,IAAI,CAAC,CAAA;IAClB,aAAa,EAAE,CACb,EAAE,EAAE,EAAE,EACN,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,CAAA;IAClB,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAChD,OAAO,EAAE,CAAC,OAAO,EAAE;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,SAAS,CAAA;QACjB,MAAM,CAAC,EAAE,WAAW,CAAA;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnB,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC9B,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,SAAS,CAAA;QACjB,IAAI,EAAE,WAAW,CAAA;QACjB,KAAK,EAAE,SAAS,CAAA;KACjB,KAAK,eAAe,CAAA;CACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.js","sourceRoot":"","sources":["../../src/sync/collections.ts"],"names":[],"mappings":""}
@@ -0,0 +1,112 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * `SyncEngine` -- drives one `(replica, collection)` feed through the pull/push
6
+ * cycle. Single-flight (concurrent `sync()` calls coalesce and set a rerun
7
+ * flag), migrate-once (the initial pull-before-migrate ordering that keeps
8
+ * envelope minting from creating server-side duplicates), and self-healing via
9
+ * exponential backoff + jitter on failure.
10
+ *
11
+ * All side effects are injected ({@link SyncEngineDeps}) so the engine runs
12
+ * anywhere -- browser, Node, or React Native -- with a fake port, an in-memory
13
+ * store, and a non-firing scheduler. The consuming app wires the real port,
14
+ * DocCipher, provisioning, and lazy migration.
15
+ */
16
+ import type { Json, ResolveConflict, SyncStore, WasSyncPort } from './types.js';
17
+ /** Per-feed replication status, surfaced to the app's state layer. */
18
+ export type SyncStatus = 'idle' | 'syncing' | 'synced' | 'error';
19
+ /**
20
+ * Everything the engine needs, injected. The pure protocol ({@link WasSyncPort},
21
+ * {@link SyncStore}) plus the app-supplied seams: provisioning, the
22
+ * migrated/last-synced stamps, decryption, and status/refetch callbacks. The
23
+ * `schedule` / `random` seams make backoff deterministic under test.
24
+ */
25
+ export interface SyncEngineDeps {
26
+ port: WasSyncPort;
27
+ store: SyncStore;
28
+ /** Decrypts a pulled body to its plaintext payload (DocCipher). */
29
+ decryptDoc: (envelope: Json) => Promise<Json>;
30
+ /**
31
+ * The 412 policy for a mutable (LWW) collection. Absent for insert-only
32
+ * content-addressed collections, whose built-in push settlement covers every
33
+ * conflict.
34
+ */
35
+ resolveConflict?: ResolveConflict;
36
+ batchSize?: number;
37
+ /** Idempotent space + collection provisioning. */
38
+ ensureProvisioned: () => Promise<void>;
39
+ /** Has this feed's lazy migration already run (per-collection milestone)? */
40
+ isMigrated: () => Promise<boolean>;
41
+ /** Mint bodies for this feed's still-unlinked local rows. */
42
+ runLazyMigration: (signal: AbortSignal) => Promise<void>;
43
+ /** Record this feed's migrated milestone after a successful first migration. */
44
+ stampMigrated: () => Promise<void>;
45
+ /** Stamp the replica's last-synced time after a successful cycle. */
46
+ stampLastSynced: () => Promise<void>;
47
+ /** Called on every status transition (drives the app's state layer). */
48
+ onStatusChange?: (status: SyncStatus) => void;
49
+ /** Called after a pull that applied >= 1 document (triggers the refetch). */
50
+ onPullApplied?: () => void;
51
+ backoff?: {
52
+ baseDelayMs?: number;
53
+ maxDelayMs?: number;
54
+ };
55
+ /** Schedules a retry; returns a canceller. Defaults to setTimeout. */
56
+ schedule?: (fn: () => void, delayMs: number) => () => void;
57
+ /** Jitter source in [0, 1). Defaults to Math.random. */
58
+ random?: () => number;
59
+ }
60
+ export declare class SyncEngine {
61
+ private readonly deps;
62
+ status: SyncStatus;
63
+ private readonly batchSize;
64
+ private readonly baseDelayMs;
65
+ private readonly maxDelayMs;
66
+ private readonly schedule;
67
+ private readonly random;
68
+ private running;
69
+ private rerunRequested;
70
+ private stopped;
71
+ private failureCount;
72
+ private currentRun;
73
+ private abortController;
74
+ private cancelRetry;
75
+ constructor(deps: SyncEngineDeps);
76
+ /**
77
+ * Requests a sync. Single-flight: if a cycle is in flight this only flags a
78
+ * rerun (so writes that land mid-cycle are not lost) and resolves with the
79
+ * in-flight run; otherwise it starts a fresh run. Never rejects -- failures
80
+ * settle into `status = 'error'` plus a scheduled backoff retry, per the
81
+ * local-first invariant (sync must never surface as a rejected write).
82
+ *
83
+ * @returns {Promise<void>}
84
+ */
85
+ sync(): Promise<void>;
86
+ /**
87
+ * Stops the engine: aborts any in-flight cycle (the injected signal unwinds
88
+ * pull/push between pages/rows), cancels a pending retry, and resets to idle.
89
+ * The caller drops the cached agents/ciphers so key material does not outlive
90
+ * the unlocked session.
91
+ */
92
+ stop(): void;
93
+ private run;
94
+ /**
95
+ * One full replication cycle. On the very first run (never migrated) it pulls
96
+ * before the sweep so existing local rows hash-link to any bodies already on
97
+ * the server -- the sweep then only encrypts genuinely-new records
98
+ * (re-encrypting an existing one would mint a different content id and leave a
99
+ * permanent server duplicate). The unlinked-record sweep (`runLazyMigration`)
100
+ * runs on EVERY cycle, not just the first, so records that enter the replica
101
+ * outside the synced write path -- an import, or a write whose minting failed
102
+ * and fell back to a plain insert -- are still picked up and pushed (it is a
103
+ * cheap no-op when there are none). Steady state is sweep-then-push-then-pull:
104
+ * our own writes echo back in the same cycle's pull, idempotently.
105
+ */
106
+ private runCycle;
107
+ private pull;
108
+ private scheduleRetry;
109
+ private clearRetry;
110
+ private setStatus;
111
+ }
112
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/sync/engine.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAI/E,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AAMhE;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,SAAS,CAAA;IAChB,mEAAmE;IACnE,UAAU,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C;;;;OAIG;IACH,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,6EAA6E;IAC7E,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAClC,6DAA6D;IAC7D,gBAAgB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,gFAAgF;IAChF,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,qEAAqE;IACrE,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAEpC,wEAAwE;IACxE,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IAC7C,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAE1B,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvD,sEAAsE;IACtE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAC1D,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;CACtB;AAOD,qBAAa,UAAU;IAiBT,OAAO,CAAC,QAAQ,CAAC,IAAI;IAhBjC,MAAM,EAAE,UAAU,CAAS;IAE3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiD;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IAErC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,WAAW,CAA4B;gBAElB,IAAI,EAAE,cAAc;IAQjD;;;;;;;;OAQG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYrB;;;;;OAKG;IACH,IAAI,IAAI,IAAI;YAOE,GAAG;IA8BjB;;;;;;;;;;;OAWG;YACW,QAAQ;YAoDR,IAAI;IAWlB,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,SAAS;CAOlB"}