local-lite 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +349 -2
- package/dist/adapters/local/better-sqlite3.d.ts +23 -0
- package/dist/adapters/local/better-sqlite3.js +46 -0
- package/dist/adapters/local/expo-sqlite.d.ts +8 -0
- package/dist/adapters/local/expo-sqlite.js +30 -0
- package/dist/adapters/local/index.d.ts +13 -0
- package/dist/adapters/local/index.js +22 -0
- package/dist/adapters/local/libsql.d.ts +24 -0
- package/dist/adapters/local/libsql.js +87 -0
- package/dist/adapters/local/node-sqlite.d.ts +24 -0
- package/dist/adapters/local/node-sqlite.js +44 -0
- package/dist/adapters/local/op-sqlite.d.ts +26 -0
- package/dist/adapters/local/op-sqlite.js +59 -0
- package/dist/adapters/local/sql-js.d.ts +26 -0
- package/dist/adapters/local/sql-js.js +63 -0
- package/dist/adapters/network/index.d.ts +11 -0
- package/dist/adapters/network/index.js +53 -0
- package/dist/adapters/remote/convex.d.ts +71 -0
- package/dist/adapters/remote/convex.js +185 -0
- package/dist/adapters/remote/demo.d.ts +21 -0
- package/dist/adapters/remote/demo.js +46 -0
- package/dist/adapters/remote/firestore.d.ts +64 -0
- package/dist/adapters/remote/firestore.js +302 -0
- package/dist/adapters/remote/index.d.ts +10 -0
- package/dist/adapters/remote/index.js +20 -0
- package/dist/adapters/remote/supabase.d.ts +97 -0
- package/dist/adapters/remote/supabase.js +276 -0
- package/dist/config/index.d.ts +24 -0
- package/dist/config/index.js +101 -0
- package/dist/config/resolve-local.d.ts +7 -0
- package/dist/config/resolve-local.js +68 -0
- package/dist/config/resolve-remote.d.ts +7 -0
- package/dist/config/resolve-remote.js +81 -0
- package/dist/config/types.d.ts +159 -0
- package/dist/config/types.js +2 -0
- package/dist/core/client.d.ts +100 -0
- package/dist/core/client.js +273 -0
- package/dist/core/conflict.d.ts +5 -0
- package/dist/core/conflict.js +38 -0
- package/dist/core/meta-store.d.ts +28 -0
- package/dist/core/meta-store.js +64 -0
- package/dist/core/outbox-store.d.ts +45 -0
- package/dist/core/outbox-store.js +141 -0
- package/dist/core/schema-manager.d.ts +39 -0
- package/dist/core/schema-manager.js +150 -0
- package/dist/core/sync-worker.d.ts +74 -0
- package/dist/core/sync-worker.js +335 -0
- package/dist/core/table-manager.d.ts +19 -0
- package/dist/core/table-manager.js +74 -0
- package/dist/core/types.d.ts +73 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +33 -0
- package/dist/react/index.d.ts +34 -0
- package/dist/react/index.js +103 -0
- package/dist/reactive/event-emitter.d.ts +7 -0
- package/dist/reactive/event-emitter.js +34 -0
- package/dist/utils/logger.d.ts +51 -0
- package/dist/utils/logger.js +169 -0
- package/dist/utils/sql.d.ts +4 -0
- package/dist/utils/sql.js +9 -0
- package/dist/utils/uuid.d.ts +7 -0
- package/dist/utils/uuid.js +27 -0
- package/package.json +130 -3
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SupabaseRemoteAdapter = void 0;
|
|
4
|
+
const logger_js_1 = require("../../utils/logger.js");
|
|
5
|
+
class SupabaseRemoteAdapter {
|
|
6
|
+
client;
|
|
7
|
+
tables;
|
|
8
|
+
updatedAtColumn;
|
|
9
|
+
primaryKey;
|
|
10
|
+
deletedColumn;
|
|
11
|
+
deletedValue;
|
|
12
|
+
pushRpcName;
|
|
13
|
+
schema;
|
|
14
|
+
channelName;
|
|
15
|
+
activeChannel;
|
|
16
|
+
logger;
|
|
17
|
+
constructor(client, options = {}) {
|
|
18
|
+
this.client = client;
|
|
19
|
+
this.tables = options.tables ?? [];
|
|
20
|
+
this.updatedAtColumn = options.updatedAtColumn ?? 'updated_at';
|
|
21
|
+
this.primaryKey = options.primaryKey ?? 'id';
|
|
22
|
+
this.deletedColumn = options.deletedColumn ?? options.deletedAtColumn ?? 'deleted';
|
|
23
|
+
this.deletedValue = options.deletedValue !== undefined ? options.deletedValue : true;
|
|
24
|
+
this.pushRpcName = options.pushRpcName;
|
|
25
|
+
this.schema = options.schema ?? 'public';
|
|
26
|
+
this.channelName = options.channelName ?? 'local_first_sync';
|
|
27
|
+
if (options.logger instanceof logger_js_1.SyncLogger) {
|
|
28
|
+
this.logger = options.logger;
|
|
29
|
+
}
|
|
30
|
+
else if (typeof options.logger === 'boolean') {
|
|
31
|
+
this.logger = new logger_js_1.SyncLogger({ enabled: options.logger, level: options.logLevel });
|
|
32
|
+
}
|
|
33
|
+
else if (options.logger && typeof options.logger === 'object') {
|
|
34
|
+
this.logger = new logger_js_1.SyncLogger({ customLogger: options.logger, level: options.logLevel });
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.logger = options.logLevel ? new logger_js_1.SyncLogger({ level: options.logLevel }) : logger_js_1.defaultLogger;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Set or update tracked tables dynamically.
|
|
42
|
+
*/
|
|
43
|
+
setTables(tables) {
|
|
44
|
+
this.tables = tables;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pushes outbox operations to Supabase.
|
|
48
|
+
*/
|
|
49
|
+
async pushChanges(operations) {
|
|
50
|
+
if (!operations || operations.length === 0) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
// 1. If custom RPC is configured, perform atomic batch RPC
|
|
54
|
+
if (this.pushRpcName) {
|
|
55
|
+
const { error } = await this.client.rpc(this.pushRpcName, {
|
|
56
|
+
operations: operations.map((op) => ({
|
|
57
|
+
id: op.id,
|
|
58
|
+
table_name: op.tableName,
|
|
59
|
+
row_id: op.rowId,
|
|
60
|
+
operation: op.operation,
|
|
61
|
+
payload: op.payload,
|
|
62
|
+
timestamp: op.timestamp,
|
|
63
|
+
})),
|
|
64
|
+
});
|
|
65
|
+
if (error) {
|
|
66
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(error, { operation: 'batch_rpc' }));
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// 2. Direct table operations
|
|
72
|
+
for (const op of operations) {
|
|
73
|
+
try {
|
|
74
|
+
if (op.operation === 'DELETE') {
|
|
75
|
+
// Soft delete: sets deleted = true and stamps updated_at
|
|
76
|
+
const patch = {
|
|
77
|
+
[this.deletedColumn]: this.deletedValue,
|
|
78
|
+
};
|
|
79
|
+
if (this.updatedAtColumn) {
|
|
80
|
+
patch[this.updatedAtColumn] = new Date(op.timestamp).toISOString();
|
|
81
|
+
}
|
|
82
|
+
const { error } = await this.client
|
|
83
|
+
.from(op.tableName)
|
|
84
|
+
.update(patch)
|
|
85
|
+
.eq(this.primaryKey, op.rowId);
|
|
86
|
+
if (error) {
|
|
87
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(error, { table: op.tableName, operation: 'DELETE' }));
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (op.operation === 'UPDATE') {
|
|
92
|
+
// Partial UPDATE on existing record: only updates the modified columns to respect NOT NULL constraints
|
|
93
|
+
const patch = {
|
|
94
|
+
...(op.payload || {}),
|
|
95
|
+
[this.deletedColumn]: false,
|
|
96
|
+
};
|
|
97
|
+
if (this.updatedAtColumn) {
|
|
98
|
+
patch[this.updatedAtColumn] = new Date(op.timestamp).toISOString();
|
|
99
|
+
}
|
|
100
|
+
const { error, data } = await this.client
|
|
101
|
+
.from(op.tableName)
|
|
102
|
+
.update(patch)
|
|
103
|
+
.eq(this.primaryKey, op.rowId)
|
|
104
|
+
.select(this.primaryKey);
|
|
105
|
+
if (error) {
|
|
106
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(error, { table: op.tableName, operation: 'UPDATE' }));
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
// If row did not exist remotely (e.g. created & modified offline), fallback to upsert
|
|
110
|
+
if (!data || data.length === 0) {
|
|
111
|
+
const rowData = {
|
|
112
|
+
...patch,
|
|
113
|
+
[this.primaryKey]: op.rowId,
|
|
114
|
+
};
|
|
115
|
+
const { error: upsertErr } = await this.client
|
|
116
|
+
.from(op.tableName)
|
|
117
|
+
.upsert(rowData);
|
|
118
|
+
if (upsertErr) {
|
|
119
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(upsertErr, { table: op.tableName, operation: 'UPSERT_FALLBACK' }));
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// INSERT -> upsert into table with deleted: false
|
|
126
|
+
const rowData = {
|
|
127
|
+
...(op.payload || {}),
|
|
128
|
+
[this.primaryKey]: op.rowId,
|
|
129
|
+
[this.deletedColumn]: false,
|
|
130
|
+
};
|
|
131
|
+
if (this.updatedAtColumn && !rowData[this.updatedAtColumn]) {
|
|
132
|
+
rowData[this.updatedAtColumn] = new Date(op.timestamp).toISOString();
|
|
133
|
+
}
|
|
134
|
+
const { error } = await this.client
|
|
135
|
+
.from(op.tableName)
|
|
136
|
+
.upsert(rowData);
|
|
137
|
+
if (error) {
|
|
138
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(error, { table: op.tableName, operation: 'INSERT' }));
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(err, { table: op.tableName, operation: op.operation }));
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Fetches delta changes from Supabase since a given timestamp.
|
|
152
|
+
* Reads updated records and checks `deleted: true` to propagate soft deletes.
|
|
153
|
+
*/
|
|
154
|
+
async fetchChanges(sinceTimestamp) {
|
|
155
|
+
const changes = [];
|
|
156
|
+
const sinceIso = sinceTimestamp > 0 ? new Date(sinceTimestamp).toISOString() : null;
|
|
157
|
+
let maxServerTimestamp = sinceTimestamp;
|
|
158
|
+
for (const table of this.tables) {
|
|
159
|
+
try {
|
|
160
|
+
let query = this.client
|
|
161
|
+
.from(table)
|
|
162
|
+
.select('*');
|
|
163
|
+
if (sinceIso) {
|
|
164
|
+
query = query.gt(this.updatedAtColumn, sinceIso);
|
|
165
|
+
}
|
|
166
|
+
if (typeof query.order === 'function') {
|
|
167
|
+
query = query.order(this.updatedAtColumn, { ascending: true });
|
|
168
|
+
}
|
|
169
|
+
const { data, error } = await query;
|
|
170
|
+
if (error) {
|
|
171
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(error, { table, operation: 'PULL' }));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (data && Array.isArray(data)) {
|
|
175
|
+
for (const row of data) {
|
|
176
|
+
const isSoftDeleted = this.isSoftDeleted(row);
|
|
177
|
+
const operation = isSoftDeleted ? 'DELETE' : 'UPDATE';
|
|
178
|
+
const rowTime = row[this.updatedAtColumn]
|
|
179
|
+
? new Date(row[this.updatedAtColumn]).getTime()
|
|
180
|
+
: Date.now();
|
|
181
|
+
if (rowTime > maxServerTimestamp) {
|
|
182
|
+
maxServerTimestamp = rowTime;
|
|
183
|
+
}
|
|
184
|
+
changes.push({
|
|
185
|
+
tableName: table,
|
|
186
|
+
operation,
|
|
187
|
+
rowId: String(row[this.primaryKey]),
|
|
188
|
+
payload: isSoftDeleted ? null : row,
|
|
189
|
+
timestamp: rowTime,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
this.logger.error((0, logger_js_1.formatSupabaseError)(err, { table, operation: 'PULL' }));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
timestamp: changes.length > 0 ? maxServerTimestamp : sinceTimestamp,
|
|
200
|
+
changes,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Helper to determine if a record represents a soft delete.
|
|
205
|
+
*/
|
|
206
|
+
isSoftDeleted(record) {
|
|
207
|
+
if (!record)
|
|
208
|
+
return false;
|
|
209
|
+
const val = record[this.deletedColumn];
|
|
210
|
+
if (val === true || val === 1)
|
|
211
|
+
return true;
|
|
212
|
+
if (val && !['false', '0', 'null', 'undefined'].includes(String(val).toLowerCase())) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Subscribes to live realtime postgres_changes from Supabase.
|
|
219
|
+
*/
|
|
220
|
+
subscribeToLiveChanges(callback) {
|
|
221
|
+
if (this.activeChannel) {
|
|
222
|
+
this.client.removeChannel(this.activeChannel);
|
|
223
|
+
this.activeChannel = undefined;
|
|
224
|
+
}
|
|
225
|
+
if (this.tables.length === 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const channel = this.client.channel(this.channelName);
|
|
229
|
+
for (const table of this.tables) {
|
|
230
|
+
channel.on('postgres_changes', { event: '*', schema: this.schema, table }, (payload) => {
|
|
231
|
+
let op = 'UPDATE';
|
|
232
|
+
if (payload.eventType === 'INSERT')
|
|
233
|
+
op = 'INSERT';
|
|
234
|
+
else if (payload.eventType === 'DELETE')
|
|
235
|
+
op = 'DELETE';
|
|
236
|
+
const record = op === 'DELETE' ? payload.old : payload.new;
|
|
237
|
+
if (!record)
|
|
238
|
+
return;
|
|
239
|
+
const isSoftDeleted = op !== 'DELETE' && this.isSoftDeleted(record);
|
|
240
|
+
const finalOp = isSoftDeleted ? 'DELETE' : op;
|
|
241
|
+
const rowId = String(record[this.primaryKey]);
|
|
242
|
+
const timestamp = record[this.updatedAtColumn]
|
|
243
|
+
? new Date(record[this.updatedAtColumn]).getTime()
|
|
244
|
+
: Date.now();
|
|
245
|
+
callback({
|
|
246
|
+
timestamp,
|
|
247
|
+
changes: [
|
|
248
|
+
{
|
|
249
|
+
tableName: table,
|
|
250
|
+
operation: finalOp,
|
|
251
|
+
rowId,
|
|
252
|
+
payload: finalOp === 'DELETE' ? null : record,
|
|
253
|
+
timestamp,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
channel.subscribe((status) => {
|
|
260
|
+
if (status === 'SUBSCRIBED') {
|
|
261
|
+
// Successfully subscribed to Supabase realtime
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
this.activeChannel = channel;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Clean up active realtime channels.
|
|
268
|
+
*/
|
|
269
|
+
destroy() {
|
|
270
|
+
if (this.activeChannel) {
|
|
271
|
+
this.client.removeChannel(this.activeChannel);
|
|
272
|
+
this.activeChannel = undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
exports.SupabaseRemoteAdapter = SupabaseRemoteAdapter;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { LocalFirstClient } from '../core/client.js';
|
|
2
|
+
import { LocalLiteConfig } from './types.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export { resolveLocalAdapter } from './resolve-local.js';
|
|
5
|
+
export { resolveRemoteAdapter } from './resolve-remote.js';
|
|
6
|
+
export { LocalFirstClient } from '../core/client.js';
|
|
7
|
+
export { ExpoSQLiteAdapter } from '../adapters/local/expo-sqlite.js';
|
|
8
|
+
export { OpSQLiteAdapter } from '../adapters/local/op-sqlite.js';
|
|
9
|
+
export { BetterSQLite3Adapter } from '../adapters/local/better-sqlite3.js';
|
|
10
|
+
export { LibSQLAdapter } from '../adapters/local/libsql.js';
|
|
11
|
+
export { SqlJsAdapter } from '../adapters/local/sql-js.js';
|
|
12
|
+
export { NodeSQLiteAdapter } from '../adapters/local/node-sqlite.js';
|
|
13
|
+
export { SupabaseRemoteAdapter } from '../adapters/remote/supabase.js';
|
|
14
|
+
export { ConvexRemoteAdapter } from '../adapters/remote/convex.js';
|
|
15
|
+
export { FirestoreRemoteAdapter } from '../adapters/remote/firestore.js';
|
|
16
|
+
export { MemoryDemoRemoteAdapter } from '../adapters/remote/demo.js';
|
|
17
|
+
/**
|
|
18
|
+
* Type-safe configuration helper function (similar to defineConfig in Vite/Tailwind).
|
|
19
|
+
*/
|
|
20
|
+
export declare function defineSyncConfig(config: LocalLiteConfig): LocalLiteConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Factory that instantiates, wires up, and initializes a LocalFirstClient from a LocalLiteConfig.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createSyncClient(config: LocalLiteConfig): Promise<LocalFirstClient>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.MemoryDemoRemoteAdapter = exports.FirestoreRemoteAdapter = exports.ConvexRemoteAdapter = exports.SupabaseRemoteAdapter = exports.NodeSQLiteAdapter = exports.SqlJsAdapter = exports.LibSQLAdapter = exports.BetterSQLite3Adapter = exports.OpSQLiteAdapter = exports.ExpoSQLiteAdapter = exports.LocalFirstClient = exports.resolveRemoteAdapter = exports.resolveLocalAdapter = void 0;
|
|
18
|
+
exports.defineSyncConfig = defineSyncConfig;
|
|
19
|
+
exports.createSyncClient = createSyncClient;
|
|
20
|
+
const client_js_1 = require("../core/client.js");
|
|
21
|
+
const index_js_1 = require("../adapters/network/index.js");
|
|
22
|
+
const resolve_local_js_1 = require("./resolve-local.js");
|
|
23
|
+
const resolve_remote_js_1 = require("./resolve-remote.js");
|
|
24
|
+
// Export all config types
|
|
25
|
+
__exportStar(require("./types.js"), exports);
|
|
26
|
+
// Export adapter resolvers
|
|
27
|
+
var resolve_local_js_2 = require("./resolve-local.js");
|
|
28
|
+
Object.defineProperty(exports, "resolveLocalAdapter", { enumerable: true, get: function () { return resolve_local_js_2.resolveLocalAdapter; } });
|
|
29
|
+
var resolve_remote_js_2 = require("./resolve-remote.js");
|
|
30
|
+
Object.defineProperty(exports, "resolveRemoteAdapter", { enumerable: true, get: function () { return resolve_remote_js_2.resolveRemoteAdapter; } });
|
|
31
|
+
// Re-export adapters and core client for convenience
|
|
32
|
+
var client_js_2 = require("../core/client.js");
|
|
33
|
+
Object.defineProperty(exports, "LocalFirstClient", { enumerable: true, get: function () { return client_js_2.LocalFirstClient; } });
|
|
34
|
+
var expo_sqlite_js_1 = require("../adapters/local/expo-sqlite.js");
|
|
35
|
+
Object.defineProperty(exports, "ExpoSQLiteAdapter", { enumerable: true, get: function () { return expo_sqlite_js_1.ExpoSQLiteAdapter; } });
|
|
36
|
+
var op_sqlite_js_1 = require("../adapters/local/op-sqlite.js");
|
|
37
|
+
Object.defineProperty(exports, "OpSQLiteAdapter", { enumerable: true, get: function () { return op_sqlite_js_1.OpSQLiteAdapter; } });
|
|
38
|
+
var better_sqlite3_js_1 = require("../adapters/local/better-sqlite3.js");
|
|
39
|
+
Object.defineProperty(exports, "BetterSQLite3Adapter", { enumerable: true, get: function () { return better_sqlite3_js_1.BetterSQLite3Adapter; } });
|
|
40
|
+
var libsql_js_1 = require("../adapters/local/libsql.js");
|
|
41
|
+
Object.defineProperty(exports, "LibSQLAdapter", { enumerable: true, get: function () { return libsql_js_1.LibSQLAdapter; } });
|
|
42
|
+
var sql_js_js_1 = require("../adapters/local/sql-js.js");
|
|
43
|
+
Object.defineProperty(exports, "SqlJsAdapter", { enumerable: true, get: function () { return sql_js_js_1.SqlJsAdapter; } });
|
|
44
|
+
var node_sqlite_js_1 = require("../adapters/local/node-sqlite.js");
|
|
45
|
+
Object.defineProperty(exports, "NodeSQLiteAdapter", { enumerable: true, get: function () { return node_sqlite_js_1.NodeSQLiteAdapter; } });
|
|
46
|
+
var supabase_js_1 = require("../adapters/remote/supabase.js");
|
|
47
|
+
Object.defineProperty(exports, "SupabaseRemoteAdapter", { enumerable: true, get: function () { return supabase_js_1.SupabaseRemoteAdapter; } });
|
|
48
|
+
var convex_js_1 = require("../adapters/remote/convex.js");
|
|
49
|
+
Object.defineProperty(exports, "ConvexRemoteAdapter", { enumerable: true, get: function () { return convex_js_1.ConvexRemoteAdapter; } });
|
|
50
|
+
var firestore_js_1 = require("../adapters/remote/firestore.js");
|
|
51
|
+
Object.defineProperty(exports, "FirestoreRemoteAdapter", { enumerable: true, get: function () { return firestore_js_1.FirestoreRemoteAdapter; } });
|
|
52
|
+
var demo_js_1 = require("../adapters/remote/demo.js");
|
|
53
|
+
Object.defineProperty(exports, "MemoryDemoRemoteAdapter", { enumerable: true, get: function () { return demo_js_1.MemoryDemoRemoteAdapter; } });
|
|
54
|
+
/**
|
|
55
|
+
* Type-safe configuration helper function (similar to defineConfig in Vite/Tailwind).
|
|
56
|
+
*/
|
|
57
|
+
function defineSyncConfig(config) {
|
|
58
|
+
return config;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Factory that instantiates, wires up, and initializes a LocalFirstClient from a LocalLiteConfig.
|
|
62
|
+
*/
|
|
63
|
+
async function createSyncClient(config) {
|
|
64
|
+
// 1. Resolve Local Database Adapter
|
|
65
|
+
const localAdapter = await (0, resolve_local_js_1.resolveLocalAdapter)(config);
|
|
66
|
+
// 2. Resolve Remote Cloud Adapter
|
|
67
|
+
const remoteAdapter = await (0, resolve_remote_js_1.resolveRemoteAdapter)(config);
|
|
68
|
+
// 3. Resolve Network Provider
|
|
69
|
+
const network = config.network ?? new index_js_1.DefaultNetworkStatusProvider(true);
|
|
70
|
+
const autoSchema = config.autoSchema !== undefined
|
|
71
|
+
? config.autoSchema
|
|
72
|
+
: !config.migrations || config.migrations.length === 0;
|
|
73
|
+
// 4. Instantiate Local-First Client
|
|
74
|
+
const client = new client_js_1.LocalFirstClient({
|
|
75
|
+
local: localAdapter,
|
|
76
|
+
remote: remoteAdapter,
|
|
77
|
+
network,
|
|
78
|
+
primaryKey: config.primaryKey ?? 'id',
|
|
79
|
+
conflictResolver: config.conflictResolver ?? 'last-write-wins',
|
|
80
|
+
pushIntervalMs: config.pushIntervalMs ?? 5000,
|
|
81
|
+
pullIntervalMs: config.pullIntervalMs ?? 10000,
|
|
82
|
+
maxRetries: config.maxRetries ?? 5,
|
|
83
|
+
retry: config.retry,
|
|
84
|
+
logger: config.logger,
|
|
85
|
+
logLevel: config.logLevel,
|
|
86
|
+
autoSchema,
|
|
87
|
+
tables: config.tables ?? [],
|
|
88
|
+
});
|
|
89
|
+
// 5. Execute Migrations & Auto-initialize
|
|
90
|
+
if (config.autoInitialize !== false) {
|
|
91
|
+
await client.initialize();
|
|
92
|
+
if (config.migrations && config.migrations.length > 0) {
|
|
93
|
+
for (const query of config.migrations) {
|
|
94
|
+
if (query && query.trim()) {
|
|
95
|
+
await client.db.execute(query);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return client;
|
|
101
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LocalDBAdapter } from '../adapters/local/index.js';
|
|
2
|
+
import { LocalLiteConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves or instantiates the appropriate LocalDBAdapter based on the provided configuration.
|
|
5
|
+
* Auto-detects Expo SQLite, OP-SQLite, better-sqlite3, LibSQL, sql.js, and Node.js built-in sqlite.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveLocalAdapter(config: LocalLiteConfig): Promise<LocalDBAdapter>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveLocalAdapter = resolveLocalAdapter;
|
|
4
|
+
const expo_sqlite_js_1 = require("../adapters/local/expo-sqlite.js");
|
|
5
|
+
const op_sqlite_js_1 = require("../adapters/local/op-sqlite.js");
|
|
6
|
+
const better_sqlite3_js_1 = require("../adapters/local/better-sqlite3.js");
|
|
7
|
+
const libsql_js_1 = require("../adapters/local/libsql.js");
|
|
8
|
+
const sql_js_js_1 = require("../adapters/local/sql-js.js");
|
|
9
|
+
const node_sqlite_js_1 = require("../adapters/local/node-sqlite.js");
|
|
10
|
+
/**
|
|
11
|
+
* Resolves or instantiates the appropriate LocalDBAdapter based on the provided configuration.
|
|
12
|
+
* Auto-detects Expo SQLite, OP-SQLite, better-sqlite3, LibSQL, sql.js, and Node.js built-in sqlite.
|
|
13
|
+
*/
|
|
14
|
+
async function resolveLocalAdapter(config) {
|
|
15
|
+
let rawDb = config.database;
|
|
16
|
+
if (typeof rawDb === 'function') {
|
|
17
|
+
rawDb = await rawDb();
|
|
18
|
+
}
|
|
19
|
+
// 1. @libsql/client (Turso / LibSQL) instance
|
|
20
|
+
if (rawDb && typeof rawDb.batch === 'function' && typeof rawDb.execute === 'function') {
|
|
21
|
+
return new libsql_js_1.LibSQLAdapter(rawDb);
|
|
22
|
+
}
|
|
23
|
+
// 2. sql.js Database instance
|
|
24
|
+
if (rawDb && typeof rawDb.export === 'function' && typeof rawDb.exec === 'function') {
|
|
25
|
+
return new sql_js_js_1.SqlJsAdapter(rawDb);
|
|
26
|
+
}
|
|
27
|
+
// 3. Custom object already implementing LocalDBAdapter
|
|
28
|
+
if (rawDb && typeof rawDb.execute === 'function' && typeof rawDb.transaction === 'function') {
|
|
29
|
+
return rawDb;
|
|
30
|
+
}
|
|
31
|
+
// 4. Expo SQLite database instance (openDatabaseAsync)
|
|
32
|
+
if (rawDb && typeof rawDb.getAllAsync === 'function') {
|
|
33
|
+
return new expo_sqlite_js_1.ExpoSQLiteAdapter(rawDb);
|
|
34
|
+
}
|
|
35
|
+
// 5. @op-engineering/op-sqlite or react-native-quick-sqlite instance
|
|
36
|
+
if (rawDb && typeof rawDb.executeAsync === 'function') {
|
|
37
|
+
return new op_sqlite_js_1.OpSQLiteAdapter(rawDb);
|
|
38
|
+
}
|
|
39
|
+
// 6. better-sqlite3 database instance
|
|
40
|
+
if (rawDb && typeof rawDb.prepare === 'function' && typeof rawDb.pragma === 'function') {
|
|
41
|
+
return new better_sqlite3_js_1.BetterSQLite3Adapter(rawDb);
|
|
42
|
+
}
|
|
43
|
+
// 7. Node.js 22+ built-in node:sqlite (DatabaseSync) instance
|
|
44
|
+
if (rawDb && typeof rawDb.prepare === 'function' && typeof rawDb.exec === 'function') {
|
|
45
|
+
return new node_sqlite_js_1.NodeSQLiteAdapter(rawDb);
|
|
46
|
+
}
|
|
47
|
+
// 8. Fallback for prepare-based SQLite instances
|
|
48
|
+
if (rawDb && typeof rawDb.prepare === 'function') {
|
|
49
|
+
return new better_sqlite3_js_1.BetterSQLite3Adapter(rawDb);
|
|
50
|
+
}
|
|
51
|
+
// 9. Auto-open by name using expo-sqlite if available
|
|
52
|
+
if (typeof rawDb === 'string' || config.databaseName) {
|
|
53
|
+
const dbName = typeof rawDb === 'string' ? rawDb : (config.databaseName ?? 'local_lite.db');
|
|
54
|
+
try {
|
|
55
|
+
const expoSqlite = await import('expo-sqlite');
|
|
56
|
+
if (expoSqlite && typeof expoSqlite.openDatabaseAsync === 'function') {
|
|
57
|
+
const expoDb = await expoSqlite.openDatabaseAsync(dbName);
|
|
58
|
+
return new expo_sqlite_js_1.ExpoSQLiteAdapter(expoDb);
|
|
59
|
+
}
|
|
60
|
+
throw new Error('expo-sqlite openDatabaseAsync not found');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
throw new Error(`Failed to auto-open SQLite database "${dbName}". ` +
|
|
64
|
+
`Please pass the database instance to config.database: ${err}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error('No database provided in sync config. Please specify config.database or config.databaseName.');
|
|
68
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RemoteDBAdapter } from '../adapters/remote/index.js';
|
|
2
|
+
import { LocalLiteConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves or instantiates the appropriate RemoteDBAdapter based on the provided configuration.
|
|
5
|
+
* Supports Supabase, Convex, Firebase / Firestore, in-memory demo adapter, or custom adapters.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveRemoteAdapter(config: LocalLiteConfig): Promise<RemoteDBAdapter>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRemoteAdapter = resolveRemoteAdapter;
|
|
4
|
+
const supabase_js_1 = require("../adapters/remote/supabase.js");
|
|
5
|
+
const convex_js_1 = require("../adapters/remote/convex.js");
|
|
6
|
+
const firestore_js_1 = require("../adapters/remote/firestore.js");
|
|
7
|
+
const demo_js_1 = require("../adapters/remote/demo.js");
|
|
8
|
+
/**
|
|
9
|
+
* Resolves or instantiates the appropriate RemoteDBAdapter based on the provided configuration.
|
|
10
|
+
* Supports Supabase, Convex, Firebase / Firestore, in-memory demo adapter, or custom adapters.
|
|
11
|
+
*/
|
|
12
|
+
async function resolveRemoteAdapter(config) {
|
|
13
|
+
// 1. Explicit custom remote adapter
|
|
14
|
+
if (config.remote) {
|
|
15
|
+
return config.remote;
|
|
16
|
+
}
|
|
17
|
+
// 2. Firebase / Cloud Firestore
|
|
18
|
+
if (config.firebase?.firestore) {
|
|
19
|
+
return new firestore_js_1.FirestoreRemoteAdapter(config.firebase.firestore, {
|
|
20
|
+
tables: config.tables,
|
|
21
|
+
primaryKey: config.firebase.primaryKey ?? config.primaryKey,
|
|
22
|
+
deletedColumn: config.firebase.deletedColumn,
|
|
23
|
+
updatedAtColumn: config.firebase.updatedAtColumn,
|
|
24
|
+
firestoreModule: config.firebase.firestoreModule,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// 3. Convex
|
|
28
|
+
if (config.convex?.client) {
|
|
29
|
+
return new convex_js_1.ConvexRemoteAdapter(config.convex.client, {
|
|
30
|
+
tables: config.tables,
|
|
31
|
+
primaryKey: config.convex.primaryKey ?? config.primaryKey,
|
|
32
|
+
pushMutation: config.convex.pushMutation,
|
|
33
|
+
pullQuery: config.convex.pullQuery,
|
|
34
|
+
deletedColumn: config.convex.deletedColumn,
|
|
35
|
+
updatedAtColumn: config.convex.updatedAtColumn,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// 4. Supabase with pre-configured client
|
|
39
|
+
if (config.supabase?.client) {
|
|
40
|
+
return new supabase_js_1.SupabaseRemoteAdapter(config.supabase.client, {
|
|
41
|
+
tables: config.tables,
|
|
42
|
+
primaryKey: config.primaryKey,
|
|
43
|
+
schema: config.supabase.schema,
|
|
44
|
+
updatedAtColumn: config.supabase.updatedAtColumn,
|
|
45
|
+
deletedColumn: config.supabase.deletedColumn,
|
|
46
|
+
deletedValue: config.supabase.deletedValue,
|
|
47
|
+
deletedAtColumn: config.supabase.deletedAtColumn,
|
|
48
|
+
pushRpcName: config.supabase.pushRpcName,
|
|
49
|
+
channelName: config.supabase.channelName,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// 5. Supabase with URL + anonKey (lazy dynamic import)
|
|
53
|
+
if (config.supabase?.url && config.supabase?.anonKey) {
|
|
54
|
+
let supabaseModule;
|
|
55
|
+
try {
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
supabaseModule = await import('@supabase/supabase-js');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new Error('To initialize Supabase from url and anonKey, please install @supabase/supabase-js or provide a pre-configured client to config.supabase.client');
|
|
61
|
+
}
|
|
62
|
+
const createClientFn = supabaseModule.createClient || supabaseModule.default?.createClient;
|
|
63
|
+
const supabaseClient = createClientFn(config.supabase.url, config.supabase.anonKey);
|
|
64
|
+
return new supabase_js_1.SupabaseRemoteAdapter(supabaseClient, {
|
|
65
|
+
tables: config.tables,
|
|
66
|
+
primaryKey: config.primaryKey,
|
|
67
|
+
schema: config.supabase.schema,
|
|
68
|
+
updatedAtColumn: config.supabase.updatedAtColumn,
|
|
69
|
+
deletedColumn: config.supabase.deletedColumn,
|
|
70
|
+
deletedValue: config.supabase.deletedValue,
|
|
71
|
+
deletedAtColumn: config.supabase.deletedAtColumn,
|
|
72
|
+
pushRpcName: config.supabase.pushRpcName,
|
|
73
|
+
channelName: config.supabase.channelName,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// 6. In-Memory Simulated Remote (Default when no cloud credentials provided)
|
|
77
|
+
if (config.inMemoryRemote !== false) {
|
|
78
|
+
return new demo_js_1.MemoryDemoRemoteAdapter(300);
|
|
79
|
+
}
|
|
80
|
+
throw new Error('No remote adapter configured. Please specify config.supabase, config.convex, config.firebase, or config.remote.');
|
|
81
|
+
}
|