@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,168 @@
1
+ import { f as HLCTimestamp, s as RetryPolicy } from "./index.mjs";
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.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index2.d.mts","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":"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"}
@@ -0,0 +1,5 @@
1
+ import { a as RetryDecision, c as retryOperation, d as HLCClock, f as HLCTimestamp, i as RetryContext, l as createChangeId, n as ExponentialBackoffOptions, o as RetryOperationOptions, p as compareHLC, r as ExponentialBackoffPolicy, s as RetryPolicy, t as sleep, u as createDeviceId } from "./index.cjs";
2
+ import { A as SyncStatus, C as DeviceId, D as SyncError, E as SyncCursor, O as SyncErrorCode, S as DatabaseAdapter, T as PushPayload, _ as ConflictResolution, a as SyncSchedulerDatabase, b as Change, c as isSyncError, d as SyncEngineOptions, f as SyncEventBus, g as ConflictContext, h as TypedSyncEventBus, i as SyncScheduler, j as SyncTransport, k as SyncResult, l as normalizeSyncError, m as SyncEventMap, n as DefaultSyncSchedulerOptions, o as SyncSchedulerEngine, p as SyncEventListener, r as SchedulerOptions, s as createSyncError, t as DefaultSyncScheduler, u as SyncEngine, v as ConflictResolver, w as PullResult, x as ChangeAction, y as LWWResolver } from "./index2.cjs";
3
+ import { FetchSyncTransport, FetchSyncTransportOptions, WebSocketSyncTransport, WebSocketSyncTransportOptions } from "./http/index.cjs";
4
+ import { DexieDatabaseAdapter, DexieDatabaseAdapterOptions, MemoryDatabaseAdapter, MemoryDatabaseAdapterOptions } from "./sql/index.cjs";
5
+ export { Change, ChangeAction, ConflictContext, ConflictResolution, ConflictResolver, DatabaseAdapter, DefaultSyncScheduler, DefaultSyncSchedulerOptions, DeviceId, DexieDatabaseAdapter, DexieDatabaseAdapterOptions, ExponentialBackoffOptions, ExponentialBackoffPolicy, FetchSyncTransport, FetchSyncTransportOptions, HLCClock, HLCTimestamp, LWWResolver, MemoryDatabaseAdapter, MemoryDatabaseAdapterOptions, PullResult, PushPayload, RetryContext, RetryDecision, RetryOperationOptions, RetryPolicy, SchedulerOptions, SyncCursor, SyncEngine, SyncEngineOptions, SyncError, SyncErrorCode, SyncEventBus, SyncEventListener, SyncEventMap, SyncResult, SyncScheduler, SyncSchedulerDatabase, SyncSchedulerEngine, SyncStatus, SyncTransport, TypedSyncEventBus, WebSocketSyncTransport, WebSocketSyncTransportOptions, compareHLC, createChangeId, createDeviceId, createSyncError, isSyncError, normalizeSyncError, retryOperation, sleep };
@@ -0,0 +1,5 @@
1
+ import { a as RetryDecision, c as retryOperation, d as HLCClock, f as HLCTimestamp, i as RetryContext, l as createChangeId, n as ExponentialBackoffOptions, o as RetryOperationOptions, p as compareHLC, r as ExponentialBackoffPolicy, s as RetryPolicy, t as sleep, u as createDeviceId } from "./index.mjs";
2
+ import { A as SyncStatus, C as DeviceId, D as SyncError, E as SyncCursor, O as SyncErrorCode, S as DatabaseAdapter, T as PushPayload, _ as ConflictResolution, a as SyncSchedulerDatabase, b as Change, c as isSyncError, d as SyncEngineOptions, f as SyncEventBus, g as ConflictContext, h as TypedSyncEventBus, i as SyncScheduler, j as SyncTransport, k as SyncResult, l as normalizeSyncError, m as SyncEventMap, n as DefaultSyncSchedulerOptions, o as SyncSchedulerEngine, p as SyncEventListener, r as SchedulerOptions, s as createSyncError, t as DefaultSyncScheduler, u as SyncEngine, v as ConflictResolver, w as PullResult, x as ChangeAction, y as LWWResolver } from "./index2.mjs";
3
+ import { FetchSyncTransport, FetchSyncTransportOptions, WebSocketSyncTransport, WebSocketSyncTransportOptions } from "./http/index.mjs";
4
+ import { DexieDatabaseAdapter, DexieDatabaseAdapterOptions, MemoryDatabaseAdapter, MemoryDatabaseAdapterOptions } from "./sql/index.mjs";
5
+ export { Change, ChangeAction, ConflictContext, ConflictResolution, ConflictResolver, DatabaseAdapter, DefaultSyncScheduler, DefaultSyncSchedulerOptions, DeviceId, DexieDatabaseAdapter, DexieDatabaseAdapterOptions, ExponentialBackoffOptions, ExponentialBackoffPolicy, FetchSyncTransport, FetchSyncTransportOptions, HLCClock, HLCTimestamp, LWWResolver, MemoryDatabaseAdapter, MemoryDatabaseAdapterOptions, PullResult, PushPayload, RetryContext, RetryDecision, RetryOperationOptions, RetryPolicy, SchedulerOptions, SyncCursor, SyncEngine, SyncEngineOptions, SyncError, SyncErrorCode, SyncEventBus, SyncEventListener, SyncEventMap, SyncResult, SyncScheduler, SyncSchedulerDatabase, SyncSchedulerEngine, SyncStatus, SyncTransport, TypedSyncEventBus, WebSocketSyncTransport, WebSocketSyncTransportOptions, compareHLC, createChangeId, createDeviceId, createSyncError, isSyncError, normalizeSyncError, retryOperation, sleep };
@@ -0,0 +1,149 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let dexie = require("dexie");
25
+ dexie = __toESM(dexie, 1);
26
+ //#region src/sql/dexie-adapter.ts
27
+ var DexieDatabaseAdapter = class {
28
+ #database;
29
+ constructor(options = {}) {
30
+ this.#database = options.database ?? createDatabase(options.databaseName ?? "nsky-sync");
31
+ }
32
+ static async delete(databaseName) {
33
+ await dexie.default.delete(databaseName);
34
+ }
35
+ close() {
36
+ this.#database.close();
37
+ }
38
+ async runInTransaction(workload) {
39
+ return await this.#database.transaction("rw", this.#database.meta, this.#database.changes, this.#database.rows, workload);
40
+ }
41
+ async getLocalCursor() {
42
+ return (await this.#database.meta.get("cursor"))?.value ?? null;
43
+ }
44
+ async saveLocalCursor(cursor) {
45
+ await this.#database.meta.put({
46
+ key: "cursor",
47
+ value: cursor
48
+ });
49
+ }
50
+ async getPendingChanges(limit) {
51
+ const query = this.#database.changes.orderBy("createdAt");
52
+ return await (limit === void 0 ? query.toArray() : query.limit(limit).toArray());
53
+ }
54
+ async markAsSynced(changeIds) {
55
+ await this.#database.changes.bulkDelete(changeIds);
56
+ }
57
+ async applyRemoteChanges(changes) {
58
+ await this.#database.rows.bulkPut(changes.map((change) => toStoredChange(change)));
59
+ }
60
+ async clearTombstones(beforeTimestamp) {
61
+ const tombstones = await this.#database.rows.where("action").equals("delete").and((row) => row.createdAt < beforeTimestamp).toArray();
62
+ await this.#database.rows.bulkDelete(tombstones.map((row) => row.key));
63
+ return tombstones.length;
64
+ }
65
+ async recordLocalChange(change) {
66
+ await this.#database.changes.put(change);
67
+ }
68
+ async getRows(table) {
69
+ return (table === void 0 ? await this.#database.rows.orderBy("createdAt").toArray() : await this.#database.rows.where("table").equals(table).sortBy("createdAt")).map(fromStoredChange);
70
+ }
71
+ };
72
+ function createDatabase(databaseName) {
73
+ const database = new dexie.default(databaseName);
74
+ database.version(1).stores({
75
+ meta: "&key",
76
+ changes: "&id, createdAt",
77
+ rows: "&key, table, entityId, action, createdAt"
78
+ });
79
+ return database;
80
+ }
81
+ function rowKey(change) {
82
+ return `${change.table}:${change.entityId}`;
83
+ }
84
+ function toStoredChange(change) {
85
+ return {
86
+ ...change,
87
+ key: rowKey(change)
88
+ };
89
+ }
90
+ function fromStoredChange(change) {
91
+ const { key: _key, ...rest } = change;
92
+ return rest;
93
+ }
94
+ //#endregion
95
+ //#region src/sql/memory-adapter.ts
96
+ var MemoryDatabaseAdapter = class {
97
+ #pendingChanges;
98
+ #rows;
99
+ #cursor;
100
+ constructor(options = {}) {
101
+ this.#pendingChanges = [...options.pendingChanges ?? []];
102
+ this.#rows = [...options.rows ?? []];
103
+ this.#cursor = options.cursor ?? null;
104
+ }
105
+ async runInTransaction(workload) {
106
+ const pendingSnapshot = [...this.#pendingChanges];
107
+ const rowsSnapshot = [...this.#rows];
108
+ const cursorSnapshot = this.#cursor;
109
+ try {
110
+ return await workload();
111
+ } catch (error) {
112
+ this.#pendingChanges = pendingSnapshot;
113
+ this.#rows = rowsSnapshot;
114
+ this.#cursor = cursorSnapshot;
115
+ throw error;
116
+ }
117
+ }
118
+ async getLocalCursor() {
119
+ return this.#cursor;
120
+ }
121
+ async saveLocalCursor(cursor) {
122
+ this.#cursor = cursor;
123
+ }
124
+ async getPendingChanges(limit) {
125
+ return limit === void 0 ? [...this.#pendingChanges] : this.#pendingChanges.slice(0, limit);
126
+ }
127
+ async markAsSynced(changeIds) {
128
+ const syncedIds = new Set(changeIds);
129
+ this.#pendingChanges = this.#pendingChanges.filter((change) => !syncedIds.has(change.id));
130
+ }
131
+ async applyRemoteChanges(changes) {
132
+ for (const change of changes) {
133
+ const existingIndex = this.#rows.findIndex((row) => row.table === change.table && row.entityId === change.entityId);
134
+ if (existingIndex >= 0) this.#rows[existingIndex] = change;
135
+ else this.#rows.push(change);
136
+ }
137
+ }
138
+ async clearTombstones(beforeTimestamp) {
139
+ const beforeCount = this.#rows.length;
140
+ this.#rows = this.#rows.filter((row) => row.action !== "delete" || row.createdAt >= beforeTimestamp);
141
+ return beforeCount - this.#rows.length;
142
+ }
143
+ getRows(table) {
144
+ return table === void 0 ? [...this.#rows] : this.#rows.filter((row) => row.table === table);
145
+ }
146
+ };
147
+ //#endregion
148
+ exports.DexieDatabaseAdapter = DexieDatabaseAdapter;
149
+ exports.MemoryDatabaseAdapter = MemoryDatabaseAdapter;
@@ -0,0 +1,57 @@
1
+ import { E as SyncCursor, S as DatabaseAdapter, b as Change } from "../index2.cjs";
2
+ import Dexie, { EntityTable } from "dexie";
3
+
4
+ //#region src/sql/dexie-adapter.d.ts
5
+ interface SyncMetaRecord {
6
+ readonly key: string;
7
+ readonly value: unknown;
8
+ }
9
+ interface StoredChange extends Change {
10
+ readonly key: string;
11
+ }
12
+ interface SyncDexieDatabase extends Dexie {
13
+ readonly meta: EntityTable<SyncMetaRecord, "key">;
14
+ readonly changes: EntityTable<Change, "id">;
15
+ readonly rows: EntityTable<StoredChange, "key">;
16
+ }
17
+ interface DexieDatabaseAdapterOptions {
18
+ readonly databaseName?: string;
19
+ readonly database?: SyncDexieDatabase;
20
+ }
21
+ declare class DexieDatabaseAdapter implements DatabaseAdapter {
22
+ #private;
23
+ constructor(options?: DexieDatabaseAdapterOptions);
24
+ static delete(databaseName: string): Promise<void>;
25
+ close(): void;
26
+ runInTransaction<T>(workload: () => Promise<T>): Promise<T>;
27
+ getLocalCursor(): Promise<SyncCursor | null>;
28
+ saveLocalCursor(cursor: SyncCursor): Promise<void>;
29
+ getPendingChanges(limit?: number): Promise<Change[]>;
30
+ markAsSynced(changeIds: string[]): Promise<void>;
31
+ applyRemoteChanges(changes: Change[]): Promise<void>;
32
+ clearTombstones(beforeTimestamp: number): Promise<number>;
33
+ recordLocalChange(change: Change): Promise<void>;
34
+ getRows(table?: string): Promise<Change[]>;
35
+ }
36
+ //#endregion
37
+ //#region src/sql/memory-adapter.d.ts
38
+ interface MemoryDatabaseAdapterOptions {
39
+ readonly pendingChanges?: ReadonlyArray<Change>;
40
+ readonly rows?: ReadonlyArray<Change>;
41
+ readonly cursor?: SyncCursor | null;
42
+ }
43
+ declare class MemoryDatabaseAdapter implements DatabaseAdapter {
44
+ #private;
45
+ constructor(options?: MemoryDatabaseAdapterOptions);
46
+ runInTransaction<T>(workload: () => Promise<T>): Promise<T>;
47
+ getLocalCursor(): Promise<SyncCursor | null>;
48
+ saveLocalCursor(cursor: SyncCursor): Promise<void>;
49
+ getPendingChanges(limit?: number): Promise<Change[]>;
50
+ markAsSynced(changeIds: string[]): Promise<void>;
51
+ applyRemoteChanges(changes: Change[]): Promise<void>;
52
+ clearTombstones(beforeTimestamp: number): Promise<number>;
53
+ getRows(table?: string): Change[];
54
+ }
55
+ //#endregion
56
+ export { DexieDatabaseAdapter, DexieDatabaseAdapterOptions, MemoryDatabaseAdapter, MemoryDatabaseAdapterOptions };
57
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/sql/dexie-adapter.ts","../../src/sql/memory-adapter.ts"],"mappings":";;;;UAIU,cAAA;EAAA,SACC,GAAA;EAAA,SACA,KAAK;AAAA;AAAA,UAGN,YAAA,SAAqB,MAAM;EAAA,SAC1B,GAAG;AAAA;AAAA,UAGJ,iBAAA,SAA0B,KAAA;EAAA,SACzB,IAAA,EAAM,WAAA,CAAY,cAAA;EAAA,SAClB,OAAA,EAAS,WAAA,CAAY,MAAA;EAAA,SACrB,IAAA,EAAM,WAAA,CAAY,YAAA;AAAA;AAAA,UAGZ,2BAAA;EAAA,SACN,YAAA;EAAA,SACA,QAAA,GAAW,iBAAiB;AAAA;AAAA,cAG1B,oBAAA,YAAgC,eAAA;EAAA;cAG/B,OAAA,GAAS,2BAAA;EAAA,OAIR,MAAA,CAAO,YAAA,WAAuB,OAAA;EAI3C,KAAA;EAIM,gBAAA,IAAoB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EAUzD,cAAA,IAAkB,OAAA,CAAQ,UAAA;EAK1B,eAAA,CAAgB,MAAA,EAAQ,UAAA,GAAa,OAAA;EAIrC,iBAAA,CAAkB,KAAA,YAAiB,OAAA,CAAQ,MAAA;EAK3C,YAAA,CAAa,SAAA,aAAsB,OAAA;EAInC,kBAAA,CAAmB,OAAA,EAAS,MAAA,KAAW,OAAA;EAIvC,eAAA,CAAgB,eAAA,WAA0B,OAAA;EAW1C,iBAAA,CAAkB,MAAA,EAAQ,MAAA,GAAS,OAAA;EAInC,OAAA,CAAQ,KAAA,YAAiB,OAAA,CAAQ,MAAA;AAAA;;;UCpFxB,4BAAA;EAAA,SACN,cAAA,GAAiB,aAAA,CAAc,MAAA;EAAA,SAC/B,IAAA,GAAO,aAAA,CAAc,MAAA;EAAA,SACrB,MAAA,GAAS,UAAA;AAAA;AAAA,cAGP,qBAAA,YAAiC,eAAA;EAAA;cAKhC,OAAA,GAAS,4BAAA;EAMf,gBAAA,IAAoB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EAezD,cAAA,IAAkB,OAAA,CAAQ,UAAA;EAI1B,eAAA,CAAgB,MAAA,EAAQ,UAAA,GAAa,OAAA;EAIrC,iBAAA,CAAkB,KAAA,YAAiB,OAAA,CAAQ,MAAA;EAI3C,YAAA,CAAa,SAAA,aAAsB,OAAA;EAKnC,kBAAA,CAAmB,OAAA,EAAS,MAAA,KAAW,OAAA;EAcvC,eAAA,CAAgB,eAAA,WAA0B,OAAA;EAQhD,OAAA,CAAQ,KAAA,YAAiB,MAAA;AAAA"}
@@ -0,0 +1,57 @@
1
+ import { E as SyncCursor, S as DatabaseAdapter, b as Change } from "../index2.mjs";
2
+ import Dexie, { EntityTable } from "dexie";
3
+
4
+ //#region src/sql/dexie-adapter.d.ts
5
+ interface SyncMetaRecord {
6
+ readonly key: string;
7
+ readonly value: unknown;
8
+ }
9
+ interface StoredChange extends Change {
10
+ readonly key: string;
11
+ }
12
+ interface SyncDexieDatabase extends Dexie {
13
+ readonly meta: EntityTable<SyncMetaRecord, "key">;
14
+ readonly changes: EntityTable<Change, "id">;
15
+ readonly rows: EntityTable<StoredChange, "key">;
16
+ }
17
+ interface DexieDatabaseAdapterOptions {
18
+ readonly databaseName?: string;
19
+ readonly database?: SyncDexieDatabase;
20
+ }
21
+ declare class DexieDatabaseAdapter implements DatabaseAdapter {
22
+ #private;
23
+ constructor(options?: DexieDatabaseAdapterOptions);
24
+ static delete(databaseName: string): Promise<void>;
25
+ close(): void;
26
+ runInTransaction<T>(workload: () => Promise<T>): Promise<T>;
27
+ getLocalCursor(): Promise<SyncCursor | null>;
28
+ saveLocalCursor(cursor: SyncCursor): Promise<void>;
29
+ getPendingChanges(limit?: number): Promise<Change[]>;
30
+ markAsSynced(changeIds: string[]): Promise<void>;
31
+ applyRemoteChanges(changes: Change[]): Promise<void>;
32
+ clearTombstones(beforeTimestamp: number): Promise<number>;
33
+ recordLocalChange(change: Change): Promise<void>;
34
+ getRows(table?: string): Promise<Change[]>;
35
+ }
36
+ //#endregion
37
+ //#region src/sql/memory-adapter.d.ts
38
+ interface MemoryDatabaseAdapterOptions {
39
+ readonly pendingChanges?: ReadonlyArray<Change>;
40
+ readonly rows?: ReadonlyArray<Change>;
41
+ readonly cursor?: SyncCursor | null;
42
+ }
43
+ declare class MemoryDatabaseAdapter implements DatabaseAdapter {
44
+ #private;
45
+ constructor(options?: MemoryDatabaseAdapterOptions);
46
+ runInTransaction<T>(workload: () => Promise<T>): Promise<T>;
47
+ getLocalCursor(): Promise<SyncCursor | null>;
48
+ saveLocalCursor(cursor: SyncCursor): Promise<void>;
49
+ getPendingChanges(limit?: number): Promise<Change[]>;
50
+ markAsSynced(changeIds: string[]): Promise<void>;
51
+ applyRemoteChanges(changes: Change[]): Promise<void>;
52
+ clearTombstones(beforeTimestamp: number): Promise<number>;
53
+ getRows(table?: string): Change[];
54
+ }
55
+ //#endregion
56
+ export { DexieDatabaseAdapter, DexieDatabaseAdapterOptions, MemoryDatabaseAdapter, MemoryDatabaseAdapterOptions };
57
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/sql/dexie-adapter.ts","../../src/sql/memory-adapter.ts"],"mappings":";;;;UAIU,cAAA;EAAA,SACC,GAAA;EAAA,SACA,KAAK;AAAA;AAAA,UAGN,YAAA,SAAqB,MAAM;EAAA,SAC1B,GAAG;AAAA;AAAA,UAGJ,iBAAA,SAA0B,KAAA;EAAA,SACzB,IAAA,EAAM,WAAA,CAAY,cAAA;EAAA,SAClB,OAAA,EAAS,WAAA,CAAY,MAAA;EAAA,SACrB,IAAA,EAAM,WAAA,CAAY,YAAA;AAAA;AAAA,UAGZ,2BAAA;EAAA,SACN,YAAA;EAAA,SACA,QAAA,GAAW,iBAAiB;AAAA;AAAA,cAG1B,oBAAA,YAAgC,eAAA;EAAA;cAG/B,OAAA,GAAS,2BAAA;EAAA,OAIR,MAAA,CAAO,YAAA,WAAuB,OAAA;EAI3C,KAAA;EAIM,gBAAA,IAAoB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EAUzD,cAAA,IAAkB,OAAA,CAAQ,UAAA;EAK1B,eAAA,CAAgB,MAAA,EAAQ,UAAA,GAAa,OAAA;EAIrC,iBAAA,CAAkB,KAAA,YAAiB,OAAA,CAAQ,MAAA;EAK3C,YAAA,CAAa,SAAA,aAAsB,OAAA;EAInC,kBAAA,CAAmB,OAAA,EAAS,MAAA,KAAW,OAAA;EAIvC,eAAA,CAAgB,eAAA,WAA0B,OAAA;EAW1C,iBAAA,CAAkB,MAAA,EAAQ,MAAA,GAAS,OAAA;EAInC,OAAA,CAAQ,KAAA,YAAiB,OAAA,CAAQ,MAAA;AAAA;;;UCpFxB,4BAAA;EAAA,SACN,cAAA,GAAiB,aAAA,CAAc,MAAA;EAAA,SAC/B,IAAA,GAAO,aAAA,CAAc,MAAA;EAAA,SACrB,MAAA,GAAS,UAAA;AAAA;AAAA,cAGP,qBAAA,YAAiC,eAAA;EAAA;cAKhC,OAAA,GAAS,4BAAA;EAMf,gBAAA,IAAoB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EAezD,cAAA,IAAkB,OAAA,CAAQ,UAAA;EAI1B,eAAA,CAAgB,MAAA,EAAQ,UAAA,GAAa,OAAA;EAIrC,iBAAA,CAAkB,KAAA,YAAiB,OAAA,CAAQ,MAAA;EAI3C,YAAA,CAAa,SAAA,aAAsB,OAAA;EAKnC,kBAAA,CAAmB,OAAA,EAAS,MAAA,KAAW,OAAA;EAcvC,eAAA,CAAgB,eAAA,WAA0B,OAAA;EAQhD,OAAA,CAAQ,KAAA,YAAiB,MAAA;AAAA"}
@@ -0,0 +1,126 @@
1
+ import Dexie from "dexie";
2
+ //#region src/sql/dexie-adapter.ts
3
+ var DexieDatabaseAdapter = class {
4
+ #database;
5
+ constructor(options = {}) {
6
+ this.#database = options.database ?? createDatabase(options.databaseName ?? "nsky-sync");
7
+ }
8
+ static async delete(databaseName) {
9
+ await Dexie.delete(databaseName);
10
+ }
11
+ close() {
12
+ this.#database.close();
13
+ }
14
+ async runInTransaction(workload) {
15
+ return await this.#database.transaction("rw", this.#database.meta, this.#database.changes, this.#database.rows, workload);
16
+ }
17
+ async getLocalCursor() {
18
+ return (await this.#database.meta.get("cursor"))?.value ?? null;
19
+ }
20
+ async saveLocalCursor(cursor) {
21
+ await this.#database.meta.put({
22
+ key: "cursor",
23
+ value: cursor
24
+ });
25
+ }
26
+ async getPendingChanges(limit) {
27
+ const query = this.#database.changes.orderBy("createdAt");
28
+ return await (limit === void 0 ? query.toArray() : query.limit(limit).toArray());
29
+ }
30
+ async markAsSynced(changeIds) {
31
+ await this.#database.changes.bulkDelete(changeIds);
32
+ }
33
+ async applyRemoteChanges(changes) {
34
+ await this.#database.rows.bulkPut(changes.map((change) => toStoredChange(change)));
35
+ }
36
+ async clearTombstones(beforeTimestamp) {
37
+ const tombstones = await this.#database.rows.where("action").equals("delete").and((row) => row.createdAt < beforeTimestamp).toArray();
38
+ await this.#database.rows.bulkDelete(tombstones.map((row) => row.key));
39
+ return tombstones.length;
40
+ }
41
+ async recordLocalChange(change) {
42
+ await this.#database.changes.put(change);
43
+ }
44
+ async getRows(table) {
45
+ return (table === void 0 ? await this.#database.rows.orderBy("createdAt").toArray() : await this.#database.rows.where("table").equals(table).sortBy("createdAt")).map(fromStoredChange);
46
+ }
47
+ };
48
+ function createDatabase(databaseName) {
49
+ const database = new Dexie(databaseName);
50
+ database.version(1).stores({
51
+ meta: "&key",
52
+ changes: "&id, createdAt",
53
+ rows: "&key, table, entityId, action, createdAt"
54
+ });
55
+ return database;
56
+ }
57
+ function rowKey(change) {
58
+ return `${change.table}:${change.entityId}`;
59
+ }
60
+ function toStoredChange(change) {
61
+ return {
62
+ ...change,
63
+ key: rowKey(change)
64
+ };
65
+ }
66
+ function fromStoredChange(change) {
67
+ const { key: _key, ...rest } = change;
68
+ return rest;
69
+ }
70
+ //#endregion
71
+ //#region src/sql/memory-adapter.ts
72
+ var MemoryDatabaseAdapter = class {
73
+ #pendingChanges;
74
+ #rows;
75
+ #cursor;
76
+ constructor(options = {}) {
77
+ this.#pendingChanges = [...options.pendingChanges ?? []];
78
+ this.#rows = [...options.rows ?? []];
79
+ this.#cursor = options.cursor ?? null;
80
+ }
81
+ async runInTransaction(workload) {
82
+ const pendingSnapshot = [...this.#pendingChanges];
83
+ const rowsSnapshot = [...this.#rows];
84
+ const cursorSnapshot = this.#cursor;
85
+ try {
86
+ return await workload();
87
+ } catch (error) {
88
+ this.#pendingChanges = pendingSnapshot;
89
+ this.#rows = rowsSnapshot;
90
+ this.#cursor = cursorSnapshot;
91
+ throw error;
92
+ }
93
+ }
94
+ async getLocalCursor() {
95
+ return this.#cursor;
96
+ }
97
+ async saveLocalCursor(cursor) {
98
+ this.#cursor = cursor;
99
+ }
100
+ async getPendingChanges(limit) {
101
+ return limit === void 0 ? [...this.#pendingChanges] : this.#pendingChanges.slice(0, limit);
102
+ }
103
+ async markAsSynced(changeIds) {
104
+ const syncedIds = new Set(changeIds);
105
+ this.#pendingChanges = this.#pendingChanges.filter((change) => !syncedIds.has(change.id));
106
+ }
107
+ async applyRemoteChanges(changes) {
108
+ for (const change of changes) {
109
+ const existingIndex = this.#rows.findIndex((row) => row.table === change.table && row.entityId === change.entityId);
110
+ if (existingIndex >= 0) this.#rows[existingIndex] = change;
111
+ else this.#rows.push(change);
112
+ }
113
+ }
114
+ async clearTombstones(beforeTimestamp) {
115
+ const beforeCount = this.#rows.length;
116
+ this.#rows = this.#rows.filter((row) => row.action !== "delete" || row.createdAt >= beforeTimestamp);
117
+ return beforeCount - this.#rows.length;
118
+ }
119
+ getRows(table) {
120
+ return table === void 0 ? [...this.#rows] : this.#rows.filter((row) => row.table === table);
121
+ }
122
+ };
123
+ //#endregion
124
+ export { DexieDatabaseAdapter, MemoryDatabaseAdapter };
125
+
126
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["#database","#pendingChanges","#rows","#cursor"],"sources":["../../src/sql/dexie-adapter.ts","../../src/sql/memory-adapter.ts"],"sourcesContent":["import Dexie, { type EntityTable } from \"dexie\";\n\nimport type { Change, DatabaseAdapter, SyncCursor } from \"../core\";\n\ninterface SyncMetaRecord {\n readonly key: string;\n readonly value: unknown;\n}\n\ninterface StoredChange extends Change {\n readonly key: string;\n}\n\ninterface SyncDexieDatabase extends Dexie {\n readonly meta: EntityTable<SyncMetaRecord, \"key\">;\n readonly changes: EntityTable<Change, \"id\">;\n readonly rows: EntityTable<StoredChange, \"key\">;\n}\n\nexport interface DexieDatabaseAdapterOptions {\n readonly databaseName?: string;\n readonly database?: SyncDexieDatabase;\n}\n\nexport class DexieDatabaseAdapter implements DatabaseAdapter {\n readonly #database: SyncDexieDatabase;\n\n constructor(options: DexieDatabaseAdapterOptions = {}) {\n this.#database = options.database ?? createDatabase(options.databaseName ?? \"nsky-sync\");\n }\n\n static async delete(databaseName: string): Promise<void> {\n await Dexie.delete(databaseName);\n }\n\n close(): void {\n this.#database.close();\n }\n\n async runInTransaction<T>(workload: () => Promise<T>): Promise<T> {\n return await this.#database.transaction(\n \"rw\",\n this.#database.meta,\n this.#database.changes,\n this.#database.rows,\n workload,\n );\n }\n\n async getLocalCursor(): Promise<SyncCursor | null> {\n const record = await this.#database.meta.get(\"cursor\");\n return (record?.value as SyncCursor | undefined) ?? null;\n }\n\n async saveLocalCursor(cursor: SyncCursor): Promise<void> {\n await this.#database.meta.put({ key: \"cursor\", value: cursor });\n }\n\n async getPendingChanges(limit?: number): Promise<Change[]> {\n const query = this.#database.changes.orderBy(\"createdAt\");\n return await (limit === undefined ? query.toArray() : query.limit(limit).toArray());\n }\n\n async markAsSynced(changeIds: string[]): Promise<void> {\n await this.#database.changes.bulkDelete(changeIds);\n }\n\n async applyRemoteChanges(changes: Change[]): Promise<void> {\n await this.#database.rows.bulkPut(changes.map((change) => toStoredChange(change)));\n }\n\n async clearTombstones(beforeTimestamp: number): Promise<number> {\n const tombstones = await this.#database.rows\n .where(\"action\")\n .equals(\"delete\")\n .and((row) => row.createdAt < beforeTimestamp)\n .toArray();\n\n await this.#database.rows.bulkDelete(tombstones.map((row) => row.key));\n return tombstones.length;\n }\n\n async recordLocalChange(change: Change): Promise<void> {\n await this.#database.changes.put(change);\n }\n\n async getRows(table?: string): Promise<Change[]> {\n const rows =\n table === undefined\n ? await this.#database.rows.orderBy(\"createdAt\").toArray()\n : await this.#database.rows.where(\"table\").equals(table).sortBy(\"createdAt\");\n\n return rows.map(fromStoredChange);\n }\n}\n\nfunction createDatabase(databaseName: string): SyncDexieDatabase {\n const database = new Dexie(databaseName) as SyncDexieDatabase;\n database.version(1).stores({\n meta: \"&key\",\n changes: \"&id, createdAt\",\n rows: \"&key, table, entityId, action, createdAt\",\n });\n return database;\n}\n\nfunction rowKey(change: Pick<Change, \"table\" | \"entityId\">): string {\n return `${change.table}:${change.entityId}`;\n}\n\nfunction toStoredChange(change: Change): StoredChange {\n return {\n ...change,\n key: rowKey(change),\n };\n}\n\nfunction fromStoredChange(change: StoredChange): Change {\n const { key: _key, ...rest } = change;\n return rest;\n}\n","import type { Change, DatabaseAdapter, SyncCursor } from \"../core\";\n\nexport interface MemoryDatabaseAdapterOptions {\n readonly pendingChanges?: ReadonlyArray<Change>;\n readonly rows?: ReadonlyArray<Change>;\n readonly cursor?: SyncCursor | null;\n}\n\nexport class MemoryDatabaseAdapter implements DatabaseAdapter {\n #pendingChanges: Change[];\n #rows: Change[];\n #cursor: SyncCursor | null;\n\n constructor(options: MemoryDatabaseAdapterOptions = {}) {\n this.#pendingChanges = [...(options.pendingChanges ?? [])];\n this.#rows = [...(options.rows ?? [])];\n this.#cursor = options.cursor ?? null;\n }\n\n async runInTransaction<T>(workload: () => Promise<T>): Promise<T> {\n const pendingSnapshot = [...this.#pendingChanges];\n const rowsSnapshot = [...this.#rows];\n const cursorSnapshot = this.#cursor;\n\n try {\n return await workload();\n } catch (error) {\n this.#pendingChanges = pendingSnapshot;\n this.#rows = rowsSnapshot;\n this.#cursor = cursorSnapshot;\n throw error;\n }\n }\n\n async getLocalCursor(): Promise<SyncCursor | null> {\n return this.#cursor;\n }\n\n async saveLocalCursor(cursor: SyncCursor): Promise<void> {\n this.#cursor = cursor;\n }\n\n async getPendingChanges(limit?: number): Promise<Change[]> {\n return limit === undefined ? [...this.#pendingChanges] : this.#pendingChanges.slice(0, limit);\n }\n\n async markAsSynced(changeIds: string[]): Promise<void> {\n const syncedIds = new Set(changeIds);\n this.#pendingChanges = this.#pendingChanges.filter((change) => !syncedIds.has(change.id));\n }\n\n async applyRemoteChanges(changes: Change[]): Promise<void> {\n for (const change of changes) {\n const existingIndex = this.#rows.findIndex(\n (row) => row.table === change.table && row.entityId === change.entityId,\n );\n\n if (existingIndex >= 0) {\n this.#rows[existingIndex] = change;\n } else {\n this.#rows.push(change);\n }\n }\n }\n\n async clearTombstones(beforeTimestamp: number): Promise<number> {\n const beforeCount = this.#rows.length;\n this.#rows = this.#rows.filter(\n (row) => row.action !== \"delete\" || row.createdAt >= beforeTimestamp,\n );\n return beforeCount - this.#rows.length;\n }\n\n getRows(table?: string): Change[] {\n return table === undefined ? [...this.#rows] : this.#rows.filter((row) => row.table === table);\n }\n}\n"],"mappings":"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"}