local-lite 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -2
  3. package/dist/adapters/local/better-sqlite3.d.ts +23 -0
  4. package/dist/adapters/local/better-sqlite3.js +46 -0
  5. package/dist/adapters/local/expo-sqlite.d.ts +8 -0
  6. package/dist/adapters/local/expo-sqlite.js +30 -0
  7. package/dist/adapters/local/index.d.ts +13 -0
  8. package/dist/adapters/local/index.js +22 -0
  9. package/dist/adapters/local/libsql.d.ts +24 -0
  10. package/dist/adapters/local/libsql.js +87 -0
  11. package/dist/adapters/local/node-sqlite.d.ts +24 -0
  12. package/dist/adapters/local/node-sqlite.js +44 -0
  13. package/dist/adapters/local/op-sqlite.d.ts +26 -0
  14. package/dist/adapters/local/op-sqlite.js +59 -0
  15. package/dist/adapters/local/sql-js.d.ts +26 -0
  16. package/dist/adapters/local/sql-js.js +63 -0
  17. package/dist/adapters/network/index.d.ts +11 -0
  18. package/dist/adapters/network/index.js +53 -0
  19. package/dist/adapters/remote/convex.d.ts +71 -0
  20. package/dist/adapters/remote/convex.js +185 -0
  21. package/dist/adapters/remote/demo.d.ts +21 -0
  22. package/dist/adapters/remote/demo.js +46 -0
  23. package/dist/adapters/remote/firestore.d.ts +64 -0
  24. package/dist/adapters/remote/firestore.js +302 -0
  25. package/dist/adapters/remote/index.d.ts +10 -0
  26. package/dist/adapters/remote/index.js +20 -0
  27. package/dist/adapters/remote/supabase.d.ts +97 -0
  28. package/dist/adapters/remote/supabase.js +276 -0
  29. package/dist/config/index.d.ts +24 -0
  30. package/dist/config/index.js +101 -0
  31. package/dist/config/resolve-local.d.ts +7 -0
  32. package/dist/config/resolve-local.js +68 -0
  33. package/dist/config/resolve-remote.d.ts +7 -0
  34. package/dist/config/resolve-remote.js +81 -0
  35. package/dist/config/types.d.ts +159 -0
  36. package/dist/config/types.js +2 -0
  37. package/dist/core/client.d.ts +153 -0
  38. package/dist/core/client.js +445 -0
  39. package/dist/core/conflict.d.ts +5 -0
  40. package/dist/core/conflict.js +38 -0
  41. package/dist/core/meta-store.d.ts +28 -0
  42. package/dist/core/meta-store.js +64 -0
  43. package/dist/core/outbox-store.d.ts +76 -0
  44. package/dist/core/outbox-store.js +265 -0
  45. package/dist/core/query.d.ts +13 -0
  46. package/dist/core/query.js +150 -0
  47. package/dist/core/schema-manager.d.ts +39 -0
  48. package/dist/core/schema-manager.js +150 -0
  49. package/dist/core/sync-worker.d.ts +89 -0
  50. package/dist/core/sync-worker.js +473 -0
  51. package/dist/core/table-manager.d.ts +19 -0
  52. package/dist/core/table-manager.js +74 -0
  53. package/dist/core/types.d.ts +156 -0
  54. package/dist/core/types.js +2 -0
  55. package/dist/index.d.ts +18 -0
  56. package/dist/index.js +34 -0
  57. package/dist/react/index.d.ts +50 -0
  58. package/dist/react/index.js +174 -0
  59. package/dist/reactive/event-emitter.d.ts +7 -0
  60. package/dist/reactive/event-emitter.js +34 -0
  61. package/dist/utils/logger.d.ts +51 -0
  62. package/dist/utils/logger.js +169 -0
  63. package/dist/utils/sql.d.ts +4 -0
  64. package/dist/utils/sql.js +9 -0
  65. package/dist/utils/uuid.d.ts +7 -0
  66. package/dist/utils/uuid.js +27 -0
  67. package/package.json +131 -4
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeSQLiteAdapter = void 0;
4
+ /**
5
+ * Adapter for Node.js 22+ built-in `node:sqlite` (DatabaseSync).
6
+ * Provides zero-dependency, native SQLite on modern Node.js runtimes.
7
+ */
8
+ class NodeSQLiteAdapter {
9
+ db;
10
+ constructor(nodeSqliteDb) {
11
+ this.db = nodeSqliteDb;
12
+ }
13
+ async execute(query, params = []) {
14
+ const trimmed = query.trim();
15
+ const stmt = this.db.prepare(trimmed);
16
+ // Queries that return rows (SELECT, PRAGMA)
17
+ if (/^SELECT|^PRAGMA/i.test(trimmed)) {
18
+ return stmt.all(...params);
19
+ }
20
+ const res = stmt.run(...params);
21
+ return [res];
22
+ }
23
+ async transaction(action) {
24
+ this.db.exec('BEGIN IMMEDIATE');
25
+ try {
26
+ const txWrapper = {
27
+ execute: async (q, p = []) => this.execute(q, p),
28
+ };
29
+ const result = await action(txWrapper);
30
+ this.db.exec('COMMIT');
31
+ return result;
32
+ }
33
+ catch (err) {
34
+ try {
35
+ this.db.exec('ROLLBACK');
36
+ }
37
+ catch {
38
+ // ignore rollback errors
39
+ }
40
+ throw err;
41
+ }
42
+ }
43
+ }
44
+ exports.NodeSQLiteAdapter = NodeSQLiteAdapter;
@@ -0,0 +1,26 @@
1
+ import { LocalDBAdapter, LocalTransaction } from './index.js';
2
+ export interface OpSQLiteQueryResult {
3
+ rows?: {
4
+ _array?: any[];
5
+ length?: number;
6
+ item?: (i: number) => any;
7
+ } | any[];
8
+ insertId?: number;
9
+ rowsAffected?: number;
10
+ }
11
+ export interface OpSQLiteDatabase {
12
+ executeAsync(query: string, params?: any[]): Promise<OpSQLiteQueryResult>;
13
+ transactionAsync(action: (tx: any) => Promise<void>): Promise<void>;
14
+ close?: () => void;
15
+ }
16
+ /**
17
+ * Adapter for @op-engineering/op-sqlite and react-native-quick-sqlite.
18
+ * Enables ultra-fast C++ JSI SQLite in bare React Native.
19
+ */
20
+ export declare class OpSQLiteAdapter implements LocalDBAdapter {
21
+ private db;
22
+ private txMutex;
23
+ constructor(opSqliteInstance: any);
24
+ execute(query: string, params?: any[]): Promise<any[]>;
25
+ transaction<T>(action: (tx: LocalTransaction) => Promise<T>): Promise<T>;
26
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpSQLiteAdapter = void 0;
4
+ function extractRows(res) {
5
+ if (!res)
6
+ return [];
7
+ if (Array.isArray(res))
8
+ return res;
9
+ if (res.rows) {
10
+ if (Array.isArray(res.rows))
11
+ return res.rows;
12
+ if (res.rows._array && Array.isArray(res.rows._array))
13
+ return res.rows._array;
14
+ if (typeof res.rows.length === 'number' && typeof res.rows.item === 'function') {
15
+ const arr = [];
16
+ for (let i = 0; i < res.rows.length; i++) {
17
+ arr.push(res.rows.item(i));
18
+ }
19
+ return arr;
20
+ }
21
+ }
22
+ return [];
23
+ }
24
+ /**
25
+ * Adapter for @op-engineering/op-sqlite and react-native-quick-sqlite.
26
+ * Enables ultra-fast C++ JSI SQLite in bare React Native.
27
+ */
28
+ class OpSQLiteAdapter {
29
+ db;
30
+ txMutex = Promise.resolve();
31
+ constructor(opSqliteInstance) {
32
+ this.db = opSqliteInstance;
33
+ }
34
+ async execute(query, params = []) {
35
+ const res = await this.db.executeAsync(query, params);
36
+ return extractRows(res);
37
+ }
38
+ async transaction(action) {
39
+ const run = async () => {
40
+ let result;
41
+ await this.db.transactionAsync(async (txInstance) => {
42
+ const txWrapper = {
43
+ execute: async (q, p = []) => {
44
+ const res = typeof txInstance.executeAsync === 'function'
45
+ ? await txInstance.executeAsync(q, p)
46
+ : await txInstance.execute(q, p);
47
+ return extractRows(res);
48
+ },
49
+ };
50
+ result = await action(txWrapper);
51
+ });
52
+ return result;
53
+ };
54
+ const currentTx = this.txMutex.then(run, run);
55
+ this.txMutex = currentTx.catch(() => { });
56
+ return await currentTx;
57
+ }
58
+ }
59
+ exports.OpSQLiteAdapter = OpSQLiteAdapter;
@@ -0,0 +1,26 @@
1
+ import { LocalDBAdapter, LocalTransaction } from './index.js';
2
+ export interface SqlJsStatement {
3
+ bind(values?: any[]): boolean;
4
+ step(): boolean;
5
+ getAsObject(params?: any[]): Record<string, any>;
6
+ free(): boolean;
7
+ }
8
+ export interface SqlJsDatabase {
9
+ prepare(sql: string, params?: any[]): SqlJsStatement;
10
+ exec(sql: string, params?: any[]): Array<{
11
+ columns: string[];
12
+ values: any[][];
13
+ }>;
14
+ run(sql: string, params?: any[]): void;
15
+ export?(): Uint8Array;
16
+ close?(): void;
17
+ }
18
+ /**
19
+ * Adapter for sql.js (WebAssembly SQLite in Web Browsers, PWAs, and Expo Web).
20
+ */
21
+ export declare class SqlJsAdapter implements LocalDBAdapter {
22
+ private db;
23
+ constructor(sqlJsDatabase: any);
24
+ execute(query: string, params?: any[]): Promise<any[]>;
25
+ transaction<T>(action: (tx: LocalTransaction) => Promise<T>): Promise<T>;
26
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqlJsAdapter = void 0;
4
+ /**
5
+ * Adapter for sql.js (WebAssembly SQLite in Web Browsers, PWAs, and Expo Web).
6
+ */
7
+ class SqlJsAdapter {
8
+ db;
9
+ constructor(sqlJsDatabase) {
10
+ this.db = sqlJsDatabase;
11
+ }
12
+ async execute(query, params = []) {
13
+ const trimmed = query.trim();
14
+ // If there are no parameters, db.exec is fastest
15
+ if (!params || params.length === 0) {
16
+ const results = this.db.exec(trimmed);
17
+ if (!results || results.length === 0)
18
+ return [];
19
+ const { columns, values } = results[0];
20
+ return values.map((row) => {
21
+ const obj = {};
22
+ columns.forEach((col, idx) => {
23
+ obj[col] = row[idx];
24
+ });
25
+ return obj;
26
+ });
27
+ }
28
+ // For parameterized queries, use Statement with bind & getAsObject
29
+ const stmt = this.db.prepare(trimmed);
30
+ try {
31
+ stmt.bind(params);
32
+ const rows = [];
33
+ while (stmt.step()) {
34
+ rows.push(stmt.getAsObject());
35
+ }
36
+ return rows;
37
+ }
38
+ finally {
39
+ stmt.free();
40
+ }
41
+ }
42
+ async transaction(action) {
43
+ this.db.run('BEGIN TRANSACTION;');
44
+ try {
45
+ const txWrapper = {
46
+ execute: async (q, p = []) => this.execute(q, p),
47
+ };
48
+ const result = await action(txWrapper);
49
+ this.db.run('COMMIT;');
50
+ return result;
51
+ }
52
+ catch (err) {
53
+ try {
54
+ this.db.run('ROLLBACK;');
55
+ }
56
+ catch {
57
+ // ignore rollback errors
58
+ }
59
+ throw err;
60
+ }
61
+ }
62
+ }
63
+ exports.SqlJsAdapter = SqlJsAdapter;
@@ -0,0 +1,11 @@
1
+ import { NetworkStatusProvider } from '../../core/types.js';
2
+ export declare class DefaultNetworkStatusProvider implements NetworkStatusProvider {
3
+ private _isOnline;
4
+ private listeners;
5
+ private cleanupBrowserListeners?;
6
+ constructor(initialOnline?: boolean);
7
+ isOnline(): boolean;
8
+ setOnline(status: boolean): void;
9
+ subscribe(callback: (isOnline: boolean) => void): () => void;
10
+ destroy(): void;
11
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultNetworkStatusProvider = void 0;
4
+ class DefaultNetworkStatusProvider {
5
+ _isOnline = true;
6
+ listeners = new Set();
7
+ cleanupBrowserListeners;
8
+ constructor(initialOnline = true) {
9
+ this._isOnline = initialOnline;
10
+ if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
11
+ if (typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean') {
12
+ this._isOnline = navigator.onLine;
13
+ }
14
+ const onOnline = () => this.setOnline(true);
15
+ const onOffline = () => this.setOnline(false);
16
+ window.addEventListener('online', onOnline);
17
+ window.addEventListener('offline', onOffline);
18
+ this.cleanupBrowserListeners = () => {
19
+ window.removeEventListener('online', onOnline);
20
+ window.removeEventListener('offline', onOffline);
21
+ };
22
+ }
23
+ }
24
+ isOnline() {
25
+ return this._isOnline;
26
+ }
27
+ setOnline(status) {
28
+ if (this._isOnline !== status) {
29
+ this._isOnline = status;
30
+ for (const listener of this.listeners) {
31
+ try {
32
+ listener(status);
33
+ }
34
+ catch (err) {
35
+ console.error('Error in network listener:', err);
36
+ }
37
+ }
38
+ }
39
+ }
40
+ subscribe(callback) {
41
+ this.listeners.add(callback);
42
+ return () => {
43
+ this.listeners.delete(callback);
44
+ };
45
+ }
46
+ destroy() {
47
+ if (this.cleanupBrowserListeners) {
48
+ this.cleanupBrowserListeners();
49
+ }
50
+ this.listeners.clear();
51
+ }
52
+ }
53
+ exports.DefaultNetworkStatusProvider = DefaultNetworkStatusProvider;
@@ -0,0 +1,71 @@
1
+ import { RemoteDBAdapter } from './index.js';
2
+ import { DeltaChanges, SyncOperation } from '../../core/types.js';
3
+ import { SyncLogger, CustomLogger, LogLevel } from '../../utils/logger.js';
4
+ export interface ConvexRemoteAdapterOptions {
5
+ /**
6
+ * Tables synchronized with Convex.
7
+ */
8
+ tables?: string[];
9
+ /**
10
+ * Primary key field name. Default: 'id' (or falls back to '_id').
11
+ */
12
+ primaryKey?: string;
13
+ /**
14
+ * Name or function reference for Convex push mutation.
15
+ * Default: 'sync:pushBatch'
16
+ */
17
+ pushMutation?: any;
18
+ /**
19
+ * Name or function reference for Convex delta pull query.
20
+ * Default: 'sync:pullDeltas'
21
+ */
22
+ pullQuery?: any;
23
+ /**
24
+ * Column used for soft deletes. Default: 'deleted'
25
+ */
26
+ deletedColumn?: string;
27
+ /**
28
+ * Column used for updated timestamps. Default: 'updatedAt'
29
+ */
30
+ updatedAtColumn?: string;
31
+ /**
32
+ * Custom logger or false to silence logs in production.
33
+ */
34
+ logger?: SyncLogger | CustomLogger | boolean;
35
+ /**
36
+ * Minimum log level. Default: 'info'
37
+ */
38
+ logLevel?: LogLevel;
39
+ }
40
+ export declare class ConvexRemoteAdapter implements RemoteDBAdapter {
41
+ private client;
42
+ private tables;
43
+ private primaryKey;
44
+ private pushMutation;
45
+ private pullQuery;
46
+ private deletedColumn;
47
+ private updatedAtColumn;
48
+ private unsubscribeLive?;
49
+ logger: SyncLogger;
50
+ constructor(client: any, options?: ConvexRemoteAdapterOptions);
51
+ /**
52
+ * Dynamically update tracked tables.
53
+ */
54
+ setTables(tables: string[]): void;
55
+ /**
56
+ * Pushes outbox operations as a batch mutation to Convex.
57
+ */
58
+ pushChanges(operations: SyncOperation[]): Promise<boolean>;
59
+ /**
60
+ * Fetches delta changes from Convex since a given timestamp.
61
+ */
62
+ fetchChanges(sinceTimestamp: number): Promise<DeltaChanges>;
63
+ /**
64
+ * Subscribes to live reactive query updates from Convex.
65
+ */
66
+ subscribeToLiveChanges(callback: (changes: DeltaChanges) => void): void;
67
+ /**
68
+ * Cleans up live subscriptions.
69
+ */
70
+ destroy(): void;
71
+ }
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConvexRemoteAdapter = void 0;
4
+ const logger_js_1 = require("../../utils/logger.js");
5
+ class ConvexRemoteAdapter {
6
+ client;
7
+ tables;
8
+ primaryKey;
9
+ pushMutation;
10
+ pullQuery;
11
+ deletedColumn;
12
+ updatedAtColumn;
13
+ unsubscribeLive;
14
+ logger;
15
+ constructor(client, options = {}) {
16
+ if (!client) {
17
+ throw new Error('ConvexRemoteAdapter requires a ConvexClient instance.');
18
+ }
19
+ this.client = client;
20
+ this.tables = options.tables ?? [];
21
+ this.primaryKey = options.primaryKey ?? 'id';
22
+ this.pushMutation = options.pushMutation ?? 'sync:pushBatch';
23
+ this.pullQuery = options.pullQuery ?? 'sync:pullDeltas';
24
+ this.deletedColumn = options.deletedColumn ?? 'deleted';
25
+ this.updatedAtColumn = options.updatedAtColumn ?? 'updatedAt';
26
+ if (options.logger instanceof logger_js_1.SyncLogger) {
27
+ this.logger = options.logger;
28
+ }
29
+ else if (typeof options.logger === 'boolean') {
30
+ this.logger = new logger_js_1.SyncLogger({ enabled: options.logger, level: options.logLevel });
31
+ }
32
+ else if (options.logger && typeof options.logger === 'object') {
33
+ this.logger = new logger_js_1.SyncLogger({ customLogger: options.logger, level: options.logLevel });
34
+ }
35
+ else {
36
+ this.logger = options.logLevel ? new logger_js_1.SyncLogger({ level: options.logLevel }) : logger_js_1.defaultLogger;
37
+ }
38
+ }
39
+ /**
40
+ * Dynamically update tracked tables.
41
+ */
42
+ setTables(tables) {
43
+ this.tables = tables;
44
+ }
45
+ /**
46
+ * Pushes outbox operations as a batch mutation to Convex.
47
+ */
48
+ async pushChanges(operations) {
49
+ if (!operations || operations.length === 0) {
50
+ return true;
51
+ }
52
+ try {
53
+ const payload = operations.map((op) => ({
54
+ id: op.id,
55
+ tableName: op.tableName,
56
+ rowId: op.rowId,
57
+ operation: op.operation,
58
+ payload: op.payload,
59
+ timestamp: op.timestamp,
60
+ }));
61
+ const res = await this.client.mutation(this.pushMutation, {
62
+ operations: payload,
63
+ });
64
+ return res !== false;
65
+ }
66
+ catch (error) {
67
+ this.logger.error((0, logger_js_1.formatConvexError)(error, { operation: 'pushChanges' }));
68
+ return false;
69
+ }
70
+ }
71
+ /**
72
+ * Fetches delta changes from Convex since a given timestamp.
73
+ */
74
+ async fetchChanges(sinceTimestamp) {
75
+ try {
76
+ const res = await this.client.query(this.pullQuery, {
77
+ sinceTimestamp,
78
+ tables: this.tables,
79
+ });
80
+ if (!res) {
81
+ return { timestamp: sinceTimestamp, changes: [] };
82
+ }
83
+ // 1. If backend returns structured DeltaChanges { timestamp, changes }
84
+ if (res.changes && Array.isArray(res.changes)) {
85
+ return {
86
+ timestamp: Number(res.timestamp || sinceTimestamp),
87
+ changes: res.changes,
88
+ };
89
+ }
90
+ // 2. If backend returns an array of updated documents
91
+ if (Array.isArray(res)) {
92
+ let maxServerTimestamp = sinceTimestamp;
93
+ const changes = [];
94
+ for (const row of res) {
95
+ const isSoftDeleted = Boolean(row[this.deletedColumn] === true ||
96
+ row[this.deletedColumn] === 1 ||
97
+ (row[this.deletedColumn] &&
98
+ !['false', '0', 'null', 'undefined'].includes(String(row[this.deletedColumn]).toLowerCase())));
99
+ const operation = isSoftDeleted ? 'DELETE' : 'UPDATE';
100
+ const rowTime = Number(row[this.updatedAtColumn] || row._creationTime || Date.now());
101
+ if (rowTime > maxServerTimestamp) {
102
+ maxServerTimestamp = rowTime;
103
+ }
104
+ const rowId = String(row[this.primaryKey] || row._id || row.id);
105
+ const tableName = row.tableName || row._table || (this.tables.length === 1 ? this.tables[0] : 'default');
106
+ changes.push({
107
+ tableName,
108
+ operation,
109
+ rowId,
110
+ payload: isSoftDeleted ? null : row,
111
+ timestamp: rowTime,
112
+ });
113
+ }
114
+ return {
115
+ timestamp: changes.length > 0 ? maxServerTimestamp : sinceTimestamp,
116
+ changes,
117
+ };
118
+ }
119
+ return { timestamp: sinceTimestamp, changes: [] };
120
+ }
121
+ catch (error) {
122
+ this.logger.error((0, logger_js_1.formatConvexError)(error, { operation: 'fetchChanges' }));
123
+ return { timestamp: sinceTimestamp, changes: [] };
124
+ }
125
+ }
126
+ /**
127
+ * Subscribes to live reactive query updates from Convex.
128
+ */
129
+ subscribeToLiveChanges(callback) {
130
+ if (typeof this.client.onUpdate !== 'function') {
131
+ return;
132
+ }
133
+ if (this.unsubscribeLive) {
134
+ this.unsubscribeLive();
135
+ this.unsubscribeLive = undefined;
136
+ }
137
+ try {
138
+ this.unsubscribeLive = this.client.onUpdate(this.pullQuery, { sinceTimestamp: Date.now() - 2000, tables: this.tables }, (data) => {
139
+ if (!data)
140
+ return;
141
+ if (data.changes && Array.isArray(data.changes)) {
142
+ callback({
143
+ timestamp: Number(data.timestamp || Date.now()),
144
+ changes: data.changes,
145
+ });
146
+ return;
147
+ }
148
+ if (Array.isArray(data) && data.length > 0) {
149
+ const changes = data.map((row) => {
150
+ const isSoftDeleted = Boolean(row[this.deletedColumn] === true);
151
+ const op = isSoftDeleted ? 'DELETE' : 'UPDATE';
152
+ const rowTime = Number(row[this.updatedAtColumn] || row._creationTime || Date.now());
153
+ const rowId = String(row[this.primaryKey] || row._id || row.id);
154
+ const tableName = row.tableName || row._table || (this.tables[0] ?? 'default');
155
+ return {
156
+ tableName,
157
+ operation: op,
158
+ rowId,
159
+ payload: isSoftDeleted ? null : row,
160
+ timestamp: rowTime,
161
+ };
162
+ });
163
+ const maxTime = Math.max(...changes.map((c) => c.timestamp || 0));
164
+ callback({
165
+ timestamp: maxTime > 0 ? maxTime : Date.now(),
166
+ changes,
167
+ });
168
+ }
169
+ });
170
+ }
171
+ catch (err) {
172
+ console.warn('ConvexRemoteAdapter live subscription failed to initialize:', err);
173
+ }
174
+ }
175
+ /**
176
+ * Cleans up live subscriptions.
177
+ */
178
+ destroy() {
179
+ if (this.unsubscribeLive) {
180
+ this.unsubscribeLive();
181
+ this.unsubscribeLive = undefined;
182
+ }
183
+ }
184
+ }
185
+ exports.ConvexRemoteAdapter = ConvexRemoteAdapter;
@@ -0,0 +1,21 @@
1
+ import { RemoteDBAdapter } from './index.js';
2
+ import { DeltaChanges, SyncOperation } from '../../core/types.js';
3
+ /**
4
+ * Built-in In-Memory Demo Remote Adapter.
5
+ * Useful for prototyping, offline testing, and previewing without a live backend.
6
+ */
7
+ export declare class MemoryDemoRemoteAdapter implements RemoteDBAdapter {
8
+ pushedBatches: SyncOperation[][];
9
+ allPushedOperations: SyncOperation[];
10
+ private liveSubscribers;
11
+ private simulatedLatencyMs;
12
+ constructor(simulatedLatencyMs?: number);
13
+ pushChanges(operations: SyncOperation[]): Promise<boolean>;
14
+ fetchChanges(_sinceTimestamp: number): Promise<DeltaChanges>;
15
+ subscribeToLiveChanges(callback: (changes: DeltaChanges) => void): void;
16
+ /**
17
+ * Manually emit simulated server deltas to subscribers.
18
+ */
19
+ emitLiveChanges(changes: DeltaChanges): void;
20
+ clear(): void;
21
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryDemoRemoteAdapter = void 0;
4
+ /**
5
+ * Built-in In-Memory Demo Remote Adapter.
6
+ * Useful for prototyping, offline testing, and previewing without a live backend.
7
+ */
8
+ class MemoryDemoRemoteAdapter {
9
+ pushedBatches = [];
10
+ allPushedOperations = [];
11
+ liveSubscribers = [];
12
+ simulatedLatencyMs;
13
+ constructor(simulatedLatencyMs = 300) {
14
+ this.simulatedLatencyMs = simulatedLatencyMs;
15
+ }
16
+ async pushChanges(operations) {
17
+ if (this.simulatedLatencyMs > 0) {
18
+ await new Promise((resolve) => setTimeout(resolve, this.simulatedLatencyMs));
19
+ }
20
+ this.pushedBatches.push(operations);
21
+ this.allPushedOperations.push(...operations);
22
+ return true;
23
+ }
24
+ async fetchChanges(_sinceTimestamp) {
25
+ return {
26
+ timestamp: Date.now(),
27
+ changes: [],
28
+ };
29
+ }
30
+ subscribeToLiveChanges(callback) {
31
+ this.liveSubscribers.push(callback);
32
+ }
33
+ /**
34
+ * Manually emit simulated server deltas to subscribers.
35
+ */
36
+ emitLiveChanges(changes) {
37
+ for (const sub of this.liveSubscribers) {
38
+ sub(changes);
39
+ }
40
+ }
41
+ clear() {
42
+ this.pushedBatches = [];
43
+ this.allPushedOperations = [];
44
+ }
45
+ }
46
+ exports.MemoryDemoRemoteAdapter = MemoryDemoRemoteAdapter;
@@ -0,0 +1,64 @@
1
+ import { RemoteDBAdapter } from './index.js';
2
+ import { DeltaChanges, SyncOperation } from '../../core/types.js';
3
+ import { SyncLogger, CustomLogger, LogLevel } from '../../utils/logger.js';
4
+ export interface FirestoreAdapterOptions {
5
+ /**
6
+ * Collections (tables) synchronized with Firestore.
7
+ * e.g. ['properties', 'tenants']
8
+ */
9
+ tables?: string[];
10
+ /**
11
+ * Primary key field name in documents. Default: 'id'
12
+ */
13
+ primaryKey?: string;
14
+ /**
15
+ * Field used for soft deletes. Default: 'deleted'
16
+ */
17
+ deletedColumn?: string;
18
+ /**
19
+ * Field used for updated timestamp tracking. Default: 'updatedAt'
20
+ */
21
+ updatedAtColumn?: string;
22
+ /**
23
+ * Optional reference to modular firebase/firestore SDK if not globally available.
24
+ */
25
+ firestoreModule?: any;
26
+ /**
27
+ * Custom logger or false to silence logs in production.
28
+ */
29
+ logger?: SyncLogger | CustomLogger | boolean;
30
+ /**
31
+ * Minimum log level. Default: 'info'
32
+ */
33
+ logLevel?: LogLevel;
34
+ }
35
+ export declare class FirestoreRemoteAdapter implements RemoteDBAdapter {
36
+ private db;
37
+ private tables;
38
+ private primaryKey;
39
+ private deletedColumn;
40
+ private updatedAtColumn;
41
+ private firestoreModule;
42
+ private unsubscribers;
43
+ logger: SyncLogger;
44
+ constructor(db: any, options?: FirestoreAdapterOptions);
45
+ setTables(tables: string[]): void;
46
+ private getModule;
47
+ /**
48
+ * Pushes outbox operations as batch writes to Firestore.
49
+ * Chunks operations into batches of 450 (below Firestore 500-write limit).
50
+ */
51
+ pushChanges(operations: SyncOperation[]): Promise<boolean>;
52
+ /**
53
+ * Fetches delta changes across tracked collections where updatedAt > sinceTimestamp.
54
+ */
55
+ fetchChanges(sinceTimestamp: number): Promise<DeltaChanges>;
56
+ /**
57
+ * Subscribes to live real-time query updates via onSnapshot.
58
+ */
59
+ subscribeToLiveChanges(callback: (changes: DeltaChanges) => void): void;
60
+ /**
61
+ * Cleans up all active real-time listeners.
62
+ */
63
+ destroy(): void;
64
+ }