@nsky/sync 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.
@@ -0,0 +1,184 @@
1
+ import { ExponentialBackoffPolicy, retryOperation } from "../utils/index.mjs";
2
+ import { createSyncError, normalizeSyncError } from "../core/index.mjs";
3
+ //#region src/http/fetch-transport.ts
4
+ var FetchSyncTransport = class {
5
+ type = "http";
6
+ #baseUrl;
7
+ #fetch;
8
+ #headers;
9
+ #retryPolicy;
10
+ constructor(options) {
11
+ this.#baseUrl = options.baseUrl.replace(/\/+$/, "");
12
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
13
+ this.#headers = options.headers ?? {};
14
+ this.#retryPolicy = options.retryPolicy ?? new ExponentialBackoffPolicy();
15
+ }
16
+ async push(payload) {
17
+ await retryOperation(async () => {
18
+ await ensureOk(await this.#fetch(`${this.#baseUrl}/push`, {
19
+ method: "POST",
20
+ headers: this.#createHeaders(),
21
+ body: JSON.stringify(payload)
22
+ }));
23
+ }, {
24
+ policy: this.#retryPolicy,
25
+ normalizeError: normalizeSyncError
26
+ });
27
+ }
28
+ async pull(cursor, limit) {
29
+ return await retryOperation(async () => {
30
+ const url = new URL(`${this.#baseUrl}/pull`);
31
+ if (cursor !== null && cursor.length > 0) url.searchParams.set("cursor", cursor);
32
+ if (limit !== void 0) url.searchParams.set("limit", limit.toString());
33
+ const response = await this.#fetch(url.toString(), {
34
+ method: "GET",
35
+ headers: this.#createHeaders()
36
+ });
37
+ await ensureOk(response);
38
+ return await response.json();
39
+ }, {
40
+ policy: this.#retryPolicy,
41
+ normalizeError: normalizeSyncError
42
+ });
43
+ }
44
+ #createHeaders() {
45
+ return {
46
+ "Content-Type": "application/json",
47
+ ...this.#headers
48
+ };
49
+ }
50
+ };
51
+ async function ensureOk(response) {
52
+ if (response.ok) return;
53
+ throw createSyncError(codeFromStatus(response.status), `Sync HTTP request failed with status ${response.status}.`, await response.text());
54
+ }
55
+ function codeFromStatus(status) {
56
+ if (status === 401 || status === 403) return "AUTH_FAILED";
57
+ if (status === 410) return "CURSOR_EXPIRED";
58
+ if (status >= 500) return "SERVER_ERROR";
59
+ return "TRANSPORT_ERROR";
60
+ }
61
+ //#endregion
62
+ //#region src/http/websocket-transport.ts
63
+ var WebSocketSyncTransport = class {
64
+ type = "websocket";
65
+ #url;
66
+ #WebSocket;
67
+ #requestTimeoutMs;
68
+ #retryPolicy;
69
+ #subscribers = /* @__PURE__ */ new Set();
70
+ #pending = /* @__PURE__ */ new Map();
71
+ #socket = null;
72
+ constructor(options) {
73
+ this.#url = options.url;
74
+ this.#WebSocket = options.WebSocket ?? globalThis.WebSocket;
75
+ this.#requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
76
+ this.#retryPolicy = options.retryPolicy ?? new ExponentialBackoffPolicy();
77
+ }
78
+ async push(payload) {
79
+ await retryOperation(async () => {
80
+ if ((await this.#request({
81
+ type: "push",
82
+ payload
83
+ })).type !== "pushResult") throw createSyncError("SERIALIZATION_ERROR", "Unexpected WebSocket push response.");
84
+ }, {
85
+ policy: this.#retryPolicy,
86
+ normalizeError: normalizeSyncError
87
+ });
88
+ }
89
+ async pull(cursor, limit) {
90
+ return await retryOperation(async () => {
91
+ const message = await this.#request({
92
+ type: "pull",
93
+ cursor,
94
+ limit
95
+ });
96
+ if (message.type !== "pullResult") throw createSyncError("SERIALIZATION_ERROR", "Unexpected WebSocket pull response.");
97
+ return message.result;
98
+ }, {
99
+ policy: this.#retryPolicy,
100
+ normalizeError: normalizeSyncError
101
+ });
102
+ }
103
+ subscribe(onChanges) {
104
+ this.#subscribers.add(onChanges);
105
+ this.#connect();
106
+ return () => {
107
+ this.#subscribers.delete(onChanges);
108
+ };
109
+ }
110
+ close() {
111
+ this.#socket?.close();
112
+ this.#socket = null;
113
+ for (const pending of this.#pending.values()) {
114
+ clearTimeout(pending.timeout);
115
+ pending.reject(createSyncError("TRANSPORT_ERROR", "WebSocket transport closed."));
116
+ }
117
+ this.#pending.clear();
118
+ }
119
+ async #request(payload) {
120
+ const socket = await this.#connect();
121
+ const id = crypto.randomUUID();
122
+ const message = JSON.stringify({
123
+ id,
124
+ ...payload
125
+ });
126
+ return await new Promise((resolve, reject) => {
127
+ const timeout = setTimeout(() => {
128
+ this.#pending.delete(id);
129
+ reject(createSyncError("NETWORK_UNAVAILABLE", "WebSocket sync request timed out."));
130
+ }, this.#requestTimeoutMs);
131
+ this.#pending.set(id, {
132
+ resolve,
133
+ reject,
134
+ timeout
135
+ });
136
+ socket.send(message);
137
+ });
138
+ }
139
+ async #connect() {
140
+ if (this.#socket?.readyState === this.#WebSocket.OPEN) return this.#socket;
141
+ const socket = new this.#WebSocket(this.#url);
142
+ this.#socket = socket;
143
+ socket.addEventListener("message", (event) => this.#handleMessage(event));
144
+ if (socket.readyState === this.#WebSocket.OPEN) return socket;
145
+ return await new Promise((resolve, reject) => {
146
+ const handleOpen = () => {
147
+ cleanup();
148
+ resolve(socket);
149
+ };
150
+ const handleError = () => {
151
+ cleanup();
152
+ reject(createSyncError("NETWORK_UNAVAILABLE", "WebSocket connection failed."));
153
+ };
154
+ const cleanup = () => {
155
+ socket.removeEventListener("open", handleOpen);
156
+ socket.removeEventListener("error", handleError);
157
+ };
158
+ socket.addEventListener("open", handleOpen);
159
+ socket.addEventListener("error", handleError);
160
+ });
161
+ }
162
+ #handleMessage(event) {
163
+ const rawData = "data" in event ? event.data : void 0;
164
+ if (typeof rawData !== "string") return;
165
+ const message = JSON.parse(rawData);
166
+ if (message.type === "changes") {
167
+ for (const subscriber of this.#subscribers) subscriber(message.cursor);
168
+ return;
169
+ }
170
+ const pending = this.#pending.get(message.id);
171
+ if (pending === void 0) return;
172
+ clearTimeout(pending.timeout);
173
+ this.#pending.delete(message.id);
174
+ if ("error" in message && message.error !== void 0) {
175
+ pending.reject(createSyncError("TRANSPORT_ERROR", message.error.message ?? "WebSocket sync request failed.", message.error));
176
+ return;
177
+ }
178
+ pending.resolve(message);
179
+ }
180
+ };
181
+ //#endregion
182
+ export { FetchSyncTransport, WebSocketSyncTransport };
183
+
184
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["#baseUrl","#fetch","#headers","#retryPolicy","#createHeaders","#url","#WebSocket","#requestTimeoutMs","#retryPolicy","#subscribers","#pending","#request","#connect","#socket","#handleMessage"],"sources":["../../src/http/fetch-transport.ts","../../src/http/websocket-transport.ts"],"sourcesContent":["import { createSyncError, type PullResult, type PushPayload, type SyncTransport } from \"../core\";\nimport type { SyncErrorCode } from \"../core\";\nimport { ExponentialBackoffPolicy, retryOperation, type RetryPolicy } from \"../utils\";\nimport { normalizeSyncError } from \"../core\";\n\nexport interface FetchSyncTransportOptions {\n readonly baseUrl: string;\n readonly fetch?: typeof fetch;\n readonly headers?: Record<string, string>;\n readonly retryPolicy?: RetryPolicy;\n}\n\nexport class FetchSyncTransport implements SyncTransport {\n readonly type = \"http\";\n readonly #baseUrl: string;\n readonly #fetch: typeof fetch;\n readonly #headers: Record<string, string>;\n readonly #retryPolicy: RetryPolicy;\n\n constructor(options: FetchSyncTransportOptions) {\n this.#baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.#headers = options.headers ?? {};\n this.#retryPolicy = options.retryPolicy ?? new ExponentialBackoffPolicy();\n }\n\n async push(payload: PushPayload): Promise<void> {\n await retryOperation(\n async () => {\n const response = await this.#fetch(`${this.#baseUrl}/push`, {\n method: \"POST\",\n headers: this.#createHeaders(),\n body: JSON.stringify(payload),\n });\n\n await ensureOk(response);\n },\n { policy: this.#retryPolicy, normalizeError: normalizeSyncError },\n );\n }\n\n async pull(cursor: string | null, limit?: number): Promise<PullResult> {\n return await retryOperation(\n async () => {\n const url = new URL(`${this.#baseUrl}/pull`);\n\n if (cursor !== null && cursor.length > 0) {\n url.searchParams.set(\"cursor\", cursor);\n }\n\n if (limit !== undefined) {\n url.searchParams.set(\"limit\", limit.toString());\n }\n\n const response = await this.#fetch(url.toString(), {\n method: \"GET\",\n headers: this.#createHeaders(),\n });\n\n await ensureOk(response);\n return (await response.json()) as PullResult;\n },\n { policy: this.#retryPolicy, normalizeError: normalizeSyncError },\n );\n }\n\n #createHeaders(): Record<string, string> {\n return {\n \"Content-Type\": \"application/json\",\n ...this.#headers,\n };\n }\n}\n\nasync function ensureOk(response: Response): Promise<void> {\n if (response.ok) {\n return;\n }\n\n throw createSyncError(\n codeFromStatus(response.status),\n `Sync HTTP request failed with status ${response.status}.`,\n await response.text(),\n );\n}\n\nfunction codeFromStatus(status: number): SyncErrorCode {\n if (status === 401 || status === 403) {\n return \"AUTH_FAILED\";\n }\n\n if (status === 410) {\n return \"CURSOR_EXPIRED\";\n }\n\n if (status >= 500) {\n return \"SERVER_ERROR\";\n }\n\n return \"TRANSPORT_ERROR\";\n}\n","import { createSyncError, normalizeSyncError, type PullResult, type PushPayload } from \"../core\";\nimport type { SyncTransport } from \"../core\";\nimport { ExponentialBackoffPolicy, retryOperation, type RetryPolicy } from \"../utils\";\n\nexport interface WebSocketSyncTransportOptions {\n readonly url: string;\n readonly WebSocket?: typeof globalThis.WebSocket;\n readonly requestTimeoutMs?: number;\n readonly retryPolicy?: RetryPolicy;\n}\n\ntype ServerMessage =\n | { readonly id: string; readonly type: \"pushResult\"; readonly error?: ErrorPayload }\n | {\n readonly id: string;\n readonly type: \"pullResult\";\n readonly result: PullResult;\n readonly error?: ErrorPayload;\n }\n | { readonly type: \"changes\"; readonly cursor: string };\n\ninterface ErrorPayload {\n readonly code?: string;\n readonly message?: string;\n}\n\nexport class WebSocketSyncTransport implements SyncTransport {\n readonly type = \"websocket\";\n readonly #url: string;\n readonly #WebSocket: typeof globalThis.WebSocket;\n readonly #requestTimeoutMs: number;\n readonly #retryPolicy: RetryPolicy;\n readonly #subscribers = new Set<(cursor: string) => void>();\n readonly #pending = new Map<\n string,\n {\n readonly resolve: (message: ServerMessage) => void;\n readonly reject: (error: unknown) => void;\n readonly timeout: ReturnType<typeof setTimeout>;\n }\n >();\n #socket: WebSocket | null = null;\n\n constructor(options: WebSocketSyncTransportOptions) {\n this.#url = options.url;\n this.#WebSocket = options.WebSocket ?? globalThis.WebSocket;\n this.#requestTimeoutMs = options.requestTimeoutMs ?? 30_000;\n this.#retryPolicy = options.retryPolicy ?? new ExponentialBackoffPolicy();\n }\n\n async push(payload: PushPayload): Promise<void> {\n await retryOperation(\n async () => {\n const message = await this.#request({ type: \"push\", payload });\n if (message.type !== \"pushResult\") {\n throw createSyncError(\"SERIALIZATION_ERROR\", \"Unexpected WebSocket push response.\");\n }\n },\n { policy: this.#retryPolicy, normalizeError: normalizeSyncError },\n );\n }\n\n async pull(cursor: string | null, limit?: number): Promise<PullResult> {\n return await retryOperation(\n async () => {\n const message = await this.#request({ type: \"pull\", cursor, limit });\n if (message.type !== \"pullResult\") {\n throw createSyncError(\"SERIALIZATION_ERROR\", \"Unexpected WebSocket pull response.\");\n }\n return message.result;\n },\n { policy: this.#retryPolicy, normalizeError: normalizeSyncError },\n );\n }\n\n subscribe(onChanges: (cursor: string) => void): () => void {\n this.#subscribers.add(onChanges);\n void this.#connect();\n return () => {\n this.#subscribers.delete(onChanges);\n };\n }\n\n close(): void {\n this.#socket?.close();\n this.#socket = null;\n for (const pending of this.#pending.values()) {\n clearTimeout(pending.timeout);\n pending.reject(createSyncError(\"TRANSPORT_ERROR\", \"WebSocket transport closed.\"));\n }\n this.#pending.clear();\n }\n\n async #request(payload: Record<string, unknown>): Promise<ServerMessage> {\n const socket = await this.#connect();\n const id = crypto.randomUUID();\n const message = JSON.stringify({ id, ...payload });\n\n return await new Promise<ServerMessage>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.#pending.delete(id);\n reject(createSyncError(\"NETWORK_UNAVAILABLE\", \"WebSocket sync request timed out.\"));\n }, this.#requestTimeoutMs);\n\n this.#pending.set(id, { resolve, reject, timeout });\n socket.send(message);\n });\n }\n\n async #connect(): Promise<WebSocket> {\n if (this.#socket?.readyState === this.#WebSocket.OPEN) {\n return this.#socket;\n }\n\n const socket = new this.#WebSocket(this.#url);\n this.#socket = socket;\n socket.addEventListener(\"message\", (event) => this.#handleMessage(event));\n\n if (socket.readyState === this.#WebSocket.OPEN) {\n return socket;\n }\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const handleOpen = (): void => {\n cleanup();\n resolve(socket);\n };\n const handleError = (): void => {\n cleanup();\n reject(createSyncError(\"NETWORK_UNAVAILABLE\", \"WebSocket connection failed.\"));\n };\n const cleanup = (): void => {\n socket.removeEventListener(\"open\", handleOpen);\n socket.removeEventListener(\"error\", handleError);\n };\n\n socket.addEventListener(\"open\", handleOpen);\n socket.addEventListener(\"error\", handleError);\n });\n }\n\n #handleMessage(event: Event): void {\n const rawData = \"data\" in event ? event.data : undefined;\n if (typeof rawData !== \"string\") {\n return;\n }\n\n const message = JSON.parse(rawData) as ServerMessage;\n if (message.type === \"changes\") {\n for (const subscriber of this.#subscribers) {\n subscriber(message.cursor);\n }\n return;\n }\n\n const pending = this.#pending.get(message.id);\n if (pending === undefined) {\n return;\n }\n\n clearTimeout(pending.timeout);\n this.#pending.delete(message.id);\n\n if (\"error\" in message && message.error !== undefined) {\n pending.reject(\n createSyncError(\n \"TRANSPORT_ERROR\",\n message.error.message ?? \"WebSocket sync request failed.\",\n message.error,\n ),\n );\n return;\n }\n\n pending.resolve(message);\n }\n}\n"],"mappings":"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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,23 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_utils_index = require("./utils/index.cjs");
3
+ const require_core_index = require("./core/index.cjs");
4
+ const require_http_index = require("./http/index.cjs");
5
+ const require_sql_index = require("./sql/index.cjs");
6
+ exports.DefaultSyncScheduler = require_core_index.DefaultSyncScheduler;
7
+ exports.DexieDatabaseAdapter = require_sql_index.DexieDatabaseAdapter;
8
+ exports.ExponentialBackoffPolicy = require_utils_index.ExponentialBackoffPolicy;
9
+ exports.FetchSyncTransport = require_http_index.FetchSyncTransport;
10
+ exports.HLCClock = require_utils_index.HLCClock;
11
+ exports.LWWResolver = require_core_index.LWWResolver;
12
+ exports.MemoryDatabaseAdapter = require_sql_index.MemoryDatabaseAdapter;
13
+ exports.SyncEngine = require_core_index.SyncEngine;
14
+ exports.TypedSyncEventBus = require_core_index.TypedSyncEventBus;
15
+ exports.WebSocketSyncTransport = require_http_index.WebSocketSyncTransport;
16
+ exports.compareHLC = require_utils_index.compareHLC;
17
+ exports.createChangeId = require_utils_index.createChangeId;
18
+ exports.createDeviceId = require_utils_index.createDeviceId;
19
+ exports.createSyncError = require_core_index.createSyncError;
20
+ exports.isSyncError = require_core_index.isSyncError;
21
+ exports.normalizeSyncError = require_core_index.normalizeSyncError;
22
+ exports.retryOperation = require_utils_index.retryOperation;
23
+ exports.sleep = require_utils_index.sleep;
@@ -0,0 +1,56 @@
1
+ //#region src/utils/hlc.d.ts
2
+ type HLCTimestamp = string;
3
+ declare class HLCClock {
4
+ #private;
5
+ constructor(nodeId: string, now?: () => number);
6
+ now(): HLCTimestamp;
7
+ merge(remoteTimestamp: HLCTimestamp): HLCTimestamp;
8
+ }
9
+ declare function compareHLC(left: HLCTimestamp, right: HLCTimestamp): number;
10
+ //#endregion
11
+ //#region src/utils/id.d.ts
12
+ declare function createDeviceId(): string;
13
+ declare function createChangeId(): string;
14
+ //#endregion
15
+ //#region src/utils/retry.d.ts
16
+ interface RetryContext {
17
+ readonly attempt: number;
18
+ readonly lastError: {
19
+ readonly code: string;
20
+ readonly message: string;
21
+ readonly occurredAt: number;
22
+ };
23
+ readonly firstFailedAt: number;
24
+ }
25
+ type RetryDecision = {
26
+ readonly action: "retry";
27
+ readonly delayMs: number;
28
+ } | {
29
+ readonly action: "abort";
30
+ };
31
+ interface RetryPolicy {
32
+ decide(context: RetryContext): RetryDecision;
33
+ }
34
+ interface RetryOperationOptions {
35
+ readonly policy: RetryPolicy;
36
+ readonly normalizeError: (error: unknown) => RetryContext["lastError"];
37
+ readonly sleep?: (delayMs: number) => Promise<void>;
38
+ }
39
+ interface ExponentialBackoffOptions {
40
+ readonly maxAttempts?: number;
41
+ readonly baseDelayMs?: number;
42
+ readonly maxDelayMs?: number;
43
+ readonly retryableCodes?: ReadonlySet<string>;
44
+ }
45
+ declare class ExponentialBackoffPolicy implements RetryPolicy {
46
+ #private;
47
+ constructor(options?: ExponentialBackoffOptions);
48
+ decide(context: RetryContext): RetryDecision;
49
+ }
50
+ declare function retryOperation<T>(operation: () => Promise<T>, options: RetryOperationOptions): Promise<T>;
51
+ //#endregion
52
+ //#region src/utils/sleep.d.ts
53
+ declare function sleep(delayMs: number): Promise<void>;
54
+ //#endregion
55
+ export { RetryDecision as a, retryOperation as c, HLCClock as d, HLCTimestamp as f, RetryContext as i, createChangeId as l, ExponentialBackoffOptions as n, RetryOperationOptions as o, compareHLC as p, ExponentialBackoffPolicy as r, RetryPolicy as s, sleep as t, createDeviceId as u };
56
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/utils/hlc.ts","../src/utils/id.ts","../src/utils/retry.ts","../src/utils/sleep.ts"],"mappings":";KAAY,YAAA;AAAA,cAQC,QAAA;EAAA;cAMC,MAAA,UAAgB,GAAA;EAS5B,GAAA,IAAO,YAAA;EAiBP,KAAA,CAAM,eAAA,EAAiB,YAAA,GAAe,YAAA;AAAA;AAAA,iBAyBxB,UAAA,CAAW,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,YAAY;;;iBCjElD,cAAA;AAAA,iBAIA,cAAA;;;UCJC,YAAA;EAAA,SACN,OAAA;EAAA,SACA,SAAA;IAAA,SACE,IAAA;IAAA,SACA,OAAA;IAAA,SACA,UAAA;EAAA;EAAA,SAEF,aAAA;AAAA;AAAA,KAGC,aAAA;EAAA,SACG,MAAA;EAAA,SAA0B,OAAA;AAAA;EAAA,SAC1B,MAAA;AAAA;AAAA,UAEE,WAAA;EACf,MAAA,CAAO,OAAA,EAAS,YAAA,GAAe,aAAa;AAAA;AAAA,UAG7B,qBAAA;EAAA,SACN,MAAA,EAAQ,WAAA;EAAA,SACR,cAAA,GAAiB,KAAA,cAAmB,YAAA;EAAA,SACpC,KAAA,IAAS,OAAA,aAAoB,OAAA;AAAA;AAAA,UAGvB,yBAAA;EAAA,SACN,WAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA;EAAA,SACA,cAAA,GAAiB,WAAW;AAAA;AAAA,cAG1B,wBAAA,YAAoC,WAAA;EAAA;cAMnC,OAAA,GAAS,yBAAA;EASrB,MAAA,CAAO,OAAA,EAAS,YAAA,GAAe,aAAA;AAAA;AAAA,iBAkBX,cAAA,IACpB,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,CAAA;;;iBCnEK,KAAA,CAAM,OAAA,WAAkB,OAAO"}
@@ -0,0 +1,56 @@
1
+ //#region src/utils/hlc.d.ts
2
+ type HLCTimestamp = string;
3
+ declare class HLCClock {
4
+ #private;
5
+ constructor(nodeId: string, now?: () => number);
6
+ now(): HLCTimestamp;
7
+ merge(remoteTimestamp: HLCTimestamp): HLCTimestamp;
8
+ }
9
+ declare function compareHLC(left: HLCTimestamp, right: HLCTimestamp): number;
10
+ //#endregion
11
+ //#region src/utils/id.d.ts
12
+ declare function createDeviceId(): string;
13
+ declare function createChangeId(): string;
14
+ //#endregion
15
+ //#region src/utils/retry.d.ts
16
+ interface RetryContext {
17
+ readonly attempt: number;
18
+ readonly lastError: {
19
+ readonly code: string;
20
+ readonly message: string;
21
+ readonly occurredAt: number;
22
+ };
23
+ readonly firstFailedAt: number;
24
+ }
25
+ type RetryDecision = {
26
+ readonly action: "retry";
27
+ readonly delayMs: number;
28
+ } | {
29
+ readonly action: "abort";
30
+ };
31
+ interface RetryPolicy {
32
+ decide(context: RetryContext): RetryDecision;
33
+ }
34
+ interface RetryOperationOptions {
35
+ readonly policy: RetryPolicy;
36
+ readonly normalizeError: (error: unknown) => RetryContext["lastError"];
37
+ readonly sleep?: (delayMs: number) => Promise<void>;
38
+ }
39
+ interface ExponentialBackoffOptions {
40
+ readonly maxAttempts?: number;
41
+ readonly baseDelayMs?: number;
42
+ readonly maxDelayMs?: number;
43
+ readonly retryableCodes?: ReadonlySet<string>;
44
+ }
45
+ declare class ExponentialBackoffPolicy implements RetryPolicy {
46
+ #private;
47
+ constructor(options?: ExponentialBackoffOptions);
48
+ decide(context: RetryContext): RetryDecision;
49
+ }
50
+ declare function retryOperation<T>(operation: () => Promise<T>, options: RetryOperationOptions): Promise<T>;
51
+ //#endregion
52
+ //#region src/utils/sleep.d.ts
53
+ declare function sleep(delayMs: number): Promise<void>;
54
+ //#endregion
55
+ export { RetryDecision as a, retryOperation as c, HLCClock as d, HLCTimestamp as f, RetryContext as i, createChangeId as l, ExponentialBackoffOptions as n, RetryOperationOptions as o, compareHLC as p, ExponentialBackoffPolicy as r, RetryPolicy as s, sleep as t, createDeviceId as u };
56
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/utils/hlc.ts","../src/utils/id.ts","../src/utils/retry.ts","../src/utils/sleep.ts"],"mappings":";KAAY,YAAA;AAAA,cAQC,QAAA;EAAA;cAMC,MAAA,UAAgB,GAAA;EAS5B,GAAA,IAAO,YAAA;EAiBP,KAAA,CAAM,eAAA,EAAiB,YAAA,GAAe,YAAA;AAAA;AAAA,iBAyBxB,UAAA,CAAW,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,YAAY;;;iBCjElD,cAAA;AAAA,iBAIA,cAAA;;;UCJC,YAAA;EAAA,SACN,OAAA;EAAA,SACA,SAAA;IAAA,SACE,IAAA;IAAA,SACA,OAAA;IAAA,SACA,UAAA;EAAA;EAAA,SAEF,aAAA;AAAA;AAAA,KAGC,aAAA;EAAA,SACG,MAAA;EAAA,SAA0B,OAAA;AAAA;EAAA,SAC1B,MAAA;AAAA;AAAA,UAEE,WAAA;EACf,MAAA,CAAO,OAAA,EAAS,YAAA,GAAe,aAAa;AAAA;AAAA,UAG7B,qBAAA;EAAA,SACN,MAAA,EAAQ,WAAA;EAAA,SACR,cAAA,GAAiB,KAAA,cAAmB,YAAA;EAAA,SACpC,KAAA,IAAS,OAAA,aAAoB,OAAA;AAAA;AAAA,UAGvB,yBAAA;EAAA,SACN,WAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA;EAAA,SACA,cAAA,GAAiB,WAAW;AAAA;AAAA,cAG1B,wBAAA,YAAoC,WAAA;EAAA;cAMnC,OAAA,GAAS,yBAAA;EASrB,MAAA,CAAO,OAAA,EAAS,YAAA,GAAe,aAAA;AAAA;AAAA,iBAkBX,cAAA,IACpB,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,CAAA;;;iBCnEK,KAAA,CAAM,OAAA,WAAkB,OAAO"}
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { ExponentialBackoffPolicy, HLCClock, compareHLC, createChangeId, createDeviceId, retryOperation, sleep } from "./utils/index.mjs";
2
+ import { DefaultSyncScheduler, LWWResolver, SyncEngine, TypedSyncEventBus, createSyncError, isSyncError, normalizeSyncError } from "./core/index.mjs";
3
+ import { FetchSyncTransport, WebSocketSyncTransport } from "./http/index.mjs";
4
+ import { DexieDatabaseAdapter, MemoryDatabaseAdapter } from "./sql/index.mjs";
5
+ export { DefaultSyncScheduler, DexieDatabaseAdapter, ExponentialBackoffPolicy, FetchSyncTransport, HLCClock, LWWResolver, MemoryDatabaseAdapter, SyncEngine, TypedSyncEventBus, WebSocketSyncTransport, compareHLC, createChangeId, createDeviceId, createSyncError, isSyncError, normalizeSyncError, retryOperation, sleep };
@@ -0,0 +1,168 @@
1
+ import { f as HLCTimestamp, s as RetryPolicy } from "./index.cjs";
2
+
3
+ //#region src/core/types.d.ts
4
+ type DeviceId = string;
5
+ type ChangeAction = "insert" | "update" | "delete";
6
+ interface Change<TPayload = unknown> {
7
+ readonly id: string;
8
+ readonly table: string;
9
+ readonly entityId: string;
10
+ readonly action: ChangeAction;
11
+ readonly payload: TPayload;
12
+ readonly version: HLCTimestamp;
13
+ readonly deviceId: DeviceId;
14
+ readonly createdAt: number;
15
+ }
16
+ interface SyncCursor {
17
+ readonly value: string;
18
+ readonly lastSyncedAt: number;
19
+ }
20
+ interface DatabaseAdapter {
21
+ runInTransaction<T>(workload: () => Promise<T>): Promise<T>;
22
+ getLocalCursor(): Promise<SyncCursor | null>;
23
+ saveLocalCursor(cursor: SyncCursor): Promise<void>;
24
+ getPendingChanges(limit?: number): Promise<Change[]>;
25
+ markAsSynced(changeIds: string[]): Promise<void>;
26
+ applyRemoteChanges(changes: Change[]): Promise<void>;
27
+ clearTombstones(beforeTimestamp: number): Promise<number>;
28
+ }
29
+ interface PushPayload {
30
+ readonly changes: ReadonlyArray<Change>;
31
+ readonly deviceId: DeviceId;
32
+ }
33
+ interface PullResult {
34
+ readonly cursor: SyncCursor;
35
+ readonly changes: ReadonlyArray<Change>;
36
+ readonly hasMore: boolean;
37
+ }
38
+ interface SyncTransport {
39
+ readonly type: "http" | "websocket" | "webrtc" | "grpc";
40
+ push(changes: PushPayload): Promise<void>;
41
+ pull(cursor: string | null, limit?: number): Promise<PullResult>;
42
+ subscribe?: (onChanges: (cursor: string) => void) => () => void;
43
+ }
44
+ type SyncStatus = "idle" | "uploading" | "downloading" | "applying" | "error";
45
+ type SyncErrorCode = "NETWORK_UNAVAILABLE" | "TRANSPORT_ERROR" | "AUTH_FAILED" | "SERVER_ERROR" | "CURSOR_EXPIRED" | "CONFLICT_UNRESOLVABLE" | "DB_WRITE_FAILED" | "SERIALIZATION_ERROR" | "MAX_RETRY_EXCEEDED" | "UNKNOWN";
46
+ interface SyncError {
47
+ readonly code: SyncErrorCode;
48
+ readonly message: string;
49
+ readonly cause?: unknown;
50
+ readonly occurredAt: number;
51
+ }
52
+ interface SyncResult {
53
+ readonly success: boolean;
54
+ readonly uploadedCount: number;
55
+ readonly downloadedCount: number;
56
+ readonly durationMs: number;
57
+ readonly error?: SyncError;
58
+ }
59
+ //#endregion
60
+ //#region src/core/conflict.d.ts
61
+ interface ConflictContext {
62
+ readonly local: Change;
63
+ readonly remote: Change;
64
+ readonly deviceId: DeviceId;
65
+ }
66
+ interface ConflictResolution {
67
+ readonly winner: Change;
68
+ readonly strategy: string;
69
+ }
70
+ interface ConflictResolver {
71
+ resolve(context: ConflictContext): ConflictResolution;
72
+ }
73
+ declare class LWWResolver implements ConflictResolver {
74
+ resolve(context: ConflictContext): ConflictResolution;
75
+ }
76
+ //#endregion
77
+ //#region src/core/event-bus.d.ts
78
+ interface SyncEventMap {
79
+ readonly syncStart: void;
80
+ readonly syncFinish: SyncResult;
81
+ readonly statusChange: SyncStatus;
82
+ readonly uploadComplete: {
83
+ readonly uploadedCount: number;
84
+ };
85
+ readonly downloadComplete: {
86
+ readonly downloadedCount: number;
87
+ };
88
+ readonly conflictResolved: ConflictResolution;
89
+ readonly error: SyncError;
90
+ readonly deadLetter: {
91
+ readonly changes: ReadonlyArray<Change>;
92
+ readonly error: SyncError;
93
+ };
94
+ }
95
+ type SyncEventListener<K extends keyof SyncEventMap> = SyncEventMap[K] extends void ? () => void : (payload: SyncEventMap[K]) => void;
96
+ interface SyncEventBus {
97
+ on<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): () => void;
98
+ once<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void;
99
+ off<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void;
100
+ }
101
+ declare class TypedSyncEventBus implements SyncEventBus {
102
+ #private;
103
+ on<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): () => void;
104
+ once<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void;
105
+ off<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void;
106
+ emit<K extends keyof SyncEventMap>(event: K, ...args: SyncEventMap[K] extends void ? [] : [SyncEventMap[K]]): void;
107
+ }
108
+ //#endregion
109
+ //#region src/core/engine.d.ts
110
+ interface SyncEngineOptions {
111
+ readonly database: DatabaseAdapter;
112
+ readonly transport: SyncTransport;
113
+ readonly resolver?: ConflictResolver;
114
+ readonly deviceId: DeviceId;
115
+ readonly batchSize?: number;
116
+ }
117
+ declare class SyncEngine {
118
+ #private;
119
+ readonly resolver: ConflictResolver;
120
+ readonly events: TypedSyncEventBus;
121
+ constructor(options: SyncEngineOptions);
122
+ get status(): SyncStatus;
123
+ sync(): Promise<SyncResult>;
124
+ dispose(): Promise<void>;
125
+ }
126
+ //#endregion
127
+ //#region src/core/errors.d.ts
128
+ declare function createSyncError(code: SyncErrorCode, message: string, cause?: unknown): SyncError;
129
+ declare function isSyncError(error: unknown): error is SyncError;
130
+ declare function normalizeSyncError(error: unknown): SyncError;
131
+ //#endregion
132
+ //#region src/core/scheduler.d.ts
133
+ interface SyncSchedulerEngine {
134
+ sync(): Promise<SyncResult>;
135
+ }
136
+ interface SyncSchedulerDatabase {
137
+ clearTombstones(beforeTimestamp: number): Promise<number>;
138
+ }
139
+ interface SchedulerOptions {
140
+ readonly intervalMs?: number;
141
+ readonly retryPolicy?: RetryPolicy;
142
+ readonly syncOnNetworkRecover?: boolean;
143
+ readonly syncOnForeground?: boolean;
144
+ readonly tombstoneCleanupIntervalMs?: number;
145
+ readonly tombstoneMaxAgeMs?: number;
146
+ }
147
+ interface DefaultSyncSchedulerOptions {
148
+ readonly engine: SyncSchedulerEngine;
149
+ readonly database?: SyncSchedulerDatabase | Pick<DatabaseAdapter, "clearTombstones">;
150
+ readonly retryPolicy?: RetryPolicy;
151
+ readonly window?: Pick<Window, "addEventListener" | "removeEventListener">;
152
+ readonly document?: Pick<Document, "addEventListener" | "removeEventListener" | "visibilityState">;
153
+ }
154
+ interface SyncScheduler {
155
+ start(options?: SchedulerOptions): void;
156
+ stop(): void;
157
+ triggerSync(): Promise<SyncResult>;
158
+ }
159
+ declare class DefaultSyncScheduler implements SyncScheduler {
160
+ #private;
161
+ constructor(options: DefaultSyncSchedulerOptions);
162
+ start(options?: SchedulerOptions): void;
163
+ stop(): void;
164
+ triggerSync(): Promise<SyncResult>;
165
+ }
166
+ //#endregion
167
+ export { SyncStatus as A, DeviceId as C, SyncError as D, SyncCursor as E, SyncErrorCode as O, DatabaseAdapter as S, PushPayload as T, ConflictResolution as _, SyncSchedulerDatabase as a, Change as b, isSyncError as c, SyncEngineOptions as d, SyncEventBus as f, ConflictContext as g, TypedSyncEventBus as h, SyncScheduler as i, SyncTransport as j, SyncResult as k, normalizeSyncError as l, SyncEventMap as m, DefaultSyncSchedulerOptions as n, SyncSchedulerEngine as o, SyncEventListener as p, SchedulerOptions as r, createSyncError as s, DefaultSyncScheduler as t, SyncEngine as u, ConflictResolver as v, PullResult as w, ChangeAction as x, LWWResolver as y };
168
+ //# sourceMappingURL=index2.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index2.d.cts","names":[],"sources":["../src/core/types.ts","../src/core/conflict.ts","../src/core/event-bus.ts","../src/core/engine.ts","../src/core/errors.ts","../src/core/scheduler.ts"],"mappings":";;;KAEY,QAAA;AAAA,KACA,YAAA;AAAA,UAEK,MAAA;EAAA,SACN,EAAA;EAAA,SACA,KAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA,EAAQ,YAAA;EAAA,SACR,OAAA,EAAS,QAAA;EAAA,SACT,OAAA,EAAS,YAAA;EAAA,SACT,QAAA,EAAU,QAAA;EAAA,SACV,SAAA;AAAA;AAAA,UAGM,UAAA;EAAA,SACN,KAAA;EAAA,SACA,YAAY;AAAA;AAAA,UAGN,eAAA;EACf,gBAAA,IAAoB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EACzD,cAAA,IAAkB,OAAA,CAAQ,UAAA;EAC1B,eAAA,CAAgB,MAAA,EAAQ,UAAA,GAAa,OAAA;EACrC,iBAAA,CAAkB,KAAA,YAAiB,OAAA,CAAQ,MAAA;EAC3C,YAAA,CAAa,SAAA,aAAsB,OAAA;EACnC,kBAAA,CAAmB,OAAA,EAAS,MAAA,KAAW,OAAA;EACvC,eAAA,CAAgB,eAAA,WAA0B,OAAA;AAAA;AAAA,UAG3B,WAAA;EAAA,SACN,OAAA,EAAS,aAAA,CAAc,MAAA;EAAA,SACvB,QAAA,EAAU,QAAA;AAAA;AAAA,UAGJ,UAAA;EAAA,SACN,MAAA,EAAQ,UAAA;EAAA,SACR,OAAA,EAAS,aAAA,CAAc,MAAA;EAAA,SACvB,OAAA;AAAA;AAAA,UAGM,aAAA;EAAA,SACN,IAAA;EACT,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA;EAC5B,IAAA,CAAK,MAAA,iBAAuB,KAAA,YAAiB,OAAA,CAAQ,UAAA;EACrD,SAAA,IAAa,SAAA,GAAY,MAAA;AAAA;AAAA,KAGf,UAAA;AAAA,KAEA,aAAA;AAAA,UAYK,SAAA;EAAA,SACN,IAAA,EAAM,aAAa;EAAA,SACnB,OAAA;EAAA,SACA,KAAA;EAAA,SACA,UAAA;AAAA;AAAA,UAGM,UAAA;EAAA,SACN,OAAA;EAAA,SACA,aAAA;EAAA,SACA,eAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA,GAAQ,SAAS;AAAA;;;UCxEX,eAAA;EAAA,SACN,KAAA,EAAO,MAAA;EAAA,SACP,MAAA,EAAQ,MAAA;EAAA,SACR,QAAA,EAAU,QAAA;AAAA;AAAA,UAGJ,kBAAA;EAAA,SACN,MAAA,EAAQ,MAAM;EAAA,SACd,QAAA;AAAA;AAAA,UAGM,gBAAA;EACf,OAAA,CAAQ,OAAA,EAAS,eAAA,GAAkB,kBAAkB;AAAA;AAAA,cAG1C,WAAA,YAAuB,gBAAA;EAClC,OAAA,CAAQ,OAAA,EAAS,eAAA,GAAkB,kBAAA;AAAA;;;UChBpB,YAAA;EAAA,SACN,SAAA;EAAA,SACA,UAAA,EAAY,UAAA;EAAA,SACZ,YAAA,EAAc,UAAA;EAAA,SACd,cAAA;IAAA,SAA2B,aAAA;EAAA;EAAA,SAC3B,gBAAA;IAAA,SAA6B,eAAA;EAAA;EAAA,SAC7B,gBAAA,EAAkB,kBAAA;EAAA,SAClB,KAAA,EAAO,SAAA;EAAA,SACP,UAAA;IAAA,SAAuB,OAAA,EAAS,aAAA,CAAc,MAAA;IAAA,SAAkB,KAAA,EAAO,SAAA;EAAA;AAAA;AAAA,KAGtE,iBAAA,iBAAkC,YAAA,IAAgB,YAAA,CAAa,CAAA,+BAEtE,OAAA,EAAS,YAAA,CAAa,CAAA;AAAA,UAEV,YAAA;EACf,EAAA,iBAAmB,YAAA,EAAc,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,iBAAA,CAAkB,CAAA;EACvE,IAAA,iBAAqB,YAAA,EAAc,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,iBAAA,CAAkB,CAAA;EACzE,GAAA,iBAAoB,YAAA,EAAc,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,iBAAA,CAAkB,CAAA;AAAA;AAAA,cAG7D,iBAAA,YAA6B,YAAA;EAAA;EAGxC,EAAA,iBAAmB,YAAA,EAAc,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,iBAAA,CAAkB,CAAA;EASvE,IAAA,iBAAqB,YAAA,EAAc,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,iBAAA,CAAkB,CAAA;EAazE,GAAA,iBAAoB,YAAA,EAAc,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,iBAAA,CAAkB,CAAA;EAIxE,IAAA,iBAAqB,YAAA,EACnB,KAAA,EAAO,CAAA,KACJ,IAAA,EAAM,YAAA,CAAa,CAAA,uBAAwB,YAAA,CAAa,CAAA;AAAA;;;UClD9C,iBAAA;EAAA,SACN,QAAA,EAAU,eAAA;EAAA,SACV,SAAA,EAAW,aAAA;EAAA,SACX,QAAA,GAAW,gBAAA;EAAA,SACX,QAAA,EAAU,QAAA;EAAA,SACV,SAAA;AAAA;AAAA,cAGE,UAAA;EAAA;WASF,QAAA,EAAU,gBAAA;EAAA,SACV,MAAA,EAAM,iBAAA;cAEH,OAAA,EAAS,iBAAA;EAAA,IAWjB,MAAA,IAAU,UAAA;EAId,IAAA,IAAQ,OAAA,CAAQ,UAAA;EAYV,OAAA,IAAW,OAAA;AAAA;;;iBClDH,eAAA,CAAgB,IAAA,EAAM,aAAA,EAAe,OAAA,UAAiB,KAAA,aAAkB,SAAS;AAAA,iBASjF,WAAA,CAAY,KAAA,YAAiB,KAAA,IAAS,SAAS;AAAA,iBAU/C,kBAAA,CAAmB,KAAA,YAAiB,SAAS;;;UClB5C,mBAAA;EACf,IAAA,IAAQ,OAAO,CAAC,UAAA;AAAA;AAAA,UAGD,qBAAA;EACf,eAAA,CAAgB,eAAA,WAA0B,OAAO;AAAA;AAAA,UAGlC,gBAAA;EAAA,SACN,UAAA;EAAA,SACA,WAAA,GAAc,WAAW;EAAA,SACzB,oBAAA;EAAA,SACA,gBAAA;EAAA,SACA,0BAAA;EAAA,SACA,iBAAA;AAAA;AAAA,UAGM,2BAAA;EAAA,SACN,MAAA,EAAQ,mBAAA;EAAA,SACR,QAAA,GAAW,qBAAA,GAAwB,IAAA,CAAK,eAAA;EAAA,SACxC,WAAA,GAAc,WAAA;EAAA,SACd,MAAA,GAAS,IAAA,CAAK,MAAA;EAAA,SACd,QAAA,GAAW,IAAA,CAClB,QAAA;AAAA;AAAA,UAKa,aAAA;EACf,KAAA,CAAM,OAAA,GAAU,gBAAA;EAChB,IAAA;EACA,WAAA,IAAe,OAAA,CAAQ,UAAA;AAAA;AAAA,cAOZ,oBAAA,YAAgC,aAAA;EAAA;cAyB/B,OAAA,EAAS,2BAAA;EASrB,KAAA,CAAM,OAAA,GAAS,gBAAA;EA6Bf,IAAA;EAoBM,WAAA,IAAe,OAAA,CAAQ,UAAA;AAAA"}