@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,272 @@
1
+ import { ExponentialBackoffPolicy, compareHLC } from "../utils/index.mjs";
2
+ //#region src/core/conflict.ts
3
+ var LWWResolver = class {
4
+ resolve(context) {
5
+ const { local, remote } = context;
6
+ if (local.action === "delete" && remote.action !== "delete") return {
7
+ winner: local,
8
+ strategy: "Tombstone wins over non-delete change"
9
+ };
10
+ if (remote.action === "delete" && local.action !== "delete") return {
11
+ winner: remote,
12
+ strategy: "Tombstone wins over non-delete change"
13
+ };
14
+ const order = compareHLC(remote.version, local.version);
15
+ if (order > 0) return {
16
+ winner: remote,
17
+ strategy: "LWW: remote version newer"
18
+ };
19
+ if (order < 0) return {
20
+ winner: local,
21
+ strategy: "LWW: local version newer"
22
+ };
23
+ return {
24
+ winner: remote,
25
+ strategy: "LWW: equal version, remote wins"
26
+ };
27
+ }
28
+ };
29
+ //#endregion
30
+ //#region src/core/errors.ts
31
+ function createSyncError(code, message, cause) {
32
+ return {
33
+ code,
34
+ message,
35
+ cause,
36
+ occurredAt: Date.now()
37
+ };
38
+ }
39
+ function isSyncError(error) {
40
+ return typeof error === "object" && error !== null && "code" in error && "message" in error && "occurredAt" in error;
41
+ }
42
+ function normalizeSyncError(error) {
43
+ if (isSyncError(error)) return error;
44
+ if (error instanceof Error) return createSyncError("UNKNOWN", error.message, error);
45
+ return createSyncError("UNKNOWN", "Unknown sync error.", error);
46
+ }
47
+ //#endregion
48
+ //#region src/core/event-bus.ts
49
+ var TypedSyncEventBus = class {
50
+ #listeners = /* @__PURE__ */ new Map();
51
+ on(event, listener) {
52
+ const listeners = this.#listeners.get(event) ?? /* @__PURE__ */ new Set();
53
+ listeners.add(listener);
54
+ this.#listeners.set(event, listeners);
55
+ return () => this.off(event, listener);
56
+ }
57
+ once(event, listener) {
58
+ const wrapped = ((payload) => {
59
+ this.off(event, wrapped);
60
+ if (payload === void 0) listener();
61
+ else listener(payload);
62
+ });
63
+ this.on(event, wrapped);
64
+ }
65
+ off(event, listener) {
66
+ this.#listeners.get(event)?.delete(listener);
67
+ }
68
+ emit(event, ...args) {
69
+ const listeners = [...this.#listeners.get(event) ?? []];
70
+ for (const listener of listeners) if (args.length === 0) listener();
71
+ else listener(args[0]);
72
+ }
73
+ };
74
+ //#endregion
75
+ //#region src/core/engine.ts
76
+ var SyncEngine = class {
77
+ #database;
78
+ #transport;
79
+ #deviceId;
80
+ #batchSize;
81
+ #unsubscribe;
82
+ #status = "idle";
83
+ #inFlight = null;
84
+ resolver;
85
+ events = new TypedSyncEventBus();
86
+ constructor(options) {
87
+ this.#database = options.database;
88
+ this.#transport = options.transport;
89
+ this.#deviceId = options.deviceId;
90
+ this.#batchSize = options.batchSize ?? 100;
91
+ this.resolver = options.resolver ?? new LWWResolver();
92
+ this.#unsubscribe = this.#transport.subscribe?.(() => {
93
+ this.sync();
94
+ });
95
+ }
96
+ get status() {
97
+ return this.#status;
98
+ }
99
+ sync() {
100
+ if (this.#inFlight !== null) return this.#inFlight;
101
+ this.#inFlight = this.#syncOnce().finally(() => {
102
+ this.#inFlight = null;
103
+ });
104
+ return this.#inFlight;
105
+ }
106
+ async dispose() {
107
+ this.#unsubscribe?.();
108
+ }
109
+ async #syncOnce() {
110
+ const startedAt = Date.now();
111
+ let uploadedCount = 0;
112
+ let downloadedCount = 0;
113
+ this.events.emit("syncStart");
114
+ try {
115
+ this.#setStatus("uploading");
116
+ const pendingChanges = await this.#database.getPendingChanges(this.#batchSize);
117
+ if (pendingChanges.length > 0) {
118
+ await this.#transport.push({
119
+ changes: pendingChanges,
120
+ deviceId: this.#deviceId
121
+ });
122
+ await this.#database.markAsSynced(pendingChanges.map((change) => change.id));
123
+ uploadedCount = pendingChanges.length;
124
+ }
125
+ this.events.emit("uploadComplete", { uploadedCount });
126
+ let cursor = await this.#database.getLocalCursor();
127
+ let hasMore = true;
128
+ while (hasMore) {
129
+ this.#setStatus("downloading");
130
+ const pulled = await this.#transport.pull(cursor?.value ?? null, this.#batchSize);
131
+ this.events.emit("downloadComplete", { downloadedCount: pulled.changes.length });
132
+ this.#setStatus("applying");
133
+ await this.#database.runInTransaction(async () => {
134
+ await this.#database.applyRemoteChanges([...pulled.changes]);
135
+ await this.#database.saveLocalCursor(pulled.cursor);
136
+ });
137
+ downloadedCount += pulled.changes.length;
138
+ cursor = pulled.cursor;
139
+ hasMore = pulled.hasMore;
140
+ }
141
+ this.#setStatus("idle");
142
+ const result = {
143
+ success: true,
144
+ uploadedCount,
145
+ downloadedCount,
146
+ durationMs: Date.now() - startedAt
147
+ };
148
+ this.events.emit("syncFinish", result);
149
+ return result;
150
+ } catch (cause) {
151
+ const error = normalizeSyncError(cause);
152
+ const result = {
153
+ success: false,
154
+ uploadedCount,
155
+ downloadedCount,
156
+ durationMs: Date.now() - startedAt,
157
+ error
158
+ };
159
+ this.events.emit("error", error);
160
+ this.#setStatus("idle");
161
+ this.events.emit("syncFinish", result);
162
+ return result;
163
+ }
164
+ }
165
+ #setStatus(status) {
166
+ this.#status = status;
167
+ this.events.emit("statusChange", status);
168
+ }
169
+ };
170
+ //#endregion
171
+ //#region src/core/scheduler.ts
172
+ const DEFAULT_INTERVAL_MS = 3e4;
173
+ const DEFAULT_TOMBSTONE_CLEANUP_INTERVAL_MS = 10080 * 60 * 1e3;
174
+ const DEFAULT_TOMBSTONE_MAX_AGE_MS = 720 * 60 * 60 * 1e3;
175
+ var DefaultSyncScheduler = class {
176
+ #engine;
177
+ #database;
178
+ #defaultRetryPolicy;
179
+ #window;
180
+ #document;
181
+ #syncTimer = null;
182
+ #retryTimer = null;
183
+ #tombstoneTimer = null;
184
+ #inFlight = null;
185
+ #retryAttempt = 0;
186
+ #firstFailedAt = 0;
187
+ #retryPolicy;
188
+ #handleOnline = () => {
189
+ this.triggerSync();
190
+ };
191
+ #handleVisibilityChange = () => {
192
+ if (this.#document?.visibilityState === "visible") this.triggerSync();
193
+ };
194
+ constructor(options) {
195
+ this.#engine = options.engine;
196
+ this.#database = options.database;
197
+ this.#defaultRetryPolicy = options.retryPolicy ?? new ExponentialBackoffPolicy();
198
+ this.#retryPolicy = this.#defaultRetryPolicy;
199
+ this.#window = options.window ?? globalThis.window;
200
+ this.#document = options.document ?? globalThis.document;
201
+ }
202
+ start(options = {}) {
203
+ this.stop();
204
+ this.#retryPolicy = options.retryPolicy ?? this.#defaultRetryPolicy;
205
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
206
+ if (intervalMs > 0) this.#syncTimer = setInterval(() => {
207
+ this.triggerSync();
208
+ }, intervalMs);
209
+ if (options.syncOnNetworkRecover ?? true) this.#window?.addEventListener("online", this.#handleOnline);
210
+ if (options.syncOnForeground ?? true) this.#document?.addEventListener("visibilitychange", this.#handleVisibilityChange);
211
+ const tombstoneCleanupIntervalMs = options.tombstoneCleanupIntervalMs ?? DEFAULT_TOMBSTONE_CLEANUP_INTERVAL_MS;
212
+ if (this.#database !== void 0 && tombstoneCleanupIntervalMs > 0) {
213
+ const tombstoneMaxAgeMs = options.tombstoneMaxAgeMs ?? DEFAULT_TOMBSTONE_MAX_AGE_MS;
214
+ this.#tombstoneTimer = setInterval(() => {
215
+ this.#database?.clearTombstones(Date.now() - tombstoneMaxAgeMs);
216
+ }, tombstoneCleanupIntervalMs);
217
+ }
218
+ }
219
+ stop() {
220
+ if (this.#syncTimer !== null) {
221
+ clearInterval(this.#syncTimer);
222
+ this.#syncTimer = null;
223
+ }
224
+ if (this.#retryTimer !== null) {
225
+ clearTimeout(this.#retryTimer);
226
+ this.#retryTimer = null;
227
+ }
228
+ if (this.#tombstoneTimer !== null) {
229
+ clearInterval(this.#tombstoneTimer);
230
+ this.#tombstoneTimer = null;
231
+ }
232
+ this.#window?.removeEventListener("online", this.#handleOnline);
233
+ this.#document?.removeEventListener("visibilitychange", this.#handleVisibilityChange);
234
+ }
235
+ async triggerSync() {
236
+ if (this.#inFlight !== null) return await this.#inFlight;
237
+ this.#inFlight = this.#engine.sync().then((result) => {
238
+ this.#handleResult(result);
239
+ return result;
240
+ });
241
+ try {
242
+ return await this.#inFlight;
243
+ } finally {
244
+ this.#inFlight = null;
245
+ }
246
+ }
247
+ #handleResult(result) {
248
+ if (result.success) {
249
+ this.#retryAttempt = 0;
250
+ this.#firstFailedAt = 0;
251
+ return;
252
+ }
253
+ if (result.error === void 0) return;
254
+ this.#firstFailedAt = this.#firstFailedAt === 0 ? result.error.occurredAt : this.#firstFailedAt;
255
+ const decision = this.#retryPolicy.decide({
256
+ attempt: this.#retryAttempt,
257
+ firstFailedAt: this.#firstFailedAt,
258
+ lastError: result.error
259
+ });
260
+ this.#retryAttempt += 1;
261
+ if (decision.action === "abort") return;
262
+ if (this.#retryTimer !== null) clearTimeout(this.#retryTimer);
263
+ this.#retryTimer = setTimeout(() => {
264
+ this.#retryTimer = null;
265
+ this.triggerSync();
266
+ }, decision.delayMs);
267
+ }
268
+ };
269
+ //#endregion
270
+ export { DefaultSyncScheduler, LWWResolver, SyncEngine, TypedSyncEventBus, createSyncError, isSyncError, normalizeSyncError };
271
+
272
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["#listeners","#database","#transport","#deviceId","#batchSize","#unsubscribe","#status","#inFlight","#syncOnce","#setStatus","#engine","#database","#defaultRetryPolicy","#window","#document","#retryPolicy","#syncTimer","#handleOnline","#handleVisibilityChange","#tombstoneTimer","#retryTimer","#inFlight","#handleResult","#retryAttempt","#firstFailedAt"],"sources":["../../src/core/conflict.ts","../../src/core/errors.ts","../../src/core/event-bus.ts","../../src/core/engine.ts","../../src/core/scheduler.ts"],"sourcesContent":["import { compareHLC } from \"../utils\";\nimport type { Change, DeviceId } from \"./types\";\n\nexport interface ConflictContext {\n readonly local: Change;\n readonly remote: Change;\n readonly deviceId: DeviceId;\n}\n\nexport interface ConflictResolution {\n readonly winner: Change;\n readonly strategy: string;\n}\n\nexport interface ConflictResolver {\n resolve(context: ConflictContext): ConflictResolution;\n}\n\nexport class LWWResolver implements ConflictResolver {\n resolve(context: ConflictContext): ConflictResolution {\n const { local, remote } = context;\n\n if (local.action === \"delete\" && remote.action !== \"delete\") {\n return { winner: local, strategy: \"Tombstone wins over non-delete change\" };\n }\n\n if (remote.action === \"delete\" && local.action !== \"delete\") {\n return { winner: remote, strategy: \"Tombstone wins over non-delete change\" };\n }\n\n const order = compareHLC(remote.version, local.version);\n\n if (order > 0) {\n return { winner: remote, strategy: \"LWW: remote version newer\" };\n }\n\n if (order < 0) {\n return { winner: local, strategy: \"LWW: local version newer\" };\n }\n\n return { winner: remote, strategy: \"LWW: equal version, remote wins\" };\n }\n}\n","import type { SyncError, SyncErrorCode } from \"./types\";\n\nexport function createSyncError(code: SyncErrorCode, message: string, cause?: unknown): SyncError {\n return {\n code,\n message,\n cause,\n occurredAt: Date.now(),\n };\n}\n\nexport function isSyncError(error: unknown): error is SyncError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n \"message\" in error &&\n \"occurredAt\" in error\n );\n}\n\nexport function normalizeSyncError(error: unknown): SyncError {\n if (isSyncError(error)) {\n return error;\n }\n\n if (error instanceof Error) {\n return createSyncError(\"UNKNOWN\", error.message, error);\n }\n\n return createSyncError(\"UNKNOWN\", \"Unknown sync error.\", error);\n}\n","import type { Change, SyncError, SyncResult, SyncStatus } from \"./types\";\nimport type { ConflictResolution } from \"./conflict\";\n\nexport interface SyncEventMap {\n readonly syncStart: void;\n readonly syncFinish: SyncResult;\n readonly statusChange: SyncStatus;\n readonly uploadComplete: { readonly uploadedCount: number };\n readonly downloadComplete: { readonly downloadedCount: number };\n readonly conflictResolved: ConflictResolution;\n readonly error: SyncError;\n readonly deadLetter: { readonly changes: ReadonlyArray<Change>; readonly error: SyncError };\n}\n\nexport type SyncEventListener<K extends keyof SyncEventMap> = SyncEventMap[K] extends void\n ? () => void\n : (payload: SyncEventMap[K]) => void;\n\nexport interface SyncEventBus {\n on<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): () => void;\n once<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void;\n off<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void;\n}\n\nexport class TypedSyncEventBus implements SyncEventBus {\n readonly #listeners = new Map<keyof SyncEventMap, Set<SyncEventListener<keyof SyncEventMap>>>();\n\n on<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): () => void {\n const listeners =\n this.#listeners.get(event) ?? new Set<SyncEventListener<keyof SyncEventMap>>();\n listeners.add(listener as SyncEventListener<keyof SyncEventMap>);\n this.#listeners.set(event, listeners);\n\n return () => this.off(event, listener);\n }\n\n once<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void {\n const wrapped = ((payload?: SyncEventMap[K]) => {\n this.off(event, wrapped as SyncEventListener<K>);\n if (payload === undefined) {\n (listener as () => void)();\n } else {\n (listener as (value: SyncEventMap[K]) => void)(payload);\n }\n }) as SyncEventListener<K>;\n\n this.on(event, wrapped);\n }\n\n off<K extends keyof SyncEventMap>(event: K, listener: SyncEventListener<K>): void {\n this.#listeners.get(event)?.delete(listener as SyncEventListener<keyof SyncEventMap>);\n }\n\n emit<K extends keyof SyncEventMap>(\n event: K,\n ...args: SyncEventMap[K] extends void ? [] : [SyncEventMap[K]]\n ): void {\n const listeners = [...(this.#listeners.get(event) ?? [])] as Array<SyncEventListener<K>>;\n\n for (const listener of listeners) {\n if (args.length === 0) {\n (listener as () => void)();\n } else {\n (listener as (payload: SyncEventMap[K]) => void)(args[0] as SyncEventMap[K]);\n }\n }\n }\n}\n","import { LWWResolver, type ConflictResolver } from \"./conflict\";\nimport { normalizeSyncError } from \"./errors\";\nimport { TypedSyncEventBus } from \"./event-bus\";\nimport type { DatabaseAdapter, DeviceId, SyncResult, SyncStatus, SyncTransport } from \"./types\";\n\nexport interface SyncEngineOptions {\n readonly database: DatabaseAdapter;\n readonly transport: SyncTransport;\n readonly resolver?: ConflictResolver;\n readonly deviceId: DeviceId;\n readonly batchSize?: number;\n}\n\nexport class SyncEngine {\n readonly #database: DatabaseAdapter;\n readonly #transport: SyncTransport;\n readonly #deviceId: DeviceId;\n readonly #batchSize: number;\n readonly #unsubscribe?: () => void;\n #status: SyncStatus = \"idle\";\n #inFlight: Promise<SyncResult> | null = null;\n\n readonly resolver: ConflictResolver;\n readonly events = new TypedSyncEventBus();\n\n constructor(options: SyncEngineOptions) {\n this.#database = options.database;\n this.#transport = options.transport;\n this.#deviceId = options.deviceId;\n this.#batchSize = options.batchSize ?? 100;\n this.resolver = options.resolver ?? new LWWResolver();\n this.#unsubscribe = this.#transport.subscribe?.(() => {\n void this.sync();\n });\n }\n\n get status(): SyncStatus {\n return this.#status;\n }\n\n sync(): Promise<SyncResult> {\n if (this.#inFlight !== null) {\n return this.#inFlight;\n }\n\n this.#inFlight = this.#syncOnce().finally(() => {\n this.#inFlight = null;\n });\n\n return this.#inFlight;\n }\n\n async dispose(): Promise<void> {\n this.#unsubscribe?.();\n }\n\n async #syncOnce(): Promise<SyncResult> {\n const startedAt = Date.now();\n let uploadedCount = 0;\n let downloadedCount = 0;\n\n this.events.emit(\"syncStart\");\n\n try {\n this.#setStatus(\"uploading\");\n const pendingChanges = await this.#database.getPendingChanges(this.#batchSize);\n\n if (pendingChanges.length > 0) {\n await this.#transport.push({ changes: pendingChanges, deviceId: this.#deviceId });\n await this.#database.markAsSynced(pendingChanges.map((change) => change.id));\n uploadedCount = pendingChanges.length;\n }\n\n this.events.emit(\"uploadComplete\", { uploadedCount });\n\n let cursor = await this.#database.getLocalCursor();\n let hasMore = true;\n\n while (hasMore) {\n this.#setStatus(\"downloading\");\n // Cursor pagination is stateful; each pull depends on the cursor returned by the previous page.\n // oxlint-disable-next-line no-await-in-loop\n const pulled = await this.#transport.pull(cursor?.value ?? null, this.#batchSize);\n this.events.emit(\"downloadComplete\", { downloadedCount: pulled.changes.length });\n\n this.#setStatus(\"applying\");\n // oxlint-disable-next-line no-await-in-loop\n await this.#database.runInTransaction(async () => {\n await this.#database.applyRemoteChanges([...pulled.changes]);\n await this.#database.saveLocalCursor(pulled.cursor);\n });\n\n downloadedCount += pulled.changes.length;\n cursor = pulled.cursor;\n hasMore = pulled.hasMore;\n }\n\n this.#setStatus(\"idle\");\n\n const result: SyncResult = {\n success: true,\n uploadedCount,\n downloadedCount,\n durationMs: Date.now() - startedAt,\n };\n this.events.emit(\"syncFinish\", result);\n\n return result;\n } catch (cause) {\n const error = normalizeSyncError(cause);\n const result: SyncResult = {\n success: false,\n uploadedCount,\n downloadedCount,\n durationMs: Date.now() - startedAt,\n error,\n };\n\n this.events.emit(\"error\", error);\n this.#setStatus(\"idle\");\n this.events.emit(\"syncFinish\", result);\n\n return result;\n }\n }\n\n #setStatus(status: SyncStatus): void {\n this.#status = status;\n this.events.emit(\"statusChange\", status);\n }\n}\n","import { ExponentialBackoffPolicy, type RetryPolicy } from \"../utils\";\nimport type { DatabaseAdapter, SyncResult } from \"./types\";\n\nexport interface SyncSchedulerEngine {\n sync(): Promise<SyncResult>;\n}\n\nexport interface SyncSchedulerDatabase {\n clearTombstones(beforeTimestamp: number): Promise<number>;\n}\n\nexport interface SchedulerOptions {\n readonly intervalMs?: number;\n readonly retryPolicy?: RetryPolicy;\n readonly syncOnNetworkRecover?: boolean;\n readonly syncOnForeground?: boolean;\n readonly tombstoneCleanupIntervalMs?: number;\n readonly tombstoneMaxAgeMs?: number;\n}\n\nexport interface DefaultSyncSchedulerOptions {\n readonly engine: SyncSchedulerEngine;\n readonly database?: SyncSchedulerDatabase | Pick<DatabaseAdapter, \"clearTombstones\">;\n readonly retryPolicy?: RetryPolicy;\n readonly window?: Pick<Window, \"addEventListener\" | \"removeEventListener\">;\n readonly document?: Pick<\n Document,\n \"addEventListener\" | \"removeEventListener\" | \"visibilityState\"\n >;\n}\n\nexport interface SyncScheduler {\n start(options?: SchedulerOptions): void;\n stop(): void;\n triggerSync(): Promise<SyncResult>;\n}\n\nconst DEFAULT_INTERVAL_MS = 30_000;\nconst DEFAULT_TOMBSTONE_CLEANUP_INTERVAL_MS = 7 * 24 * 60 * 60 * 1_000;\nconst DEFAULT_TOMBSTONE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1_000;\n\nexport class DefaultSyncScheduler implements SyncScheduler {\n readonly #engine: SyncSchedulerEngine;\n readonly #database?: SyncSchedulerDatabase;\n readonly #defaultRetryPolicy: RetryPolicy;\n readonly #window?: Pick<Window, \"addEventListener\" | \"removeEventListener\">;\n readonly #document?: Pick<\n Document,\n \"addEventListener\" | \"removeEventListener\" | \"visibilityState\"\n >;\n #syncTimer: ReturnType<typeof setInterval> | null = null;\n #retryTimer: ReturnType<typeof setTimeout> | null = null;\n #tombstoneTimer: ReturnType<typeof setInterval> | null = null;\n #inFlight: Promise<SyncResult> | null = null;\n #retryAttempt = 0;\n #firstFailedAt = 0;\n #retryPolicy: RetryPolicy;\n #handleOnline = (): void => {\n void this.triggerSync();\n };\n #handleVisibilityChange = (): void => {\n if (this.#document?.visibilityState === \"visible\") {\n void this.triggerSync();\n }\n };\n\n constructor(options: DefaultSyncSchedulerOptions) {\n this.#engine = options.engine;\n this.#database = options.database;\n this.#defaultRetryPolicy = options.retryPolicy ?? new ExponentialBackoffPolicy();\n this.#retryPolicy = this.#defaultRetryPolicy;\n this.#window = options.window ?? globalThis.window;\n this.#document = options.document ?? globalThis.document;\n }\n\n start(options: SchedulerOptions = {}): void {\n this.stop();\n this.#retryPolicy = options.retryPolicy ?? this.#defaultRetryPolicy;\n\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n if (intervalMs > 0) {\n this.#syncTimer = setInterval(() => {\n void this.triggerSync();\n }, intervalMs);\n }\n\n if (options.syncOnNetworkRecover ?? true) {\n this.#window?.addEventListener(\"online\", this.#handleOnline);\n }\n\n if (options.syncOnForeground ?? true) {\n this.#document?.addEventListener(\"visibilitychange\", this.#handleVisibilityChange);\n }\n\n const tombstoneCleanupIntervalMs =\n options.tombstoneCleanupIntervalMs ?? DEFAULT_TOMBSTONE_CLEANUP_INTERVAL_MS;\n if (this.#database !== undefined && tombstoneCleanupIntervalMs > 0) {\n const tombstoneMaxAgeMs = options.tombstoneMaxAgeMs ?? DEFAULT_TOMBSTONE_MAX_AGE_MS;\n this.#tombstoneTimer = setInterval(() => {\n void this.#database?.clearTombstones(Date.now() - tombstoneMaxAgeMs);\n }, tombstoneCleanupIntervalMs);\n }\n }\n\n stop(): void {\n if (this.#syncTimer !== null) {\n clearInterval(this.#syncTimer);\n this.#syncTimer = null;\n }\n\n if (this.#retryTimer !== null) {\n clearTimeout(this.#retryTimer);\n this.#retryTimer = null;\n }\n\n if (this.#tombstoneTimer !== null) {\n clearInterval(this.#tombstoneTimer);\n this.#tombstoneTimer = null;\n }\n\n this.#window?.removeEventListener(\"online\", this.#handleOnline);\n this.#document?.removeEventListener(\"visibilitychange\", this.#handleVisibilityChange);\n }\n\n async triggerSync(): Promise<SyncResult> {\n if (this.#inFlight !== null) {\n return await this.#inFlight;\n }\n\n this.#inFlight = this.#engine.sync().then((result) => {\n this.#handleResult(result);\n return result;\n });\n\n try {\n return await this.#inFlight;\n } finally {\n this.#inFlight = null;\n }\n }\n\n #handleResult(result: SyncResult): void {\n if (result.success) {\n this.#retryAttempt = 0;\n this.#firstFailedAt = 0;\n return;\n }\n\n if (result.error === undefined) {\n return;\n }\n\n this.#firstFailedAt = this.#firstFailedAt === 0 ? result.error.occurredAt : this.#firstFailedAt;\n const decision = this.#retryPolicy.decide({\n attempt: this.#retryAttempt,\n firstFailedAt: this.#firstFailedAt,\n lastError: result.error,\n });\n\n this.#retryAttempt += 1;\n\n if (decision.action === \"abort\") {\n return;\n }\n\n if (this.#retryTimer !== null) {\n clearTimeout(this.#retryTimer);\n }\n\n this.#retryTimer = setTimeout(() => {\n this.#retryTimer = null;\n void this.triggerSync();\n }, decision.delayMs);\n }\n}\n"],"mappings":";;AAkBA,IAAa,cAAb,MAAqD;CACnD,QAAQ,SAA8C;EACpD,MAAM,EAAE,OAAO,WAAW;EAE1B,IAAI,MAAM,WAAW,YAAY,OAAO,WAAW,UACjD,OAAO;GAAE,QAAQ;GAAO,UAAU;EAAwC;EAG5E,IAAI,OAAO,WAAW,YAAY,MAAM,WAAW,UACjD,OAAO;GAAE,QAAQ;GAAQ,UAAU;EAAwC;EAG7E,MAAM,QAAQ,WAAW,OAAO,SAAS,MAAM,OAAO;EAEtD,IAAI,QAAQ,GACV,OAAO;GAAE,QAAQ;GAAQ,UAAU;EAA4B;EAGjE,IAAI,QAAQ,GACV,OAAO;GAAE,QAAQ;GAAO,UAAU;EAA2B;EAG/D,OAAO;GAAE,QAAQ;GAAQ,UAAU;EAAkC;CACvE;AACF;;;ACxCA,SAAgB,gBAAgB,MAAqB,SAAiB,OAA4B;CAChG,OAAO;EACL;EACA;EACA;EACA,YAAY,KAAK,IAAI;CACvB;AACF;AAEA,SAAgB,YAAY,OAAoC;CAC9D,OACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,aAAa,SACb,gBAAgB;AAEpB;AAEA,SAAgB,mBAAmB,OAA2B;CAC5D,IAAI,YAAY,KAAK,GACnB,OAAO;CAGT,IAAI,iBAAiB,OACnB,OAAO,gBAAgB,WAAW,MAAM,SAAS,KAAK;CAGxD,OAAO,gBAAgB,WAAW,uBAAuB,KAAK;AAChE;;;ACPA,IAAa,oBAAb,MAAuD;CACrD,6BAAsB,IAAI,IAAoE;CAE9F,GAAiC,OAAU,UAA4C;EACrF,MAAM,YACJ,KAAKA,WAAW,IAAI,KAAK,qBAAK,IAAI,IAA2C;EAC/E,UAAU,IAAI,QAAiD;EAC/D,KAAKA,WAAW,IAAI,OAAO,SAAS;EAEpC,aAAa,KAAK,IAAI,OAAO,QAAQ;CACvC;CAEA,KAAmC,OAAU,UAAsC;EACjF,MAAM,YAAY,YAA8B;GAC9C,KAAK,IAAI,OAAO,OAA+B;GAC/C,IAAI,YAAY,KAAA,GACd,SAAyB;QAEzB,SAA+C,OAAO;EAE1D;EAEA,KAAK,GAAG,OAAO,OAAO;CACxB;CAEA,IAAkC,OAAU,UAAsC;EAChF,KAAKA,WAAW,IAAI,KAAK,CAAC,EAAE,OAAO,QAAiD;CACtF;CAEA,KACE,OACA,GAAG,MACG;EACN,MAAM,YAAY,CAAC,GAAI,KAAKA,WAAW,IAAI,KAAK,KAAK,CAAC,CAAE;EAExD,KAAK,MAAM,YAAY,WACrB,IAAI,KAAK,WAAW,GAClB,SAAyB;OAEzB,SAAiD,KAAK,EAAqB;CAGjF;AACF;;;ACtDA,IAAa,aAAb,MAAwB;CACtB;CACA;CACA;CACA;CACA;CACA,UAAsB;CACtB,YAAwC;CAExC;CACA,SAAkB,IAAI,kBAAkB;CAExC,YAAY,SAA4B;EACtC,KAAKC,YAAY,QAAQ;EACzB,KAAKC,aAAa,QAAQ;EAC1B,KAAKC,YAAY,QAAQ;EACzB,KAAKC,aAAa,QAAQ,aAAa;EACvC,KAAK,WAAW,QAAQ,YAAY,IAAI,YAAY;EACpD,KAAKC,eAAe,KAAKH,WAAW,kBAAkB;GACpD,KAAU,KAAK;EACjB,CAAC;CACH;CAEA,IAAI,SAAqB;EACvB,OAAO,KAAKI;CACd;CAEA,OAA4B;EAC1B,IAAI,KAAKC,cAAc,MACrB,OAAO,KAAKA;EAGd,KAAKA,YAAY,KAAKC,UAAU,CAAC,CAAC,cAAc;GAC9C,KAAKD,YAAY;EACnB,CAAC;EAED,OAAO,KAAKA;CACd;CAEA,MAAM,UAAyB;EAC7B,KAAKF,eAAe;CACtB;CAEA,MAAMG,YAAiC;EACrC,MAAM,YAAY,KAAK,IAAI;EAC3B,IAAI,gBAAgB;EACpB,IAAI,kBAAkB;EAEtB,KAAK,OAAO,KAAK,WAAW;EAE5B,IAAI;GACF,KAAKC,WAAW,WAAW;GAC3B,MAAM,iBAAiB,MAAM,KAAKR,UAAU,kBAAkB,KAAKG,UAAU;GAE7E,IAAI,eAAe,SAAS,GAAG;IAC7B,MAAM,KAAKF,WAAW,KAAK;KAAE,SAAS;KAAgB,UAAU,KAAKC;IAAU,CAAC;IAChF,MAAM,KAAKF,UAAU,aAAa,eAAe,KAAK,WAAW,OAAO,EAAE,CAAC;IAC3E,gBAAgB,eAAe;GACjC;GAEA,KAAK,OAAO,KAAK,kBAAkB,EAAE,cAAc,CAAC;GAEpD,IAAI,SAAS,MAAM,KAAKA,UAAU,eAAe;GACjD,IAAI,UAAU;GAEd,OAAO,SAAS;IACd,KAAKQ,WAAW,aAAa;IAG7B,MAAM,SAAS,MAAM,KAAKP,WAAW,KAAK,QAAQ,SAAS,MAAM,KAAKE,UAAU;IAChF,KAAK,OAAO,KAAK,oBAAoB,EAAE,iBAAiB,OAAO,QAAQ,OAAO,CAAC;IAE/E,KAAKK,WAAW,UAAU;IAE1B,MAAM,KAAKR,UAAU,iBAAiB,YAAY;KAChD,MAAM,KAAKA,UAAU,mBAAmB,CAAC,GAAG,OAAO,OAAO,CAAC;KAC3D,MAAM,KAAKA,UAAU,gBAAgB,OAAO,MAAM;IACpD,CAAC;IAED,mBAAmB,OAAO,QAAQ;IAClC,SAAS,OAAO;IAChB,UAAU,OAAO;GACnB;GAEA,KAAKQ,WAAW,MAAM;GAEtB,MAAM,SAAqB;IACzB,SAAS;IACT;IACA;IACA,YAAY,KAAK,IAAI,IAAI;GAC3B;GACA,KAAK,OAAO,KAAK,cAAc,MAAM;GAErC,OAAO;EACT,SAAS,OAAO;GACd,MAAM,QAAQ,mBAAmB,KAAK;GACtC,MAAM,SAAqB;IACzB,SAAS;IACT;IACA;IACA,YAAY,KAAK,IAAI,IAAI;IACzB;GACF;GAEA,KAAK,OAAO,KAAK,SAAS,KAAK;GAC/B,KAAKA,WAAW,MAAM;GACtB,KAAK,OAAO,KAAK,cAAc,MAAM;GAErC,OAAO;EACT;CACF;CAEA,WAAW,QAA0B;EACnC,KAAKH,UAAU;EACf,KAAK,OAAO,KAAK,gBAAgB,MAAM;CACzC;AACF;;;AC7FA,MAAM,sBAAsB;AAC5B,MAAM,wCAAwC,QAAc,KAAK;AACjE,MAAM,+BAA+B,MAAU,KAAK,KAAK;AAEzD,IAAa,uBAAb,MAA2D;CACzD;CACA;CACA;CACA;CACA;CAIA,aAAoD;CACpD,cAAoD;CACpD,kBAAyD;CACzD,YAAwC;CACxC,gBAAgB;CAChB,iBAAiB;CACjB;CACA,sBAA4B;EAC1B,KAAU,YAAY;CACxB;CACA,gCAAsC;EACpC,IAAI,KAAKQ,WAAW,oBAAoB,WACtC,KAAU,YAAY;CAE1B;CAEA,YAAY,SAAsC;EAChD,KAAKJ,UAAU,QAAQ;EACvB,KAAKC,YAAY,QAAQ;EACzB,KAAKC,sBAAsB,QAAQ,eAAe,IAAI,yBAAyB;EAC/E,KAAKG,eAAe,KAAKH;EACzB,KAAKC,UAAU,QAAQ,UAAU,WAAW;EAC5C,KAAKC,YAAY,QAAQ,YAAY,WAAW;CAClD;CAEA,MAAM,UAA4B,CAAC,GAAS;EAC1C,KAAK,KAAK;EACV,KAAKC,eAAe,QAAQ,eAAe,KAAKH;EAEhD,MAAM,aAAa,QAAQ,cAAc;EACzC,IAAI,aAAa,GACf,KAAKI,aAAa,kBAAkB;GAClC,KAAU,YAAY;EACxB,GAAG,UAAU;EAGf,IAAI,QAAQ,wBAAwB,MAClC,KAAKH,SAAS,iBAAiB,UAAU,KAAKI,aAAa;EAG7D,IAAI,QAAQ,oBAAoB,MAC9B,KAAKH,WAAW,iBAAiB,oBAAoB,KAAKI,uBAAuB;EAGnF,MAAM,6BACJ,QAAQ,8BAA8B;EACxC,IAAI,KAAKP,cAAc,KAAA,KAAa,6BAA6B,GAAG;GAClE,MAAM,oBAAoB,QAAQ,qBAAqB;GACvD,KAAKQ,kBAAkB,kBAAkB;IACvC,KAAUR,WAAW,gBAAgB,KAAK,IAAI,IAAI,iBAAiB;GACrE,GAAG,0BAA0B;EAC/B;CACF;CAEA,OAAa;EACX,IAAI,KAAKK,eAAe,MAAM;GAC5B,cAAc,KAAKA,UAAU;GAC7B,KAAKA,aAAa;EACpB;EAEA,IAAI,KAAKI,gBAAgB,MAAM;GAC7B,aAAa,KAAKA,WAAW;GAC7B,KAAKA,cAAc;EACrB;EAEA,IAAI,KAAKD,oBAAoB,MAAM;GACjC,cAAc,KAAKA,eAAe;GAClC,KAAKA,kBAAkB;EACzB;EAEA,KAAKN,SAAS,oBAAoB,UAAU,KAAKI,aAAa;EAC9D,KAAKH,WAAW,oBAAoB,oBAAoB,KAAKI,uBAAuB;CACtF;CAEA,MAAM,cAAmC;EACvC,IAAI,KAAKG,cAAc,MACrB,OAAO,MAAM,KAAKA;EAGpB,KAAKA,YAAY,KAAKX,QAAQ,KAAK,CAAC,CAAC,MAAM,WAAW;GACpD,KAAKY,cAAc,MAAM;GACzB,OAAO;EACT,CAAC;EAED,IAAI;GACF,OAAO,MAAM,KAAKD;EACpB,UAAU;GACR,KAAKA,YAAY;EACnB;CACF;CAEA,cAAc,QAA0B;EACtC,IAAI,OAAO,SAAS;GAClB,KAAKE,gBAAgB;GACrB,KAAKC,iBAAiB;GACtB;EACF;EAEA,IAAI,OAAO,UAAU,KAAA,GACnB;EAGF,KAAKA,iBAAiB,KAAKA,mBAAmB,IAAI,OAAO,MAAM,aAAa,KAAKA;EACjF,MAAM,WAAW,KAAKT,aAAa,OAAO;GACxC,SAAS,KAAKQ;GACd,eAAe,KAAKC;GACpB,WAAW,OAAO;EACpB,CAAC;EAED,KAAKD,iBAAiB;EAEtB,IAAI,SAAS,WAAW,SACtB;EAGF,IAAI,KAAKH,gBAAgB,MACvB,aAAa,KAAKA,WAAW;EAG/B,KAAKA,cAAc,iBAAiB;GAClC,KAAKA,cAAc;GACnB,KAAU,YAAY;EACxB,GAAG,SAAS,OAAO;CACrB;AACF"}
@@ -0,0 +1,184 @@
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
+ //#region src/http/fetch-transport.ts
5
+ var FetchSyncTransport = class {
6
+ type = "http";
7
+ #baseUrl;
8
+ #fetch;
9
+ #headers;
10
+ #retryPolicy;
11
+ constructor(options) {
12
+ this.#baseUrl = options.baseUrl.replace(/\/+$/, "");
13
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
14
+ this.#headers = options.headers ?? {};
15
+ this.#retryPolicy = options.retryPolicy ?? new require_utils_index.ExponentialBackoffPolicy();
16
+ }
17
+ async push(payload) {
18
+ await require_utils_index.retryOperation(async () => {
19
+ await ensureOk(await this.#fetch(`${this.#baseUrl}/push`, {
20
+ method: "POST",
21
+ headers: this.#createHeaders(),
22
+ body: JSON.stringify(payload)
23
+ }));
24
+ }, {
25
+ policy: this.#retryPolicy,
26
+ normalizeError: require_core_index.normalizeSyncError
27
+ });
28
+ }
29
+ async pull(cursor, limit) {
30
+ return await require_utils_index.retryOperation(async () => {
31
+ const url = new URL(`${this.#baseUrl}/pull`);
32
+ if (cursor !== null && cursor.length > 0) url.searchParams.set("cursor", cursor);
33
+ if (limit !== void 0) url.searchParams.set("limit", limit.toString());
34
+ const response = await this.#fetch(url.toString(), {
35
+ method: "GET",
36
+ headers: this.#createHeaders()
37
+ });
38
+ await ensureOk(response);
39
+ return await response.json();
40
+ }, {
41
+ policy: this.#retryPolicy,
42
+ normalizeError: require_core_index.normalizeSyncError
43
+ });
44
+ }
45
+ #createHeaders() {
46
+ return {
47
+ "Content-Type": "application/json",
48
+ ...this.#headers
49
+ };
50
+ }
51
+ };
52
+ async function ensureOk(response) {
53
+ if (response.ok) return;
54
+ throw require_core_index.createSyncError(codeFromStatus(response.status), `Sync HTTP request failed with status ${response.status}.`, await response.text());
55
+ }
56
+ function codeFromStatus(status) {
57
+ if (status === 401 || status === 403) return "AUTH_FAILED";
58
+ if (status === 410) return "CURSOR_EXPIRED";
59
+ if (status >= 500) return "SERVER_ERROR";
60
+ return "TRANSPORT_ERROR";
61
+ }
62
+ //#endregion
63
+ //#region src/http/websocket-transport.ts
64
+ var WebSocketSyncTransport = class {
65
+ type = "websocket";
66
+ #url;
67
+ #WebSocket;
68
+ #requestTimeoutMs;
69
+ #retryPolicy;
70
+ #subscribers = /* @__PURE__ */ new Set();
71
+ #pending = /* @__PURE__ */ new Map();
72
+ #socket = null;
73
+ constructor(options) {
74
+ this.#url = options.url;
75
+ this.#WebSocket = options.WebSocket ?? globalThis.WebSocket;
76
+ this.#requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
77
+ this.#retryPolicy = options.retryPolicy ?? new require_utils_index.ExponentialBackoffPolicy();
78
+ }
79
+ async push(payload) {
80
+ await require_utils_index.retryOperation(async () => {
81
+ if ((await this.#request({
82
+ type: "push",
83
+ payload
84
+ })).type !== "pushResult") throw require_core_index.createSyncError("SERIALIZATION_ERROR", "Unexpected WebSocket push response.");
85
+ }, {
86
+ policy: this.#retryPolicy,
87
+ normalizeError: require_core_index.normalizeSyncError
88
+ });
89
+ }
90
+ async pull(cursor, limit) {
91
+ return await require_utils_index.retryOperation(async () => {
92
+ const message = await this.#request({
93
+ type: "pull",
94
+ cursor,
95
+ limit
96
+ });
97
+ if (message.type !== "pullResult") throw require_core_index.createSyncError("SERIALIZATION_ERROR", "Unexpected WebSocket pull response.");
98
+ return message.result;
99
+ }, {
100
+ policy: this.#retryPolicy,
101
+ normalizeError: require_core_index.normalizeSyncError
102
+ });
103
+ }
104
+ subscribe(onChanges) {
105
+ this.#subscribers.add(onChanges);
106
+ this.#connect();
107
+ return () => {
108
+ this.#subscribers.delete(onChanges);
109
+ };
110
+ }
111
+ close() {
112
+ this.#socket?.close();
113
+ this.#socket = null;
114
+ for (const pending of this.#pending.values()) {
115
+ clearTimeout(pending.timeout);
116
+ pending.reject(require_core_index.createSyncError("TRANSPORT_ERROR", "WebSocket transport closed."));
117
+ }
118
+ this.#pending.clear();
119
+ }
120
+ async #request(payload) {
121
+ const socket = await this.#connect();
122
+ const id = crypto.randomUUID();
123
+ const message = JSON.stringify({
124
+ id,
125
+ ...payload
126
+ });
127
+ return await new Promise((resolve, reject) => {
128
+ const timeout = setTimeout(() => {
129
+ this.#pending.delete(id);
130
+ reject(require_core_index.createSyncError("NETWORK_UNAVAILABLE", "WebSocket sync request timed out."));
131
+ }, this.#requestTimeoutMs);
132
+ this.#pending.set(id, {
133
+ resolve,
134
+ reject,
135
+ timeout
136
+ });
137
+ socket.send(message);
138
+ });
139
+ }
140
+ async #connect() {
141
+ if (this.#socket?.readyState === this.#WebSocket.OPEN) return this.#socket;
142
+ const socket = new this.#WebSocket(this.#url);
143
+ this.#socket = socket;
144
+ socket.addEventListener("message", (event) => this.#handleMessage(event));
145
+ if (socket.readyState === this.#WebSocket.OPEN) return socket;
146
+ return await new Promise((resolve, reject) => {
147
+ const handleOpen = () => {
148
+ cleanup();
149
+ resolve(socket);
150
+ };
151
+ const handleError = () => {
152
+ cleanup();
153
+ reject(require_core_index.createSyncError("NETWORK_UNAVAILABLE", "WebSocket connection failed."));
154
+ };
155
+ const cleanup = () => {
156
+ socket.removeEventListener("open", handleOpen);
157
+ socket.removeEventListener("error", handleError);
158
+ };
159
+ socket.addEventListener("open", handleOpen);
160
+ socket.addEventListener("error", handleError);
161
+ });
162
+ }
163
+ #handleMessage(event) {
164
+ const rawData = "data" in event ? event.data : void 0;
165
+ if (typeof rawData !== "string") return;
166
+ const message = JSON.parse(rawData);
167
+ if (message.type === "changes") {
168
+ for (const subscriber of this.#subscribers) subscriber(message.cursor);
169
+ return;
170
+ }
171
+ const pending = this.#pending.get(message.id);
172
+ if (pending === void 0) return;
173
+ clearTimeout(pending.timeout);
174
+ this.#pending.delete(message.id);
175
+ if ("error" in message && message.error !== void 0) {
176
+ pending.reject(require_core_index.createSyncError("TRANSPORT_ERROR", message.error.message ?? "WebSocket sync request failed.", message.error));
177
+ return;
178
+ }
179
+ pending.resolve(message);
180
+ }
181
+ };
182
+ //#endregion
183
+ exports.FetchSyncTransport = FetchSyncTransport;
184
+ exports.WebSocketSyncTransport = WebSocketSyncTransport;
@@ -0,0 +1,37 @@
1
+ import { s as RetryPolicy } from "../index.cjs";
2
+ import { T as PushPayload, j as SyncTransport, w as PullResult } from "../index2.cjs";
3
+
4
+ //#region src/http/fetch-transport.d.ts
5
+ interface FetchSyncTransportOptions {
6
+ readonly baseUrl: string;
7
+ readonly fetch?: typeof fetch;
8
+ readonly headers?: Record<string, string>;
9
+ readonly retryPolicy?: RetryPolicy;
10
+ }
11
+ declare class FetchSyncTransport implements SyncTransport {
12
+ #private;
13
+ readonly type = "http";
14
+ constructor(options: FetchSyncTransportOptions);
15
+ push(payload: PushPayload): Promise<void>;
16
+ pull(cursor: string | null, limit?: number): Promise<PullResult>;
17
+ }
18
+ //#endregion
19
+ //#region src/http/websocket-transport.d.ts
20
+ interface WebSocketSyncTransportOptions {
21
+ readonly url: string;
22
+ readonly WebSocket?: typeof globalThis.WebSocket;
23
+ readonly requestTimeoutMs?: number;
24
+ readonly retryPolicy?: RetryPolicy;
25
+ }
26
+ declare class WebSocketSyncTransport implements SyncTransport {
27
+ #private;
28
+ readonly type = "websocket";
29
+ constructor(options: WebSocketSyncTransportOptions);
30
+ push(payload: PushPayload): Promise<void>;
31
+ pull(cursor: string | null, limit?: number): Promise<PullResult>;
32
+ subscribe(onChanges: (cursor: string) => void): () => void;
33
+ close(): void;
34
+ }
35
+ //#endregion
36
+ export { FetchSyncTransport, FetchSyncTransportOptions, WebSocketSyncTransport, WebSocketSyncTransportOptions };
37
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/http/fetch-transport.ts","../../src/http/websocket-transport.ts"],"mappings":";;;;UAKiB,yBAAA;EAAA,SACN,OAAA;EAAA,SACA,KAAA,UAAe,KAAA;EAAA,SACf,OAAA,GAAU,MAAA;EAAA,SACV,WAAA,GAAc,WAAA;AAAA;AAAA,cAGZ,kBAAA,YAA8B,aAAA;EAAA;WAChC,IAAA;cAMG,OAAA,EAAS,yBAAA;EAOf,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA;EAe5B,IAAA,CAAK,MAAA,iBAAuB,KAAA,YAAiB,OAAA,CAAQ,UAAA;AAAA;;;UCrC5C,6BAAA;EAAA,SACN,GAAA;EAAA,SACA,SAAA,UAAmB,UAAA,CAAW,SAAA;EAAA,SAC9B,gBAAA;EAAA,SACA,WAAA,GAAc,WAAW;AAAA;AAAA,cAkBvB,sBAAA,YAAkC,aAAA;EAAA;WACpC,IAAA;cAgBG,OAAA,EAAS,6BAAA;EAOf,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA;EAY5B,IAAA,CAAK,MAAA,iBAAuB,KAAA,YAAiB,OAAA,CAAQ,UAAA;EAa3D,SAAA,CAAU,SAAA,GAAY,MAAA;EAQtB,KAAA;AAAA"}
@@ -0,0 +1,37 @@
1
+ import { s as RetryPolicy } from "../index.mjs";
2
+ import { T as PushPayload, j as SyncTransport, w as PullResult } from "../index2.mjs";
3
+
4
+ //#region src/http/fetch-transport.d.ts
5
+ interface FetchSyncTransportOptions {
6
+ readonly baseUrl: string;
7
+ readonly fetch?: typeof fetch;
8
+ readonly headers?: Record<string, string>;
9
+ readonly retryPolicy?: RetryPolicy;
10
+ }
11
+ declare class FetchSyncTransport implements SyncTransport {
12
+ #private;
13
+ readonly type = "http";
14
+ constructor(options: FetchSyncTransportOptions);
15
+ push(payload: PushPayload): Promise<void>;
16
+ pull(cursor: string | null, limit?: number): Promise<PullResult>;
17
+ }
18
+ //#endregion
19
+ //#region src/http/websocket-transport.d.ts
20
+ interface WebSocketSyncTransportOptions {
21
+ readonly url: string;
22
+ readonly WebSocket?: typeof globalThis.WebSocket;
23
+ readonly requestTimeoutMs?: number;
24
+ readonly retryPolicy?: RetryPolicy;
25
+ }
26
+ declare class WebSocketSyncTransport implements SyncTransport {
27
+ #private;
28
+ readonly type = "websocket";
29
+ constructor(options: WebSocketSyncTransportOptions);
30
+ push(payload: PushPayload): Promise<void>;
31
+ pull(cursor: string | null, limit?: number): Promise<PullResult>;
32
+ subscribe(onChanges: (cursor: string) => void): () => void;
33
+ close(): void;
34
+ }
35
+ //#endregion
36
+ export { FetchSyncTransport, FetchSyncTransportOptions, WebSocketSyncTransport, WebSocketSyncTransportOptions };
37
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/http/fetch-transport.ts","../../src/http/websocket-transport.ts"],"mappings":";;;;UAKiB,yBAAA;EAAA,SACN,OAAA;EAAA,SACA,KAAA,UAAe,KAAA;EAAA,SACf,OAAA,GAAU,MAAA;EAAA,SACV,WAAA,GAAc,WAAA;AAAA;AAAA,cAGZ,kBAAA,YAA8B,aAAA;EAAA;WAChC,IAAA;cAMG,OAAA,EAAS,yBAAA;EAOf,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA;EAe5B,IAAA,CAAK,MAAA,iBAAuB,KAAA,YAAiB,OAAA,CAAQ,UAAA;AAAA;;;UCrC5C,6BAAA;EAAA,SACN,GAAA;EAAA,SACA,SAAA,UAAmB,UAAA,CAAW,SAAA;EAAA,SAC9B,gBAAA;EAAA,SACA,WAAA,GAAc,WAAW;AAAA;AAAA,cAkBvB,sBAAA,YAAkC,aAAA;EAAA;WACpC,IAAA;cAgBG,OAAA,EAAS,6BAAA;EAOf,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA;EAY5B,IAAA,CAAK,MAAA,iBAAuB,KAAA,YAAiB,OAAA,CAAQ,UAAA;EAa3D,SAAA,CAAU,SAAA,GAAY,MAAA;EAQtB,KAAA;AAAA"}