@anfenn/dync 1.0.27 → 1.0.30
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/dist/index.cjs +41 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +41 -11
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/{types-yJuzCcux.d.cts → types-DW42y281.d.cts} +1 -28
- package/dist/{types-BLw25Wlc.d.ts → types-n8Zge2zF.d.ts} +1 -28
- package/package.json +6 -4
- package/src/core/tableEnhancers.ts +15 -5
- package/src/index.shared.ts +37 -9
- package/src/types.ts +2 -28
package/dist/react/index.d.cts
CHANGED
package/dist/react/index.d.ts
CHANGED
|
@@ -94,36 +94,10 @@ interface SyncOptions {
|
|
|
94
94
|
onAfterMissingRemoteRecordDuringUpdate?: MissingRemoteRecordDuringUpdateCallback;
|
|
95
95
|
conflictResolutionStrategy?: ConflictResolutionStrategy;
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Configuration options for creating a Dync instance.
|
|
99
|
-
*
|
|
100
|
-
* @example Per-table sync mode
|
|
101
|
-
* ```ts
|
|
102
|
-
* const db = new Dync<Store>({
|
|
103
|
-
* databaseName: 'my-app',
|
|
104
|
-
* storageAdapter: new SQLiteAdapter(driver),
|
|
105
|
-
* sync: { todos: todoSyncApi },
|
|
106
|
-
* });
|
|
107
|
-
* ```
|
|
108
|
-
*
|
|
109
|
-
* @example Batch sync mode
|
|
110
|
-
* ```ts
|
|
111
|
-
* const db = new Dync<Store>({
|
|
112
|
-
* databaseName: 'my-app',
|
|
113
|
-
* storageAdapter: new SQLiteAdapter(driver),
|
|
114
|
-
* sync: { syncTables: ['todos'], push, pull },
|
|
115
|
-
* });
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
97
|
interface DyncOptions<TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
119
98
|
databaseName: string;
|
|
120
99
|
storageAdapter: StorageAdapter;
|
|
121
|
-
|
|
122
|
-
* Sync configuration - either per-table APIs or batch sync.
|
|
123
|
-
* Per-table: `{ tableName: { add, update, remove, list } }`
|
|
124
|
-
* Batch: `{ syncTables: [...], push, pull }`
|
|
125
|
-
*/
|
|
126
|
-
sync: Partial<Record<keyof TStoreMap, ApiFunctions>> | BatchSync;
|
|
100
|
+
sync?: Partial<Record<keyof TStoreMap, ApiFunctions>> | BatchSync;
|
|
127
101
|
options?: SyncOptions;
|
|
128
102
|
}
|
|
129
103
|
interface FirstLoadProgress {
|
|
@@ -136,7 +110,6 @@ type FirstLoadProgressCallback = (progress: FirstLoadProgress) => void;
|
|
|
136
110
|
type SyncApi = {
|
|
137
111
|
enable: (enabled: boolean) => Promise<void>;
|
|
138
112
|
startFirstLoad: (onProgress?: FirstLoadProgressCallback) => Promise<void>;
|
|
139
|
-
/** Current sync state - use useSyncState() hook for reactive updates in React */
|
|
140
113
|
readonly state: SyncState;
|
|
141
114
|
resolveConflict: (localId: string, keepLocal: boolean) => Promise<void>;
|
|
142
115
|
onStateChange: (fn: (state: SyncState) => void) => () => void;
|
|
@@ -94,36 +94,10 @@ interface SyncOptions {
|
|
|
94
94
|
onAfterMissingRemoteRecordDuringUpdate?: MissingRemoteRecordDuringUpdateCallback;
|
|
95
95
|
conflictResolutionStrategy?: ConflictResolutionStrategy;
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Configuration options for creating a Dync instance.
|
|
99
|
-
*
|
|
100
|
-
* @example Per-table sync mode
|
|
101
|
-
* ```ts
|
|
102
|
-
* const db = new Dync<Store>({
|
|
103
|
-
* databaseName: 'my-app',
|
|
104
|
-
* storageAdapter: new SQLiteAdapter(driver),
|
|
105
|
-
* sync: { todos: todoSyncApi },
|
|
106
|
-
* });
|
|
107
|
-
* ```
|
|
108
|
-
*
|
|
109
|
-
* @example Batch sync mode
|
|
110
|
-
* ```ts
|
|
111
|
-
* const db = new Dync<Store>({
|
|
112
|
-
* databaseName: 'my-app',
|
|
113
|
-
* storageAdapter: new SQLiteAdapter(driver),
|
|
114
|
-
* sync: { syncTables: ['todos'], push, pull },
|
|
115
|
-
* });
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
97
|
interface DyncOptions<TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
119
98
|
databaseName: string;
|
|
120
99
|
storageAdapter: StorageAdapter;
|
|
121
|
-
|
|
122
|
-
* Sync configuration - either per-table APIs or batch sync.
|
|
123
|
-
* Per-table: `{ tableName: { add, update, remove, list } }`
|
|
124
|
-
* Batch: `{ syncTables: [...], push, pull }`
|
|
125
|
-
*/
|
|
126
|
-
sync: Partial<Record<keyof TStoreMap, ApiFunctions>> | BatchSync;
|
|
100
|
+
sync?: Partial<Record<keyof TStoreMap, ApiFunctions>> | BatchSync;
|
|
127
101
|
options?: SyncOptions;
|
|
128
102
|
}
|
|
129
103
|
interface FirstLoadProgress {
|
|
@@ -136,7 +110,6 @@ type FirstLoadProgressCallback = (progress: FirstLoadProgress) => void;
|
|
|
136
110
|
type SyncApi = {
|
|
137
111
|
enable: (enabled: boolean) => Promise<void>;
|
|
138
112
|
startFirstLoad: (onProgress?: FirstLoadProgressCallback) => Promise<void>;
|
|
139
|
-
/** Current sync state - use useSyncState() hook for reactive updates in React */
|
|
140
113
|
readonly state: SyncState;
|
|
141
114
|
resolveConflict: (localId: string, keepLocal: boolean) => Promise<void>;
|
|
142
115
|
onStateChange: (fn: (state: SyncState) => void) => () => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anfenn/dync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Write once, run IndexedDB & SQLite with sync anywhere - React, React Native, Expo, Capacitor, Electron & Node.js",
|
|
6
6
|
"keywords": [
|
|
@@ -132,8 +132,8 @@
|
|
|
132
132
|
"LICENSE"
|
|
133
133
|
],
|
|
134
134
|
"scripts": {
|
|
135
|
-
"build": "pnpm
|
|
136
|
-
"build:all": "pnpm
|
|
135
|
+
"build": "pnpm format && pnpm lint && pnpm exec tsc --noEmit && tsup",
|
|
136
|
+
"build:all": "pnpm build && pnpm --filter react-capacitor build",
|
|
137
137
|
"dev": "tsup --watch",
|
|
138
138
|
"lint": "eslint . --fix \"src/**/*.{js,jsx,ts,tsx,json}\"",
|
|
139
139
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
|
@@ -142,7 +142,9 @@
|
|
|
142
142
|
"test:all": "pnpm --filter tests test:all",
|
|
143
143
|
"test:ci": "pnpm --filter tests test:ci",
|
|
144
144
|
"test:browser:full": "pnpm --filter tests test:browser:full",
|
|
145
|
-
"reinstall": "find . -name node_modules -type d -prune -exec rm -rf {} + && pnpm install"
|
|
145
|
+
"reinstall": "find . -name node_modules -type d -prune -exec rm -rf {} + && pnpm install",
|
|
146
|
+
"push": "bash -c '[ -n \"$1\" ] && git add . && git commit -m \"$1\" && git push' --",
|
|
147
|
+
"release": "bash -c '[ -n \"$1\" ] || { echo \"Usage: pnpm release <commit-message>\"; exit 1; } && pnpm build:all && pnpm test:all && pnpm test:browser:full && git add . && git commit -m \"$1\" && npm version patch && git push && git push --tags && npm publish' --"
|
|
146
148
|
},
|
|
147
149
|
"devDependencies": {
|
|
148
150
|
"@capacitor-community/sqlite": "7.0.2",
|
|
@@ -6,7 +6,7 @@ import type { WithTransaction } from './types';
|
|
|
6
6
|
export type EmitMutation = (event: MutationEvent) => void;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Wraps a table with mutation emission for reactive updates.
|
|
9
|
+
* Wraps a table with mutation emission for reactive updates and auto-generates _localId.
|
|
10
10
|
* This allows useLiveQuery to work with any table.
|
|
11
11
|
*/
|
|
12
12
|
export function wrapWithMutationEmitter<T>(table: StorageTable<T>, tableName: string, emitMutation: EmitMutation): void {
|
|
@@ -21,13 +21,15 @@ export function wrapWithMutationEmitter<T>(table: StorageTable<T>, tableName: st
|
|
|
21
21
|
const rawClear = table.raw.clear;
|
|
22
22
|
|
|
23
23
|
table.add = async (item: AddItem<T>) => {
|
|
24
|
-
const
|
|
24
|
+
const itemWithLocalId = { ...item, _localId: (item as any)._localId || createLocalId() } as T;
|
|
25
|
+
const result = await rawAdd(itemWithLocalId);
|
|
25
26
|
emitMutation({ type: 'add', tableName, keys: [result] });
|
|
26
27
|
return result;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
table.put = async (item: T) => {
|
|
30
|
-
const
|
|
31
|
+
const itemWithLocalId = { ...item, _localId: (item as any)._localId || createLocalId() } as T;
|
|
32
|
+
const result = await rawPut(itemWithLocalId);
|
|
31
33
|
emitMutation({ type: 'update', tableName, keys: [result] });
|
|
32
34
|
return result;
|
|
33
35
|
};
|
|
@@ -46,7 +48,11 @@ export function wrapWithMutationEmitter<T>(table: StorageTable<T>, tableName: st
|
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
table.bulkAdd = async (items: AddItem<T>[]) => {
|
|
49
|
-
const
|
|
51
|
+
const itemsWithLocalIds = items.map((item) => ({
|
|
52
|
+
...item,
|
|
53
|
+
_localId: (item as any)._localId || createLocalId(),
|
|
54
|
+
})) as T[];
|
|
55
|
+
const result = await rawBulkAdd(itemsWithLocalIds);
|
|
50
56
|
if (items.length > 0) {
|
|
51
57
|
emitMutation({ type: 'add', tableName });
|
|
52
58
|
}
|
|
@@ -54,7 +60,11 @@ export function wrapWithMutationEmitter<T>(table: StorageTable<T>, tableName: st
|
|
|
54
60
|
};
|
|
55
61
|
|
|
56
62
|
table.bulkPut = async (items: T[]) => {
|
|
57
|
-
const
|
|
63
|
+
const itemsWithLocalIds = items.map((item) => ({
|
|
64
|
+
...item,
|
|
65
|
+
_localId: (item as any)._localId || createLocalId(),
|
|
66
|
+
})) as T[];
|
|
67
|
+
const result = await rawBulkPut(itemsWithLocalIds);
|
|
58
68
|
if (items.length > 0) {
|
|
59
69
|
emitMutation({ type: 'update', tableName });
|
|
60
70
|
}
|
package/src/index.shared.ts
CHANGED
|
@@ -85,15 +85,17 @@ class DyncBase<_TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
|
85
85
|
constructor(config: DyncOptions<_TStoreMap>) {
|
|
86
86
|
const { databaseName, storageAdapter, sync: syncConfig, options } = config;
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
if (syncConfig) {
|
|
89
|
+
// Detect mode based on whether sync config has batch sync shape
|
|
90
|
+
const isBatchMode = typeof syncConfig.push === 'function' && typeof syncConfig.pull === 'function';
|
|
91
|
+
|
|
92
|
+
if (isBatchMode) {
|
|
93
|
+
this.batchSync = syncConfig as BatchSync;
|
|
94
|
+
this.syncedTables = new Set(this.batchSync.syncTables);
|
|
95
|
+
} else {
|
|
96
|
+
this.syncApis = syncConfig as Record<string, ApiFunctions>;
|
|
97
|
+
this.syncedTables = new Set(Object.keys(this.syncApis));
|
|
98
|
+
}
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
this.adapter = storageAdapter;
|
|
@@ -158,6 +160,9 @@ class DyncBase<_TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
|
158
160
|
// Auto-inject sync fields for sync tables
|
|
159
161
|
// Note: updated_at is indexed to support user queries like orderBy('updated_at')
|
|
160
162
|
fullSchema[tableName] = `${LOCAL_PK}, &${SERVER_PK}, ${tableSchema}, ${UPDATED_AT}`;
|
|
163
|
+
} else {
|
|
164
|
+
// Auto-inject _localId as primary key for non-sync tables
|
|
165
|
+
fullSchema[tableName] = `${LOCAL_PK}, ${tableSchema}`;
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
self.logger.debug(
|
|
@@ -167,6 +172,9 @@ class DyncBase<_TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
|
167
172
|
if (isSyncTable) {
|
|
168
173
|
// Auto-inject sync columns for structured schemas
|
|
169
174
|
fullSchema[tableName] = self.injectSyncColumns(tableSchema);
|
|
175
|
+
} else {
|
|
176
|
+
// Auto-inject _localId column for non-sync structured schemas
|
|
177
|
+
fullSchema[tableName] = self.injectLocalIdColumn(tableSchema);
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
const schemaColumns = Object.keys((fullSchema[tableName] as SQLiteTableDefinition).columns ?? {}).join(', ');
|
|
@@ -350,6 +358,26 @@ class DyncBase<_TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
|
350
358
|
};
|
|
351
359
|
}
|
|
352
360
|
|
|
361
|
+
private injectLocalIdColumn(schema: SQLiteTableDefinition): SQLiteTableDefinition {
|
|
362
|
+
const columns = schema.columns ?? {};
|
|
363
|
+
|
|
364
|
+
// Validate user hasn't defined reserved _localId column
|
|
365
|
+
if (columns[LOCAL_PK]) {
|
|
366
|
+
throw new Error(`Column '${LOCAL_PK}' is auto-injected and cannot be defined manually.`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Inject _localId column as primary key
|
|
370
|
+
const injectedColumns: Record<string, any> = {
|
|
371
|
+
...columns,
|
|
372
|
+
[LOCAL_PK]: { type: 'TEXT' },
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
...schema,
|
|
377
|
+
columns: injectedColumns,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
353
381
|
private enhanceSyncTable<T>(table: StorageTable<T & SyncedRecord>, tableName: string): void {
|
|
354
382
|
enhanceSyncTable({
|
|
355
383
|
table,
|
package/src/types.ts
CHANGED
|
@@ -122,36 +122,10 @@ export interface SyncOptions {
|
|
|
122
122
|
conflictResolutionStrategy?: ConflictResolutionStrategy;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
/**
|
|
126
|
-
* Configuration options for creating a Dync instance.
|
|
127
|
-
*
|
|
128
|
-
* @example Per-table sync mode
|
|
129
|
-
* ```ts
|
|
130
|
-
* const db = new Dync<Store>({
|
|
131
|
-
* databaseName: 'my-app',
|
|
132
|
-
* storageAdapter: new SQLiteAdapter(driver),
|
|
133
|
-
* sync: { todos: todoSyncApi },
|
|
134
|
-
* });
|
|
135
|
-
* ```
|
|
136
|
-
*
|
|
137
|
-
* @example Batch sync mode
|
|
138
|
-
* ```ts
|
|
139
|
-
* const db = new Dync<Store>({
|
|
140
|
-
* databaseName: 'my-app',
|
|
141
|
-
* storageAdapter: new SQLiteAdapter(driver),
|
|
142
|
-
* sync: { syncTables: ['todos'], push, pull },
|
|
143
|
-
* });
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
125
|
export interface DyncOptions<TStoreMap extends Record<string, any> = Record<string, any>> {
|
|
147
126
|
databaseName: string;
|
|
148
127
|
storageAdapter: StorageAdapter;
|
|
149
|
-
|
|
150
|
-
* Sync configuration - either per-table APIs or batch sync.
|
|
151
|
-
* Per-table: `{ tableName: { add, update, remove, list } }`
|
|
152
|
-
* Batch: `{ syncTables: [...], push, pull }`
|
|
153
|
-
*/
|
|
154
|
-
sync: Partial<Record<keyof TStoreMap, ApiFunctions>> | BatchSync;
|
|
128
|
+
sync?: Partial<Record<keyof TStoreMap, ApiFunctions>> | BatchSync;
|
|
155
129
|
options?: SyncOptions;
|
|
156
130
|
}
|
|
157
131
|
|
|
@@ -167,7 +141,7 @@ export type FirstLoadProgressCallback = (progress: FirstLoadProgress) => void;
|
|
|
167
141
|
export type SyncApi = {
|
|
168
142
|
enable: (enabled: boolean) => Promise<void>;
|
|
169
143
|
startFirstLoad: (onProgress?: FirstLoadProgressCallback) => Promise<void>;
|
|
170
|
-
|
|
144
|
+
// Current sync state - use useSyncState() hook for reactive updates in React
|
|
171
145
|
readonly state: SyncState;
|
|
172
146
|
resolveConflict: (localId: string, keepLocal: boolean) => Promise<void>;
|
|
173
147
|
onStateChange: (fn: (state: SyncState) => void) => () => void;
|