local-lite 0.0.12 → 0.0.14

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,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseMaintenance = void 0;
4
+ const sql_js_1 = require("../utils/sql.js");
5
+ class DatabaseMaintenance {
6
+ db;
7
+ getKnownTables;
8
+ constructor(db, getKnownTables = () => []) {
9
+ this.db = db;
10
+ this.getKnownTables = getKnownTables;
11
+ }
12
+ /**
13
+ * Reclaims unused space, defragments the database file, and optimizes internal B-trees.
14
+ */
15
+ async vacuum() {
16
+ await this.db.execute('VACUUM');
17
+ }
18
+ /**
19
+ * Checkpoints WAL (Write-Ahead Log) pages into the main database file.
20
+ * 'TRUNCATE' (default) copies all WAL frames to the database file and resets WAL size to zero.
21
+ */
22
+ async checkpoint(mode = 'TRUNCATE') {
23
+ await this.db.execute(`PRAGMA wal_checkpoint(${mode})`);
24
+ }
25
+ /**
26
+ * Prunes old or completed mutations from the sync_outbox table to keep database footprint small.
27
+ * Returns the count of deleted outbox records.
28
+ */
29
+ async pruneOutbox(options = {}) {
30
+ const statuses = options.statuses ?? ['COMPLETED', 'FAILED'];
31
+ if (statuses.length === 0)
32
+ return 0;
33
+ let cutoffTimestamp = options.beforeTimestamp;
34
+ if (options.retentionDays !== undefined && options.retentionDays >= 0) {
35
+ const daysMs = options.retentionDays * 24 * 60 * 60 * 1000;
36
+ const calculatedCutoff = Date.now() - daysMs;
37
+ cutoffTimestamp = cutoffTimestamp !== undefined
38
+ ? Math.min(cutoffTimestamp, calculatedCutoff)
39
+ : calculatedCutoff;
40
+ }
41
+ const conditions = [];
42
+ const params = [];
43
+ const statusPlaceholders = statuses.map(() => '?').join(', ');
44
+ conditions.push(`status IN (${statusPlaceholders})`);
45
+ params.push(...statuses);
46
+ if (cutoffTimestamp !== undefined) {
47
+ conditions.push('timestamp < ?');
48
+ params.push(cutoffTimestamp);
49
+ }
50
+ const whereClause = `WHERE ${conditions.join(' AND ')}`;
51
+ // If maxEntries is set, only prune if total matching exceeds maxEntries
52
+ if (options.maxEntries !== undefined && options.maxEntries >= 0) {
53
+ const countRes = await this.db.execute(`SELECT COUNT(*) as total FROM sync_outbox ${whereClause}`, params);
54
+ const totalMatching = Number(countRes[0]?.total ?? 0);
55
+ const excess = totalMatching - options.maxEntries;
56
+ if (excess <= 0) {
57
+ return 0;
58
+ }
59
+ // Delete the oldest `excess` records
60
+ const selectOldest = await this.db.execute(`SELECT id FROM sync_outbox ${whereClause} ORDER BY timestamp ASC LIMIT ?`, [...params, excess]);
61
+ if (selectOldest.length === 0)
62
+ return 0;
63
+ const ids = selectOldest.map((r) => r.id);
64
+ const idPlaceholders = ids.map(() => '?').join(', ');
65
+ await this.db.execute(`DELETE FROM sync_outbox WHERE id IN (${idPlaceholders})`, ids);
66
+ return ids.length;
67
+ }
68
+ // Standard cutoff delete
69
+ const beforeCountRes = await this.db.execute(`SELECT COUNT(*) as total FROM sync_outbox ${whereClause}`, params);
70
+ const toDeleteCount = Number(beforeCountRes[0]?.total ?? 0);
71
+ if (toDeleteCount === 0)
72
+ return 0;
73
+ await this.db.execute(`DELETE FROM sync_outbox ${whereClause}`, params);
74
+ return toDeleteCount;
75
+ }
76
+ /**
77
+ * Gathers storage metrics: table row counts, outbox status distribution, and database size.
78
+ */
79
+ async getStorageStats() {
80
+ // 1. Table row counts
81
+ const tableCounts = {};
82
+ let tableNames = [];
83
+ try {
84
+ const masterRows = await this.db.execute(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`);
85
+ tableNames = masterRows.map((r) => r.name);
86
+ }
87
+ catch {
88
+ tableNames = this.getKnownTables();
89
+ }
90
+ for (const tableName of tableNames) {
91
+ try {
92
+ const safeTable = (0, sql_js_1.escapeIdentifier)(tableName);
93
+ const rows = await this.db.execute(`SELECT COUNT(*) as count FROM ${safeTable}`);
94
+ tableCounts[tableName] = Number(rows[0]?.count ?? 0);
95
+ }
96
+ catch {
97
+ // Table might not exist or be accessible
98
+ }
99
+ }
100
+ // 2. Outbox breakdown
101
+ const outboxStats = {
102
+ total: 0,
103
+ pending: 0,
104
+ processing: 0,
105
+ completed: 0,
106
+ failed: 0,
107
+ };
108
+ try {
109
+ const outboxRows = await this.db.execute(`SELECT status, COUNT(*) as count FROM sync_outbox GROUP BY status`);
110
+ for (const row of outboxRows) {
111
+ const st = String(row.status).toUpperCase();
112
+ const cnt = Number(row.count ?? 0);
113
+ outboxStats.total += cnt;
114
+ if (st === 'PENDING')
115
+ outboxStats.pending = cnt;
116
+ else if (st === 'PROCESSING')
117
+ outboxStats.processing = cnt;
118
+ else if (st === 'COMPLETED')
119
+ outboxStats.completed = cnt;
120
+ else if (st === 'FAILED')
121
+ outboxStats.failed = cnt;
122
+ }
123
+ }
124
+ catch {
125
+ // sync_outbox table may not exist yet
126
+ }
127
+ // 3. SQLite database sizing via PRAGMAs
128
+ let pageSize = 4096;
129
+ let pageCount = 0;
130
+ try {
131
+ const pageSizeRes = await this.db.execute('PRAGMA page_size');
132
+ if (pageSizeRes[0]) {
133
+ pageSize = Number(Object.values(pageSizeRes[0])[0] ?? 4096);
134
+ }
135
+ }
136
+ catch {
137
+ // fallback
138
+ }
139
+ try {
140
+ const pageCountRes = await this.db.execute('PRAGMA page_count');
141
+ if (pageCountRes[0]) {
142
+ pageCount = Number(Object.values(pageCountRes[0])[0] ?? 0);
143
+ }
144
+ }
145
+ catch {
146
+ // fallback
147
+ }
148
+ return {
149
+ tables: tableCounts,
150
+ outbox: outboxStats,
151
+ pageSize,
152
+ pageCount,
153
+ databaseSizeBytes: pageSize * pageCount,
154
+ };
155
+ }
156
+ }
157
+ exports.DatabaseMaintenance = DatabaseMaintenance;
@@ -654,7 +654,7 @@ class SyncWorker {
654
654
  }
655
655
  }
656
656
  // 3. Apply remote change to local database
657
- await table_manager_js_1.TableManager.applyMutation(tx, change.tableName, change.rowId, change.operation, change.payload, this.primaryKey, { autoSchema: this.autoSchema, schemaManager: this.schemaManager });
657
+ await table_manager_js_1.TableManager.applyMutation(tx, change.tableName, change.rowId, change.operation, change.payload ?? null, this.primaryKey, { autoSchema: this.autoSchema, schemaManager: this.schemaManager });
658
658
  // 4. Queue table change event to notify reactive listeners after commit
659
659
  queuedEvents.push({
660
660
  event: change.tableName,
@@ -5,7 +5,7 @@ export interface SyncOperation {
5
5
  tableName: string;
6
6
  rowId: string;
7
7
  operation: SyncOp;
8
- payload: Record<string, any> | null;
8
+ payload?: Record<string, any> | null;
9
9
  timestamp: number;
10
10
  }
11
11
  export interface PushResult {
@@ -16,14 +16,14 @@ export interface PushResult {
16
16
  }
17
17
  export interface OutboxRecord extends SyncOperation {
18
18
  status: OutboxStatus;
19
- retryCount: number;
19
+ retryCount?: number;
20
20
  lastError?: string | null;
21
21
  }
22
22
  export interface DeltaChange {
23
23
  tableName: string;
24
24
  operation: SyncOp;
25
25
  rowId: string;
26
- payload: Record<string, any> | null;
26
+ payload?: Record<string, any> | null;
27
27
  timestamp?: number;
28
28
  }
29
29
  export interface DeltaChanges {
@@ -41,7 +41,37 @@ export interface ConflictContext {
41
41
  localRow?: Record<string, any> | null;
42
42
  }
43
43
  export type ConflictResolver = (context: ConflictContext) => ConflictResolution | Promise<ConflictResolution>;
44
- export type ConflictStrategy = 'last-write-wins' | 'server-wins' | 'client-wins';
44
+ export type ConflictStrategy = 'last-write-wins' | 'server-wins' | 'client-wins' | 'field-merge';
45
+ export type FieldMergeWinner = 'remote' | 'local' | 'newer' | 'max' | 'min';
46
+ export type FieldMergeCustomResolver = (localVal: any, remoteVal: any, context: ConflictContext) => any;
47
+ export type FieldMergeRule = FieldMergeWinner | FieldMergeCustomResolver;
48
+ export interface FieldMergeResolverOptions {
49
+ /**
50
+ * Default resolution strategy when both local and remote updated the exact same field.
51
+ * 'newer' (default) uses timestamp to pick the newer field; 'remote' or 'local' picks fixed side.
52
+ */
53
+ defaultWinner?: 'remote' | 'local' | 'newer';
54
+ /**
55
+ * Field-level merge rules by table and column.
56
+ * e.g.
57
+ * {
58
+ * notes: {
59
+ * tags: (local, remote) => Array.from(new Set([...(local ?? []), ...(remote ?? [])])),
60
+ * view_count: 'max',
61
+ * },
62
+ * '*': {
63
+ * updated_at: 'newer'
64
+ * }
65
+ * }
66
+ */
67
+ fieldRules?: Record<string, Record<string, FieldMergeRule>>;
68
+ /**
69
+ * Strategy when one side deleted the row while the other side updated fields.
70
+ * 'delete-wins' (default) preserves the deletion.
71
+ * 'update-wins' resurrects the row with the updated fields.
72
+ */
73
+ deleteHandling?: 'delete-wins' | 'update-wins';
74
+ }
45
75
  export interface NetworkStatusProvider {
46
76
  isOnline(): boolean | Promise<boolean>;
47
77
  subscribe(callback: (isOnline: boolean) => void): () => void;
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './core/meta-store.js';
8
8
  export * from './core/table-manager.js';
9
9
  export * from './core/schema-manager.js';
10
10
  export * from './core/query-builder.js';
11
+ export * from './core/maintenance.js';
11
12
  export * from './reactive/event-emitter.js';
12
13
  export * from './adapters/local/index.js';
13
14
  export * from './adapters/remote/index.js';
@@ -17,6 +18,7 @@ export type * from './react/index.js';
17
18
  export * from './utils/uuid.js';
18
19
  export * from './utils/sql.js';
19
20
  export * from './utils/logger.js';
21
+ export * from './utils/portability.js';
20
22
  export * from './schema/index.js';
21
23
  export * from './auth/index.js';
22
24
  export * from './backups/index.js';
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ __exportStar(require("./core/meta-store.js"), exports);
24
24
  __exportStar(require("./core/table-manager.js"), exports);
25
25
  __exportStar(require("./core/schema-manager.js"), exports);
26
26
  __exportStar(require("./core/query-builder.js"), exports);
27
+ __exportStar(require("./core/maintenance.js"), exports);
27
28
  __exportStar(require("./reactive/event-emitter.js"), exports);
28
29
  __exportStar(require("./adapters/local/index.js"), exports);
29
30
  __exportStar(require("./adapters/remote/index.js"), exports);
@@ -32,6 +33,7 @@ __exportStar(require("./config/index.js"), exports);
32
33
  __exportStar(require("./utils/uuid.js"), exports);
33
34
  __exportStar(require("./utils/sql.js"), exports);
34
35
  __exportStar(require("./utils/logger.js"), exports);
36
+ __exportStar(require("./utils/portability.js"), exports);
35
37
  __exportStar(require("./schema/index.js"), exports);
36
38
  __exportStar(require("./auth/index.js"), exports);
37
39
  __exportStar(require("./backups/index.js"), exports);
@@ -1,4 +1,7 @@
1
1
  import type { GoogleAuthClient, GoogleUser } from '../auth/google.js';
2
+ import type { MicrosoftAuthClient, MicrosoftUser } from '../auth/microsoft.js';
3
+ import type { GitHubAuthClient, GitHubUser } from '../auth/github.js';
4
+ import type { AppleAuthClient, AppleUser, AppleNativeCredential } from '../auth/apple.js';
2
5
  export interface UseGoogleAuthResult {
3
6
  user: GoogleUser | null;
4
7
  isSignedIn: boolean;
@@ -9,3 +12,35 @@ export interface UseGoogleAuthResult {
9
12
  refetchUser: () => Promise<GoogleUser | null>;
10
13
  }
11
14
  export declare function useGoogleAuth(client: GoogleAuthClient | null | undefined): UseGoogleAuthResult;
15
+ export interface UseMicrosoftAuthResult {
16
+ user: MicrosoftUser | null;
17
+ isSignedIn: boolean;
18
+ loading: boolean;
19
+ error: Error | null;
20
+ signIn: () => Promise<MicrosoftUser | null>;
21
+ signOut: () => Promise<void>;
22
+ refetchUser: () => Promise<MicrosoftUser | null>;
23
+ }
24
+ export type UseOneDriveAuthResult = UseMicrosoftAuthResult;
25
+ export declare function useMicrosoftAuth(client: MicrosoftAuthClient | null | undefined): UseMicrosoftAuthResult;
26
+ export declare const useOneDriveAuth: typeof useMicrosoftAuth;
27
+ export interface UseGitHubAuthResult {
28
+ user: GitHubUser | null;
29
+ isSignedIn: boolean;
30
+ loading: boolean;
31
+ error: Error | null;
32
+ signIn: () => Promise<GitHubUser | null>;
33
+ signOut: () => Promise<void>;
34
+ refetchUser: () => Promise<GitHubUser | null>;
35
+ }
36
+ export declare function useGitHubAuth(client: GitHubAuthClient | null | undefined): UseGitHubAuthResult;
37
+ export interface UseAppleAuthResult {
38
+ user: AppleUser | null;
39
+ isSignedIn: boolean;
40
+ loading: boolean;
41
+ error: Error | null;
42
+ signIn: (nativeCredential?: AppleNativeCredential) => Promise<AppleUser | null>;
43
+ signOut: () => Promise<void>;
44
+ refetchUser: () => Promise<AppleUser | null>;
45
+ }
46
+ export declare function useAppleAuth(client: AppleAuthClient | null | undefined): UseAppleAuthResult;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useOneDriveAuth = void 0;
3
4
  exports.useGoogleAuth = useGoogleAuth;
5
+ exports.useMicrosoftAuth = useMicrosoftAuth;
6
+ exports.useGitHubAuth = useGitHubAuth;
7
+ exports.useAppleAuth = useAppleAuth;
4
8
  const react_1 = require("react");
5
9
  function useGoogleAuth(client) {
6
10
  const [user, setUser] = (0, react_1.useState)(null);
@@ -91,3 +95,271 @@ function useGoogleAuth(client) {
91
95
  refetchUser,
92
96
  };
93
97
  }
98
+ function useMicrosoftAuth(client) {
99
+ const [user, setUser] = (0, react_1.useState)(null);
100
+ const [loading, setLoading] = (0, react_1.useState)(true);
101
+ const [error, setError] = (0, react_1.useState)(null);
102
+ (0, react_1.useEffect)(() => {
103
+ if (!client) {
104
+ setUser(null);
105
+ setLoading(false);
106
+ return;
107
+ }
108
+ let isMounted = true;
109
+ client
110
+ .getUser()
111
+ .then(currentUser => {
112
+ if (isMounted) {
113
+ setUser(currentUser);
114
+ setLoading(false);
115
+ }
116
+ })
117
+ .catch(err => {
118
+ if (isMounted) {
119
+ setError(err instanceof Error ? err : new Error(String(err)));
120
+ setLoading(false);
121
+ }
122
+ });
123
+ const unsubscribe = client.onAuthStateChange(updatedUser => {
124
+ if (isMounted) {
125
+ setUser(updatedUser);
126
+ }
127
+ });
128
+ return () => {
129
+ isMounted = false;
130
+ unsubscribe();
131
+ };
132
+ }, [client]);
133
+ const signIn = (0, react_1.useCallback)(async () => {
134
+ if (!client)
135
+ throw new Error('MicrosoftAuthClient is not initialized.');
136
+ setError(null);
137
+ setLoading(true);
138
+ try {
139
+ const res = await client.signIn();
140
+ setUser(res);
141
+ return res;
142
+ }
143
+ catch (err) {
144
+ const e = err instanceof Error ? err : new Error(String(err));
145
+ setError(e);
146
+ throw e;
147
+ }
148
+ finally {
149
+ setLoading(false);
150
+ }
151
+ }, [client]);
152
+ const signOut = (0, react_1.useCallback)(async () => {
153
+ if (!client)
154
+ return;
155
+ setError(null);
156
+ setLoading(true);
157
+ try {
158
+ await client.signOut();
159
+ setUser(null);
160
+ }
161
+ catch (err) {
162
+ const e = err instanceof Error ? err : new Error(String(err));
163
+ setError(e);
164
+ throw e;
165
+ }
166
+ finally {
167
+ setLoading(false);
168
+ }
169
+ }, [client]);
170
+ const refetchUser = (0, react_1.useCallback)(async () => {
171
+ if (!client)
172
+ return null;
173
+ const res = await client.getUser();
174
+ setUser(res);
175
+ return res;
176
+ }, [client]);
177
+ return {
178
+ user,
179
+ isSignedIn: Boolean(user),
180
+ loading,
181
+ error,
182
+ signIn,
183
+ signOut,
184
+ refetchUser,
185
+ };
186
+ }
187
+ exports.useOneDriveAuth = useMicrosoftAuth;
188
+ function useGitHubAuth(client) {
189
+ const [user, setUser] = (0, react_1.useState)(null);
190
+ const [loading, setLoading] = (0, react_1.useState)(true);
191
+ const [error, setError] = (0, react_1.useState)(null);
192
+ (0, react_1.useEffect)(() => {
193
+ if (!client) {
194
+ setUser(null);
195
+ setLoading(false);
196
+ return;
197
+ }
198
+ let isMounted = true;
199
+ client
200
+ .getUser()
201
+ .then(currentUser => {
202
+ if (isMounted) {
203
+ setUser(currentUser);
204
+ setLoading(false);
205
+ }
206
+ })
207
+ .catch(err => {
208
+ if (isMounted) {
209
+ setError(err instanceof Error ? err : new Error(String(err)));
210
+ setLoading(false);
211
+ }
212
+ });
213
+ const unsubscribe = client.onAuthStateChange(updatedUser => {
214
+ if (isMounted) {
215
+ setUser(updatedUser);
216
+ }
217
+ });
218
+ return () => {
219
+ isMounted = false;
220
+ unsubscribe();
221
+ };
222
+ }, [client]);
223
+ const signIn = (0, react_1.useCallback)(async () => {
224
+ if (!client)
225
+ throw new Error('GitHubAuthClient is not initialized.');
226
+ setError(null);
227
+ setLoading(true);
228
+ try {
229
+ const res = await client.signIn();
230
+ setUser(res);
231
+ return res;
232
+ }
233
+ catch (err) {
234
+ const e = err instanceof Error ? err : new Error(String(err));
235
+ setError(e);
236
+ throw e;
237
+ }
238
+ finally {
239
+ setLoading(false);
240
+ }
241
+ }, [client]);
242
+ const signOut = (0, react_1.useCallback)(async () => {
243
+ if (!client)
244
+ return;
245
+ setError(null);
246
+ setLoading(true);
247
+ try {
248
+ await client.signOut();
249
+ setUser(null);
250
+ }
251
+ catch (err) {
252
+ const e = err instanceof Error ? err : new Error(String(err));
253
+ setError(e);
254
+ throw e;
255
+ }
256
+ finally {
257
+ setLoading(false);
258
+ }
259
+ }, [client]);
260
+ const refetchUser = (0, react_1.useCallback)(async () => {
261
+ if (!client)
262
+ return null;
263
+ const res = await client.getUser();
264
+ setUser(res);
265
+ return res;
266
+ }, [client]);
267
+ return {
268
+ user,
269
+ isSignedIn: Boolean(user),
270
+ loading,
271
+ error,
272
+ signIn,
273
+ signOut,
274
+ refetchUser,
275
+ };
276
+ }
277
+ function useAppleAuth(client) {
278
+ const [user, setUser] = (0, react_1.useState)(null);
279
+ const [loading, setLoading] = (0, react_1.useState)(true);
280
+ const [error, setError] = (0, react_1.useState)(null);
281
+ (0, react_1.useEffect)(() => {
282
+ if (!client) {
283
+ setUser(null);
284
+ setLoading(false);
285
+ return;
286
+ }
287
+ let isMounted = true;
288
+ client
289
+ .getUser()
290
+ .then(currentUser => {
291
+ if (isMounted) {
292
+ setUser(currentUser);
293
+ setLoading(false);
294
+ }
295
+ })
296
+ .catch(err => {
297
+ if (isMounted) {
298
+ setError(err instanceof Error ? err : new Error(String(err)));
299
+ setLoading(false);
300
+ }
301
+ });
302
+ const unsubscribe = client.onAuthStateChange(updatedUser => {
303
+ if (isMounted) {
304
+ setUser(updatedUser);
305
+ }
306
+ });
307
+ return () => {
308
+ isMounted = false;
309
+ unsubscribe();
310
+ };
311
+ }, [client]);
312
+ const signIn = (0, react_1.useCallback)(async (nativeCredential) => {
313
+ if (!client)
314
+ throw new Error('AppleAuthClient is not initialized.');
315
+ setError(null);
316
+ setLoading(true);
317
+ try {
318
+ const res = await client.signIn(nativeCredential);
319
+ setUser(res);
320
+ return res;
321
+ }
322
+ catch (err) {
323
+ const e = err instanceof Error ? err : new Error(String(err));
324
+ setError(e);
325
+ throw e;
326
+ }
327
+ finally {
328
+ setLoading(false);
329
+ }
330
+ }, [client]);
331
+ const signOut = (0, react_1.useCallback)(async () => {
332
+ if (!client)
333
+ return;
334
+ setError(null);
335
+ setLoading(true);
336
+ try {
337
+ await client.signOut();
338
+ setUser(null);
339
+ }
340
+ catch (err) {
341
+ const e = err instanceof Error ? err : new Error(String(err));
342
+ setError(e);
343
+ throw e;
344
+ }
345
+ finally {
346
+ setLoading(false);
347
+ }
348
+ }, [client]);
349
+ const refetchUser = (0, react_1.useCallback)(async () => {
350
+ if (!client)
351
+ return null;
352
+ const res = await client.getUser();
353
+ setUser(res);
354
+ return res;
355
+ }, [client]);
356
+ return {
357
+ user,
358
+ isSignedIn: Boolean(user),
359
+ loading,
360
+ error,
361
+ signIn,
362
+ signOut,
363
+ refetchUser,
364
+ };
365
+ }
@@ -0,0 +1,73 @@
1
+ import type { LocalFirstClient } from '../core/client.js';
2
+ export interface ExportJsonOptions {
3
+ /** Pretty-print JSON with 2-space indentation. Default: false */
4
+ pretty?: boolean;
5
+ /** Explicit list of tables to export. If not specified, exports all registered tables. */
6
+ tables?: string[];
7
+ }
8
+ export interface JsonExportData {
9
+ version: number;
10
+ exportedAt: string;
11
+ tables: Record<string, Array<Record<string, any>>>;
12
+ }
13
+ export interface ImportJsonOptions {
14
+ /**
15
+ * 'merge' (default): updates existing rows and inserts new rows using primary key.
16
+ * 'replace': deletes all rows in the table before inserting the imported data.
17
+ */
18
+ mode?: 'merge' | 'replace';
19
+ /**
20
+ * Whether to skip queueing outbox mutations for remote sync.
21
+ * When true, imported data is only stored in local SQLite without triggering sync pushes.
22
+ * Default: false.
23
+ */
24
+ skipOutbox?: boolean;
25
+ /** Whitelist of tables to import. If specified, any tables in JSON not in this list are ignored. */
26
+ tables?: string[];
27
+ }
28
+ export interface ExportCsvOptions {
29
+ /** Columns to export in order. If omitted, uses all columns from the table. */
30
+ columns?: string[];
31
+ /** Field delimiter. Default: ',' */
32
+ delimiter?: string;
33
+ /** Whether to output the first row as column headers. Default: true */
34
+ includeHeader?: boolean;
35
+ }
36
+ export interface ImportCsvOptions {
37
+ /** 'merge' (default) or 'replace' */
38
+ mode?: 'merge' | 'replace';
39
+ /** Whether to skip queueing outbox records. Default: false */
40
+ skipOutbox?: boolean;
41
+ /** Field delimiter. Default: ',' */
42
+ delimiter?: string;
43
+ /** Whether the first line contains header columns. Default: true */
44
+ hasHeader?: boolean;
45
+ /** Explicit column names if CSV has no header. */
46
+ columns?: string[];
47
+ }
48
+ export interface ImportSummary {
49
+ tables: string[];
50
+ rowCounts: Record<string, number>;
51
+ }
52
+ /**
53
+ * Robust RFC-4180 CSV parser supporting multiline fields and escaped quotes.
54
+ */
55
+ export declare function parseCsv(text: string, delimiter?: string): string[][];
56
+ /**
57
+ * Exports SQLite tables to a structured JSON string.
58
+ */
59
+ export declare function exportTablesToJson(client: LocalFirstClient, tables?: string[], options?: ExportJsonOptions): Promise<string>;
60
+ /**
61
+ * Imports tables from a JSON export string into the database.
62
+ */
63
+ export declare function importTablesFromJson(client: LocalFirstClient, jsonString: string, options?: ImportJsonOptions): Promise<ImportSummary>;
64
+ /**
65
+ * Exports a single SQLite table to an RFC-4180 CSV string.
66
+ */
67
+ export declare function exportTableToCsv(client: LocalFirstClient, tableName: string, options?: ExportCsvOptions): Promise<string>;
68
+ /**
69
+ * Imports an RFC-4180 CSV string into a SQLite table.
70
+ */
71
+ export declare function importTableFromCsv(client: LocalFirstClient, tableName: string, csvString: string, options?: ImportCsvOptions): Promise<{
72
+ rowCount: number;
73
+ }>;