@decentrl/sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/client.d.ts +36 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +192 -0
  4. package/dist/contract-manager.d.ts +23 -0
  5. package/dist/contract-manager.d.ts.map +1 -0
  6. package/dist/contract-manager.js +91 -0
  7. package/dist/define-app.d.ts +8 -0
  8. package/dist/define-app.d.ts.map +1 -0
  9. package/dist/define-app.js +7 -0
  10. package/dist/direct-transport.d.ts +69 -0
  11. package/dist/direct-transport.d.ts.map +1 -0
  12. package/dist/direct-transport.js +450 -0
  13. package/dist/errors.d.ts +7 -0
  14. package/dist/errors.d.ts.map +1 -0
  15. package/dist/errors.js +10 -0
  16. package/dist/event-processor.d.ts +19 -0
  17. package/dist/event-processor.d.ts.map +1 -0
  18. package/dist/event-processor.js +93 -0
  19. package/dist/identity-manager.d.ts +22 -0
  20. package/dist/identity-manager.d.ts.map +1 -0
  21. package/dist/identity-manager.js +62 -0
  22. package/dist/identity-serialization.d.ts +5 -0
  23. package/dist/identity-serialization.d.ts.map +1 -0
  24. package/dist/identity-serialization.js +30 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +10 -0
  28. package/dist/persistence.d.ts +11 -0
  29. package/dist/persistence.d.ts.map +1 -0
  30. package/dist/persistence.js +82 -0
  31. package/dist/state-store.d.ts +12 -0
  32. package/dist/state-store.d.ts.map +1 -0
  33. package/dist/state-store.js +32 -0
  34. package/dist/sync-manager.d.ts +33 -0
  35. package/dist/sync-manager.d.ts.map +1 -0
  36. package/dist/sync-manager.js +244 -0
  37. package/dist/tag-templates.d.ts +2 -0
  38. package/dist/tag-templates.d.ts.map +1 -0
  39. package/dist/tag-templates.js +23 -0
  40. package/dist/test-helpers.d.ts +15 -0
  41. package/dist/test-helpers.d.ts.map +1 -0
  42. package/dist/test-helpers.js +65 -0
  43. package/dist/transport.d.ts +41 -0
  44. package/dist/transport.d.ts.map +1 -0
  45. package/dist/transport.js +1 -0
  46. package/dist/types.d.ts +131 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +1 -0
  49. package/dist/websocket-transport.d.ts +36 -0
  50. package/dist/websocket-transport.d.ts.map +1 -0
  51. package/dist/websocket-transport.js +160 -0
  52. package/package.json +35 -0
  53. package/src/client.ts +277 -0
  54. package/src/contract-manager.test.ts +207 -0
  55. package/src/contract-manager.ts +130 -0
  56. package/src/define-app.ts +25 -0
  57. package/src/direct-transport.test.ts +460 -0
  58. package/src/direct-transport.ts +729 -0
  59. package/src/errors.ts +23 -0
  60. package/src/event-processor.ts +133 -0
  61. package/src/identity-manager.ts +91 -0
  62. package/src/identity-serialization.ts +33 -0
  63. package/src/index.ts +43 -0
  64. package/src/persistence.ts +103 -0
  65. package/src/sdk.e2e.test.ts +367 -0
  66. package/src/state-store.ts +42 -0
  67. package/src/sync-manager.test.ts +414 -0
  68. package/src/sync-manager.ts +308 -0
  69. package/src/tag-templates.test.ts +111 -0
  70. package/src/tag-templates.ts +30 -0
  71. package/src/test-helpers.ts +88 -0
  72. package/src/transport.ts +65 -0
  73. package/src/types.ts +191 -0
  74. package/src/websocket-transport.ts +233 -0
@@ -0,0 +1,62 @@
1
+ import { DecentrlSDKError } from './errors.js';
2
+ import { deserializeKeys, serializeIdentity } from './identity-serialization.js';
3
+ export class IdentityManager {
4
+ identity = null;
5
+ listeners = new Set();
6
+ transport = null;
7
+ setTransport(transport) {
8
+ this.transport = transport;
9
+ }
10
+ getIdentity() {
11
+ return this.identity;
12
+ }
13
+ requireIdentity() {
14
+ if (!this.identity) {
15
+ throw new DecentrlSDKError('Identity not initialized. Call create() or load() first.', 'IDENTITY_NOT_INITIALIZED');
16
+ }
17
+ return this.identity;
18
+ }
19
+ async create(options) {
20
+ if (this.identity) {
21
+ throw new DecentrlSDKError('Identity already exists. Call reset() first to create a new one.', 'IDENTITY_ALREADY_EXISTS');
22
+ }
23
+ if (!this.transport) {
24
+ throw new DecentrlSDKError('Transport not set', 'NO_TRANSPORT');
25
+ }
26
+ const serialized = await this.transport.createIdentity(options);
27
+ return this.load(serialized);
28
+ }
29
+ load(serialized) {
30
+ this.identity = {
31
+ did: serialized.did,
32
+ alias: serialized.alias,
33
+ mediatorDid: serialized.mediatorDid,
34
+ mediatorEndpoint: serialized.mediatorEndpoint,
35
+ keys: deserializeKeys(serialized),
36
+ mediatorContract: serialized.mediatorContract,
37
+ };
38
+ // Sync with transport
39
+ this.transport?.loadIdentity?.(serialized);
40
+ this.notify();
41
+ return this.identity;
42
+ }
43
+ serialize() {
44
+ const identity = this.requireIdentity();
45
+ return serializeIdentity(identity);
46
+ }
47
+ reset() {
48
+ this.identity = null;
49
+ this.notify();
50
+ }
51
+ onChange(listener) {
52
+ this.listeners.add(listener);
53
+ return () => {
54
+ this.listeners.delete(listener);
55
+ };
56
+ }
57
+ notify() {
58
+ for (const listener of this.listeners) {
59
+ listener(this.identity);
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,5 @@
1
+ import { type DecentrlIdentityKeys } from '@decentrl/crypto';
2
+ import type { IdentityState, SerializedIdentity } from './types.js';
3
+ export declare const serializeIdentity: (identity: IdentityState) => SerializedIdentity;
4
+ export declare const deserializeKeys: (serialized: SerializedIdentity) => DecentrlIdentityKeys;
5
+ //# sourceMappingURL=identity-serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-serialization.d.ts","sourceRoot":"","sources":["../src/identity-serialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE,eAAO,MAAM,iBAAiB,GAAI,UAAU,aAAa,KAAG,kBAiB1D,CAAC;AAEH,eAAO,MAAM,eAAe,GAAI,YAAY,kBAAkB,KAAG,oBAU/D,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { base64Decode, base64Encode } from '@decentrl/crypto';
2
+ export const serializeIdentity = (identity) => ({
3
+ did: identity.did,
4
+ alias: identity.alias,
5
+ mediatorDid: identity.mediatorDid,
6
+ mediatorEndpoint: identity.mediatorEndpoint,
7
+ keys: {
8
+ signing: {
9
+ privateKey: base64Encode(identity.keys.signing.privateKey),
10
+ publicKey: base64Encode(identity.keys.signing.publicKey),
11
+ },
12
+ encryption: {
13
+ privateKey: base64Encode(identity.keys.encryption.privateKey),
14
+ publicKey: base64Encode(identity.keys.encryption.publicKey),
15
+ },
16
+ storageKey: base64Encode(identity.keys.storageKey),
17
+ },
18
+ mediatorContract: identity.mediatorContract,
19
+ });
20
+ export const deserializeKeys = (serialized) => ({
21
+ signing: {
22
+ privateKey: base64Decode(serialized.keys.signing.privateKey),
23
+ publicKey: base64Decode(serialized.keys.signing.publicKey),
24
+ },
25
+ encryption: {
26
+ privateKey: base64Decode(serialized.keys.encryption.privateKey),
27
+ publicKey: base64Decode(serialized.keys.encryption.publicKey),
28
+ },
29
+ storageKey: base64Decode(serialized.keys.storageKey),
30
+ });
@@ -0,0 +1,18 @@
1
+ export { DecentrlClient } from './client.js';
2
+ export { ContractManager } from './contract-manager.js';
3
+ export type { DecentrlApp } from './define-app.js';
4
+ export { defineDecentrlApp } from './define-app.js';
5
+ export type { DirectTransportOptions, HttpPost } from './direct-transport.js';
6
+ export { DirectTransport } from './direct-transport.js';
7
+ export type { DecentrlSDKErrorCode } from './errors.js';
8
+ export { DecentrlSDKError } from './errors.js';
9
+ export { EventProcessor } from './event-processor.js';
10
+ export { IdentityManager } from './identity-manager.js';
11
+ export type { PersistOptions } from './persistence.js';
12
+ export { StateStore } from './state-store.js';
13
+ export { SyncManager } from './sync-manager.js';
14
+ export type { DecentrlTransport } from './transport.js';
15
+ export type { ArchivedContract, DecentrlAppConfig, DecentrlClientConfig, EventDefinition, EventDefinitions, EventEnvelope, EventMeta, IdentityState, InferState, PaginatedResult, PaginationMeta, PendingContractRequest, PublishOptions, QueryOptions, SerializedIdentity, SerializedKeyPair, StateDefinitions, StateListener, StateSliceDefinition, StoredSignedContract, SyncOptions, } from './types.js';
16
+ export type { ConnectionStatus, WebSocketTransportCallbacks, WebSocketTransportIdentity, } from './websocket-transport.js';
17
+ export { deriveWsUrl, WebSocketTransport } from './websocket-transport.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EACX,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,aAAa,EACb,UAAU,EACV,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,GACX,MAAM,YAAY,CAAC;AACpB,YAAY,EACX,gBAAgB,EAChB,2BAA2B,EAC3B,0BAA0B,GAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { DecentrlClient } from './client.js';
2
+ export { ContractManager } from './contract-manager.js';
3
+ export { defineDecentrlApp } from './define-app.js';
4
+ export { DirectTransport } from './direct-transport.js';
5
+ export { DecentrlSDKError } from './errors.js';
6
+ export { EventProcessor } from './event-processor.js';
7
+ export { IdentityManager } from './identity-manager.js';
8
+ export { StateStore } from './state-store.js';
9
+ export { SyncManager } from './sync-manager.js';
10
+ export { deriveWsUrl, WebSocketTransport } from './websocket-transport.js';
@@ -0,0 +1,11 @@
1
+ import type { SerializedIdentity } from './types.js';
2
+ export interface PersistOptions {
3
+ /** localStorage key prefix. Identity stored at `${key}:identity`, state at `${key}:state` */
4
+ key: string;
5
+ }
6
+ export declare function loadPersistedIdentity(opts: PersistOptions): SerializedIdentity | null;
7
+ export declare function persistIdentity(opts: PersistOptions, identity: SerializedIdentity | null): void;
8
+ export declare function loadPersistedState<T extends Record<string, unknown>>(opts: PersistOptions): T | null;
9
+ export declare function persistState(opts: PersistOptions, state: Record<string, unknown>): void;
10
+ export declare function clearPersisted(opts: PersistOptions): void;
11
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,cAAc;IAC9B,6FAA6F;IAC7F,GAAG,EAAE,MAAM,CAAC;CACZ;AAUD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,kBAAkB,GAAG,IAAI,CAkBrF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAgB/F;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,cAAc,GAClB,CAAC,GAAG,IAAI,CAkBV;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYvF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAazD"}
@@ -0,0 +1,82 @@
1
+ function getStorage() {
2
+ try {
3
+ return typeof localStorage !== 'undefined' ? localStorage : null;
4
+ }
5
+ catch {
6
+ return null;
7
+ }
8
+ }
9
+ export function loadPersistedIdentity(opts) {
10
+ const storage = getStorage();
11
+ if (!storage) {
12
+ return null;
13
+ }
14
+ try {
15
+ const raw = storage.getItem(`${opts.key}:identity`);
16
+ if (!raw) {
17
+ return null;
18
+ }
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export function persistIdentity(opts, identity) {
26
+ const storage = getStorage();
27
+ if (!storage) {
28
+ return;
29
+ }
30
+ try {
31
+ if (identity) {
32
+ storage.setItem(`${opts.key}:identity`, JSON.stringify(identity));
33
+ }
34
+ else {
35
+ storage.removeItem(`${opts.key}:identity`);
36
+ }
37
+ }
38
+ catch {
39
+ // Silently fail (quota exceeded, etc.)
40
+ }
41
+ }
42
+ export function loadPersistedState(opts) {
43
+ const storage = getStorage();
44
+ if (!storage) {
45
+ return null;
46
+ }
47
+ try {
48
+ const raw = storage.getItem(`${opts.key}:state`);
49
+ if (!raw) {
50
+ return null;
51
+ }
52
+ return JSON.parse(raw);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ export function persistState(opts, state) {
59
+ const storage = getStorage();
60
+ if (!storage) {
61
+ return;
62
+ }
63
+ try {
64
+ storage.setItem(`${opts.key}:state`, JSON.stringify(state));
65
+ }
66
+ catch {
67
+ // Silently fail
68
+ }
69
+ }
70
+ export function clearPersisted(opts) {
71
+ const storage = getStorage();
72
+ if (!storage) {
73
+ return;
74
+ }
75
+ try {
76
+ storage.removeItem(`${opts.key}:identity`);
77
+ storage.removeItem(`${opts.key}:state`);
78
+ }
79
+ catch {
80
+ // Silently fail
81
+ }
82
+ }
@@ -0,0 +1,12 @@
1
+ import type { StateListener } from './types.js';
2
+ export declare class StateStore<TState extends Record<string, unknown>> {
3
+ private state;
4
+ private listeners;
5
+ constructor(initialState: TState);
6
+ getState: () => TState;
7
+ setSlice<K extends keyof TState>(key: K, value: TState[K]): void;
8
+ subscribe: (listener: StateListener<TState>) => (() => void);
9
+ reset(initialState: TState): void;
10
+ private notify;
11
+ }
12
+ //# sourceMappingURL=state-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAoC;gBAEzC,YAAY,EAAE,MAAM;IAIhC,QAAQ,QAAO,MAAM,CAEnB;IAEF,QAAQ,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI;IAShE,SAAS,GAAI,UAAU,aAAa,CAAC,MAAM,CAAC,KAAG,CAAC,MAAM,IAAI,CAAC,CAMzD;IAEF,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAKjC,OAAO,CAAC,MAAM;CAKd"}
@@ -0,0 +1,32 @@
1
+ export class StateStore {
2
+ state;
3
+ listeners = new Set();
4
+ constructor(initialState) {
5
+ this.state = { ...initialState };
6
+ }
7
+ getState = () => {
8
+ return this.state;
9
+ };
10
+ setSlice(key, value) {
11
+ if (this.state[key] === value) {
12
+ return;
13
+ }
14
+ this.state = { ...this.state, [key]: value };
15
+ this.notify();
16
+ }
17
+ subscribe = (listener) => {
18
+ this.listeners.add(listener);
19
+ return () => {
20
+ this.listeners.delete(listener);
21
+ };
22
+ };
23
+ reset(initialState) {
24
+ this.state = { ...initialState };
25
+ this.notify();
26
+ }
27
+ notify() {
28
+ for (const listener of this.listeners) {
29
+ listener(this.state);
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,33 @@
1
+ import type { ContractManager } from './contract-manager.js';
2
+ import type { EventProcessor } from './event-processor.js';
3
+ import type { IdentityManager } from './identity-manager.js';
4
+ import type { DecentrlTransport } from './transport.js';
5
+ import type { EventDefinitions, StateDefinitions, SyncOptions } from './types.js';
6
+ import { type ConnectionStatus } from './websocket-transport.js';
7
+ type StatusListener = (status: ConnectionStatus) => void;
8
+ export declare class SyncManager<TEvents extends EventDefinitions, TState extends StateDefinitions<TEvents>> {
9
+ private eventProcessor;
10
+ private contractManager;
11
+ private identityManager;
12
+ private transport;
13
+ private intervalId;
14
+ private isSyncing;
15
+ private wsTransport;
16
+ private connectionStatus;
17
+ private statusListeners;
18
+ private unsubscribePush;
19
+ private unsubscribeContractsPush;
20
+ private currentOptions;
21
+ constructor(eventProcessor: EventProcessor<TEvents, TState>, contractManager: ContractManager, identityManager: IdentityManager, transport: DecentrlTransport);
22
+ start(options?: SyncOptions): void;
23
+ stop(): void;
24
+ tick(onError?: (error: unknown) => void): Promise<void>;
25
+ getConnectionStatus: () => ConnectionStatus;
26
+ onConnectionStatusChange: (listener: StatusListener) => (() => void);
27
+ get isRunning(): boolean;
28
+ private loadStoredEvents;
29
+ private processUntaggedEvents;
30
+ private startWebSocket;
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=sync-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../src/sync-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAiB,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,KAAK,gBAAgB,EAAmC,MAAM,0BAA0B,CAAC;AAElG,KAAK,cAAc,GAAG,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEzD,qBAAa,WAAW,CACvB,OAAO,SAAS,gBAAgB,EAChC,MAAM,SAAS,gBAAgB,CAAC,OAAO,CAAC;IAYvC,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,SAAS;IAblB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,gBAAgB,CAAoC;IAC5D,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,wBAAwB,CAA6B;IAC7D,OAAO,CAAC,cAAc,CAAmB;gBAGhC,cAAc,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,EAC/C,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,iBAAiB;IAGrC,KAAK,CAAC,OAAO,GAAE,WAAgB,GAAG,IAAI;IA+DtC,IAAI,IAAI,IAAI;IAsBN,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAyC7D,mBAAmB,QAAO,gBAAgB,CAExC;IAEF,wBAAwB,GAAI,UAAU,cAAc,KAAG,CAAC,MAAM,IAAI,CAAC,CAMjE;IAEF,IAAI,SAAS,IAAI,OAAO,CAEvB;YAEa,gBAAgB;YA6BhB,qBAAqB;IA4CnC,OAAO,CAAC,cAAc;CA+DtB"}
@@ -0,0 +1,244 @@
1
+ import { deriveWsUrl, WebSocketTransport } from './websocket-transport.js';
2
+ export class SyncManager {
3
+ eventProcessor;
4
+ contractManager;
5
+ identityManager;
6
+ transport;
7
+ intervalId = null;
8
+ isSyncing = false;
9
+ wsTransport = null;
10
+ connectionStatus = 'disconnected';
11
+ statusListeners = new Set();
12
+ unsubscribePush = null;
13
+ unsubscribeContractsPush = null;
14
+ currentOptions = {};
15
+ constructor(eventProcessor, contractManager, identityManager, transport) {
16
+ this.eventProcessor = eventProcessor;
17
+ this.contractManager = contractManager;
18
+ this.identityManager = identityManager;
19
+ this.transport = transport;
20
+ }
21
+ start(options = {}) {
22
+ this.stop();
23
+ this.currentOptions = options;
24
+ // If transport supports push-based events, subscribe
25
+ if (this.transport.onEvents) {
26
+ this.unsubscribePush = this.transport.onEvents((events) => {
27
+ try {
28
+ this.eventProcessor.processBatch(events);
29
+ }
30
+ catch (err) {
31
+ console.error('[Decentrl] Failed to process pushed events:', err);
32
+ }
33
+ // Tag enrichment for non-ephemeral pushed events
34
+ if (events.some((e) => !e.meta.ephemeral)) {
35
+ this.processUntaggedEvents().catch((err) => console.debug('[Decentrl] Failed to process untagged events:', err));
36
+ }
37
+ });
38
+ if (this.transport.onContractsChanged) {
39
+ this.unsubscribeContractsPush = this.transport.onContractsChanged(() => {
40
+ this.contractManager.refresh();
41
+ });
42
+ }
43
+ // Still do an initial sync tick + slower polling for contracts
44
+ this.tick(options.onError);
45
+ const intervalMs = options.fallbackIntervalMs ?? 30_000;
46
+ this.intervalId = setInterval(() => {
47
+ this.tick(options.onError);
48
+ }, intervalMs);
49
+ // Load stored events from mediator to catch up on offline period
50
+ this.loadStoredEvents().catch((err) => console.debug('[Decentrl] loadStoredEvents() failed:', err));
51
+ // Process any events that haven't been tagged by the app yet
52
+ this.processUntaggedEvents().catch((err) => console.debug('[Decentrl] processUntaggedEvents() failed:', err));
53
+ return;
54
+ }
55
+ const intervalMs = options.intervalMs ?? 3000;
56
+ // Run immediately
57
+ this.tick(options.onError);
58
+ this.intervalId = setInterval(() => {
59
+ this.tick(options.onError);
60
+ }, intervalMs);
61
+ // Start WebSocket if available and enabled
62
+ const useWebSocket = options.websocket !== false;
63
+ if (useWebSocket && typeof globalThis.WebSocket !== 'undefined') {
64
+ this.startWebSocket(options);
65
+ }
66
+ }
67
+ stop() {
68
+ if (this.intervalId !== null) {
69
+ clearInterval(this.intervalId);
70
+ this.intervalId = null;
71
+ }
72
+ if (this.wsTransport) {
73
+ this.wsTransport.disconnect();
74
+ this.wsTransport = null;
75
+ }
76
+ if (this.unsubscribePush) {
77
+ this.unsubscribePush();
78
+ this.unsubscribePush = null;
79
+ }
80
+ if (this.unsubscribeContractsPush) {
81
+ this.unsubscribeContractsPush();
82
+ this.unsubscribeContractsPush = null;
83
+ }
84
+ }
85
+ async tick(onError) {
86
+ if (this.isSyncing) {
87
+ return;
88
+ }
89
+ this.isSyncing = true;
90
+ try {
91
+ const processedEvents = await this.transport.processPendingEvents();
92
+ this.eventProcessor.processBatch(processedEvents);
93
+ // Enrich any unprocessed events with app-computed tags
94
+ if (processedEvents.length > 0) {
95
+ await this.processUntaggedEvents();
96
+ }
97
+ // Refresh contracts
98
+ await this.contractManager.refresh();
99
+ // Auto-renew expiring contracts
100
+ const autoRenew = this.currentOptions.autoRenew;
101
+ if (autoRenew?.enabled) {
102
+ await this.contractManager.processAutoRenewals(autoRenew.threshold);
103
+ }
104
+ // Clean up old superseded contracts
105
+ try {
106
+ await this.contractManager.processContractCleanup();
107
+ }
108
+ catch (err) {
109
+ console.error('[Decentrl] Contract cleanup failed:', err);
110
+ }
111
+ }
112
+ catch (error) {
113
+ if (onError) {
114
+ onError(error);
115
+ }
116
+ }
117
+ finally {
118
+ this.isSyncing = false;
119
+ }
120
+ }
121
+ getConnectionStatus = () => {
122
+ return this.connectionStatus;
123
+ };
124
+ onConnectionStatusChange = (listener) => {
125
+ this.statusListeners.add(listener);
126
+ return () => {
127
+ this.statusListeners.delete(listener);
128
+ };
129
+ };
130
+ get isRunning() {
131
+ return this.intervalId !== null;
132
+ }
133
+ async loadStoredEvents() {
134
+ if (!this.transport.queryEvents) {
135
+ return;
136
+ }
137
+ try {
138
+ let page = 0;
139
+ const pageSize = 50;
140
+ while (true) {
141
+ const result = await this.transport.queryEvents({
142
+ pagination: { page, pageSize },
143
+ });
144
+ if (result.data.length > 0) {
145
+ this.eventProcessor.processBatch(result.data);
146
+ }
147
+ if ((page + 1) * pageSize >= result.pagination.total) {
148
+ break;
149
+ }
150
+ page++;
151
+ }
152
+ }
153
+ catch (err) {
154
+ console.debug('[Decentrl] loadStoredEvents() failed:', err);
155
+ }
156
+ }
157
+ async processUntaggedEvents() {
158
+ if (!this.transport.queryUnprocessedEvents) {
159
+ return;
160
+ }
161
+ if (!this.transport.updateEventTags) {
162
+ return;
163
+ }
164
+ try {
165
+ let page = 0;
166
+ const pageSize = 50;
167
+ while (true) {
168
+ const result = await this.transport.queryUnprocessedEvents({ page, pageSize });
169
+ if (result.data.length === 0) {
170
+ break;
171
+ }
172
+ this.eventProcessor.processBatch(result.data);
173
+ const tagUpdates = result.data
174
+ .filter((e) => !e.meta.ephemeral)
175
+ .map((e) => ({
176
+ eventId: e._mediatorEventId ?? e.meta.eventId,
177
+ tags: this.eventProcessor.computeTagsSafe(e.type, e.data),
178
+ }));
179
+ if (tagUpdates.length > 0) {
180
+ await this.transport.updateEventTags(tagUpdates);
181
+ }
182
+ if ((page + 1) * pageSize >= result.pagination.total) {
183
+ break;
184
+ }
185
+ page++;
186
+ }
187
+ }
188
+ catch (err) {
189
+ console.debug('[Decentrl] processUntaggedEvents failed:', err);
190
+ }
191
+ }
192
+ startWebSocket(options) {
193
+ const identity = this.identityManager.getIdentity();
194
+ if (!identity) {
195
+ return;
196
+ }
197
+ const wsUrl = deriveWsUrl(identity.mediatorEndpoint);
198
+ this.wsTransport = new WebSocketTransport(() => {
199
+ const id = this.identityManager.getIdentity();
200
+ return id ? { did: id.did, keys: id.keys } : null;
201
+ }, () => wsUrl, {
202
+ onPendingEvents: async (events) => {
203
+ try {
204
+ if (this.transport.processPreFetchedPendingEvents) {
205
+ const processed = await this.transport.processPreFetchedPendingEvents(events);
206
+ this.eventProcessor.processBatch(processed);
207
+ }
208
+ }
209
+ catch (error) {
210
+ options.onError?.(error);
211
+ }
212
+ },
213
+ onContractsUpdated: async () => {
214
+ try {
215
+ await this.contractManager.refresh();
216
+ }
217
+ catch (error) {
218
+ options.onError?.(error);
219
+ }
220
+ },
221
+ onStatusChange: (status) => {
222
+ this.connectionStatus = status;
223
+ // Adjust polling interval based on WS connection
224
+ if (this.intervalId !== null) {
225
+ clearInterval(this.intervalId);
226
+ const intervalMs = status === 'connected'
227
+ ? (options.fallbackIntervalMs ?? 30_000)
228
+ : (options.intervalMs ?? 3000);
229
+ this.intervalId = setInterval(() => {
230
+ this.tick(options.onError);
231
+ }, intervalMs);
232
+ // Trigger immediate catch-up tick when WS connects
233
+ if (status === 'connected') {
234
+ this.tick(options.onError);
235
+ }
236
+ }
237
+ for (const listener of this.statusListeners) {
238
+ listener(status);
239
+ }
240
+ },
241
+ });
242
+ this.wsTransport.connect();
243
+ }
244
+ }
@@ -0,0 +1,2 @@
1
+ export declare function evaluateTagTemplates(templates: string[], data: unknown): string[];
2
+ //# sourceMappingURL=tag-templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-templates.d.ts","sourceRoot":"","sources":["../src/tag-templates.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAEjF"}
@@ -0,0 +1,23 @@
1
+ export function evaluateTagTemplates(templates, data) {
2
+ return templates.map((t) => evaluateTemplate(t, data));
3
+ }
4
+ function evaluateTemplate(template, data) {
5
+ return template.replace(/\$\{([^}]+)\}/g, (_, path) => {
6
+ return String(resolveProperty(data, path) ?? '');
7
+ });
8
+ }
9
+ const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
10
+ function resolveProperty(obj, path) {
11
+ const parts = path.split(/[.[\]]+/).filter(Boolean);
12
+ let value = obj;
13
+ for (const part of parts) {
14
+ if (BLOCKED_KEYS.has(part)) {
15
+ return undefined;
16
+ }
17
+ if (value == null || typeof value !== 'object') {
18
+ return undefined;
19
+ }
20
+ value = value[part];
21
+ }
22
+ return value;
23
+ }
@@ -0,0 +1,15 @@
1
+ import type { ContractManager } from './contract-manager.js';
2
+ import type { EventProcessor } from './event-processor.js';
3
+ import type { IdentityManager } from './identity-manager.js';
4
+ import type { DecentrlTransport } from './transport.js';
5
+ import type { EventDefinitions, StateDefinitions, StoredSignedContract } from './types.js';
6
+ export declare const makeContract: (overrides: Partial<StoredSignedContract> & {
7
+ participantDid: string;
8
+ expiresAt: number;
9
+ timestamp: number;
10
+ }) => StoredSignedContract;
11
+ export declare const createMockIdentityManager: (overrides?: Partial<Record<keyof IdentityManager, unknown>>) => IdentityManager;
12
+ export declare const createMockTransport: (overrides?: Partial<DecentrlTransport>) => DecentrlTransport;
13
+ export declare const createMockEventProcessor: (overrides?: Partial<Record<string, unknown>>) => EventProcessor<EventDefinitions, StateDefinitions<EventDefinitions>>;
14
+ export declare const createMockContractManager: (overrides?: Partial<Record<string, unknown>>) => ContractManager;
15
+ //# sourceMappingURL=test-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE3F,eAAO,MAAM,YAAY,GACxB,WAAW,OAAO,CAAC,oBAAoB,CAAC,GAAG;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB,KACC,oBAsBD,CAAC;AAEH,eAAO,MAAM,yBAAyB,GACrC,YAAW,OAAO,CAAC,MAAM,CAAC,MAAM,eAAe,EAAE,OAAO,CAAC,CAAM,KAqB9C,eAAe,CAAC;AAElC,eAAO,MAAM,mBAAmB,GAAI,YAAW,OAAO,CAAC,iBAAiB,CAAM,KAW5D,iBAAiB,CAAC;AAEpC,eAAO,MAAM,wBAAwB,GAAI,YAAW,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,KAKvE,cAAc,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAEvF,eAAO,MAAM,yBAAyB,GAAI,YAAW,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,KAMxE,eAAe,CAAC"}