@anfenn/dync 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/capacitor.cjs +228 -0
- package/dist/capacitor.cjs.map +1 -0
- package/dist/capacitor.d.cts +62 -0
- package/dist/capacitor.d.ts +62 -0
- package/dist/capacitor.js +9 -0
- package/dist/capacitor.js.map +1 -0
- package/dist/chunk-LGHOZECP.js +3884 -0
- package/dist/chunk-LGHOZECP.js.map +1 -0
- package/dist/chunk-SQB6E7V2.js +191 -0
- package/dist/chunk-SQB6E7V2.js.map +1 -0
- package/dist/dexie-Bv-fV10P.d.cts +444 -0
- package/dist/dexie-DJFApKsM.d.ts +444 -0
- package/dist/dexie.cjs +381 -0
- package/dist/dexie.cjs.map +1 -0
- package/dist/dexie.d.cts +3 -0
- package/dist/dexie.d.ts +3 -0
- package/dist/dexie.js +343 -0
- package/dist/dexie.js.map +1 -0
- package/dist/expoSqlite.cjs +98 -0
- package/dist/expoSqlite.cjs.map +1 -0
- package/dist/expoSqlite.d.cts +17 -0
- package/dist/expoSqlite.d.ts +17 -0
- package/dist/expoSqlite.js +61 -0
- package/dist/expoSqlite.js.map +1 -0
- package/dist/index.cjs +3916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/index.shared-CPIge2ZM.d.ts +234 -0
- package/dist/index.shared-YSn6c01d.d.cts +234 -0
- package/dist/node.cjs +126 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +80 -0
- package/dist/node.d.ts +80 -0
- package/dist/node.js +89 -0
- package/dist/node.js.map +1 -0
- package/dist/react/index.cjs +1754 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +40 -0
- package/dist/react/index.d.ts +40 -0
- package/dist/react/index.js +78 -0
- package/dist/react/index.js.map +1 -0
- package/dist/types-CSbIAfu2.d.cts +46 -0
- package/dist/types-CSbIAfu2.d.ts +46 -0
- package/dist/wa-sqlite.cjs +318 -0
- package/dist/wa-sqlite.cjs.map +1 -0
- package/dist/wa-sqlite.d.cts +175 -0
- package/dist/wa-sqlite.d.ts +175 -0
- package/dist/wa-sqlite.js +281 -0
- package/dist/wa-sqlite.js.map +1 -0
- package/package.json +171 -0
- package/src/addVisibilityChangeListener.native.ts +33 -0
- package/src/addVisibilityChangeListener.ts +24 -0
- package/src/capacitor.ts +4 -0
- package/src/core/StateManager.ts +272 -0
- package/src/core/firstLoad.ts +332 -0
- package/src/core/pullOperations.ts +212 -0
- package/src/core/pushOperations.ts +290 -0
- package/src/core/tableEnhancers.ts +457 -0
- package/src/core/types.ts +3 -0
- package/src/createLocalId.native.ts +8 -0
- package/src/createLocalId.ts +6 -0
- package/src/dexie.ts +2 -0
- package/src/expoSqlite.ts +2 -0
- package/src/helpers.ts +87 -0
- package/src/index.native.ts +28 -0
- package/src/index.shared.ts +613 -0
- package/src/index.ts +28 -0
- package/src/logger.ts +26 -0
- package/src/node.ts +4 -0
- package/src/react/index.ts +2 -0
- package/src/react/useDync.ts +156 -0
- package/src/storage/dexie/DexieAdapter.ts +72 -0
- package/src/storage/dexie/DexieQueryContext.ts +14 -0
- package/src/storage/dexie/DexieStorageCollection.ts +124 -0
- package/src/storage/dexie/DexieStorageTable.ts +123 -0
- package/src/storage/dexie/DexieStorageWhereClause.ts +103 -0
- package/src/storage/dexie/helpers.ts +1 -0
- package/src/storage/dexie/index.ts +7 -0
- package/src/storage/memory/MemoryAdapter.ts +55 -0
- package/src/storage/memory/MemoryCollection.ts +215 -0
- package/src/storage/memory/MemoryQueryContext.ts +14 -0
- package/src/storage/memory/MemoryTable.ts +336 -0
- package/src/storage/memory/MemoryWhereClause.ts +134 -0
- package/src/storage/memory/index.ts +7 -0
- package/src/storage/memory/types.ts +24 -0
- package/src/storage/sqlite/SQLiteAdapter.ts +564 -0
- package/src/storage/sqlite/SQLiteCollection.ts +294 -0
- package/src/storage/sqlite/SQLiteTable.ts +604 -0
- package/src/storage/sqlite/SQLiteWhereClause.ts +341 -0
- package/src/storage/sqlite/SqliteQueryContext.ts +30 -0
- package/src/storage/sqlite/drivers/BetterSqlite3Driver.ts +156 -0
- package/src/storage/sqlite/drivers/CapacitorFastSqlDriver.ts +114 -0
- package/src/storage/sqlite/drivers/CapacitorSQLiteDriver.ts +137 -0
- package/src/storage/sqlite/drivers/ExpoSQLiteDriver.native.ts +67 -0
- package/src/storage/sqlite/drivers/WaSqliteDriver.ts +537 -0
- package/src/storage/sqlite/drivers/wa-sqlite-vfs.d.ts +46 -0
- package/src/storage/sqlite/helpers.ts +144 -0
- package/src/storage/sqlite/index.ts +11 -0
- package/src/storage/sqlite/schema.ts +44 -0
- package/src/storage/sqlite/types.ts +164 -0
- package/src/storage/types.ts +112 -0
- package/src/types.ts +186 -0
- package/src/wa-sqlite.ts +4 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { SQLiteCollectionState, SQLiteCondition } from './types';
|
|
2
|
+
|
|
3
|
+
export const SQLITE_SCHEMA_VERSION_STATE_KEY = 'sqlite_schema_version';
|
|
4
|
+
export const DEFAULT_STREAM_BATCH_SIZE = 200;
|
|
5
|
+
|
|
6
|
+
export const createDefaultState = <T>(): SQLiteCollectionState<T> => ({
|
|
7
|
+
sqlConditions: [],
|
|
8
|
+
jsPredicate: undefined,
|
|
9
|
+
orderBy: undefined,
|
|
10
|
+
reverse: false,
|
|
11
|
+
offset: 0,
|
|
12
|
+
limit: undefined,
|
|
13
|
+
distinct: false,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export interface SQLiteBuiltQuery {
|
|
17
|
+
whereClause: string;
|
|
18
|
+
parameters: unknown[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const buildWhereClause = (conditions: SQLiteCondition[]): SQLiteBuiltQuery => {
|
|
22
|
+
if (conditions.length === 0) {
|
|
23
|
+
return { whereClause: '', parameters: [] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const clauses: string[] = [];
|
|
27
|
+
const parameters: unknown[] = [];
|
|
28
|
+
|
|
29
|
+
for (const condition of conditions) {
|
|
30
|
+
const built = buildCondition(condition);
|
|
31
|
+
clauses.push(built.clause);
|
|
32
|
+
parameters.push(...built.parameters);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
whereClause: `WHERE ${clauses.join(' AND ')}`,
|
|
37
|
+
parameters,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
interface BuiltCondition {
|
|
42
|
+
clause: string;
|
|
43
|
+
parameters: unknown[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const buildCondition = (condition: SQLiteCondition): BuiltCondition => {
|
|
47
|
+
// Handle 'or' condition first since it doesn't have a column property
|
|
48
|
+
if (condition.type === 'or') {
|
|
49
|
+
if (condition.conditions.length === 0) {
|
|
50
|
+
return { clause: '0 = 1', parameters: [] };
|
|
51
|
+
}
|
|
52
|
+
const subClauses: string[] = [];
|
|
53
|
+
const subParams: unknown[] = [];
|
|
54
|
+
for (const sub of condition.conditions) {
|
|
55
|
+
const built = buildCondition(sub);
|
|
56
|
+
subClauses.push(built.clause);
|
|
57
|
+
subParams.push(...built.parameters);
|
|
58
|
+
}
|
|
59
|
+
return { clause: `(${subClauses.join(' OR ')})`, parameters: subParams };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle compound equals (multiple columns ANDed together)
|
|
63
|
+
if (condition.type === 'compoundEquals') {
|
|
64
|
+
const clauses = condition.columns.map((col) => `${quoteIdentifier(col)} = ?`);
|
|
65
|
+
return { clause: `(${clauses.join(' AND ')})`, parameters: condition.values };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const col = quoteIdentifier(condition.column);
|
|
69
|
+
|
|
70
|
+
switch (condition.type) {
|
|
71
|
+
case 'equals': {
|
|
72
|
+
if (condition.caseInsensitive) {
|
|
73
|
+
return { clause: `LOWER(${col}) = LOWER(?)`, parameters: [condition.value] };
|
|
74
|
+
}
|
|
75
|
+
return { clause: `${col} = ?`, parameters: [condition.value] };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 'comparison': {
|
|
79
|
+
return { clause: `${col} ${condition.op} ?`, parameters: [condition.value] };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'between': {
|
|
83
|
+
const lowerOp = condition.includeLower ? '>=' : '>';
|
|
84
|
+
const upperOp = condition.includeUpper ? '<=' : '<';
|
|
85
|
+
return {
|
|
86
|
+
clause: `(${col} ${lowerOp} ? AND ${col} ${upperOp} ?)`,
|
|
87
|
+
parameters: [condition.lower, condition.upper],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case 'in': {
|
|
92
|
+
if (condition.values.length === 0) {
|
|
93
|
+
// No values = no matches
|
|
94
|
+
return { clause: '0 = 1', parameters: [] };
|
|
95
|
+
}
|
|
96
|
+
const placeholders = condition.values.map(() => '?').join(', ');
|
|
97
|
+
if (condition.caseInsensitive) {
|
|
98
|
+
return {
|
|
99
|
+
clause: `LOWER(${col}) IN (${condition.values.map(() => 'LOWER(?)').join(', ')})`,
|
|
100
|
+
parameters: condition.values,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { clause: `${col} IN (${placeholders})`, parameters: condition.values };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case 'notIn': {
|
|
107
|
+
if (condition.values.length === 0) {
|
|
108
|
+
// No exclusions = all match
|
|
109
|
+
return { clause: '1 = 1', parameters: [] };
|
|
110
|
+
}
|
|
111
|
+
const placeholders = condition.values.map(() => '?').join(', ');
|
|
112
|
+
return { clause: `${col} NOT IN (${placeholders})`, parameters: condition.values };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case 'like': {
|
|
116
|
+
if (condition.caseInsensitive) {
|
|
117
|
+
return { clause: `LOWER(${col}) LIKE LOWER(?)`, parameters: [condition.pattern] };
|
|
118
|
+
}
|
|
119
|
+
return { clause: `${col} LIKE ?`, parameters: [condition.pattern] };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const cloneValue = <T>(value: T): T => {
|
|
125
|
+
if (typeof globalThis.structuredClone === 'function') {
|
|
126
|
+
return globalThis.structuredClone(value);
|
|
127
|
+
}
|
|
128
|
+
return JSON.parse(JSON.stringify(value ?? null)) as T;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const quoteIdentifier = (name: string): string => `"${name.replace(/"/g, '""')}"`;
|
|
132
|
+
|
|
133
|
+
export const normalizeComparableValue = (value: unknown): unknown => {
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return value.map((entry) => normalizeComparableValue(entry));
|
|
136
|
+
}
|
|
137
|
+
if (value instanceof Date) {
|
|
138
|
+
return value.valueOf();
|
|
139
|
+
}
|
|
140
|
+
if (value === undefined) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './helpers';
|
|
3
|
+
export * from './SQLiteCollection';
|
|
4
|
+
export * from './SQLiteWhereClause';
|
|
5
|
+
export * from './SQLiteTable';
|
|
6
|
+
export * from './SqliteQueryContext';
|
|
7
|
+
export * from './SQLiteAdapter';
|
|
8
|
+
|
|
9
|
+
// Drivers
|
|
10
|
+
export { CapacitorSQLiteDriver } from './drivers/CapacitorSQLiteDriver';
|
|
11
|
+
export { CapacitorFastSqlDriver, type FastSqlDriverOptions } from './drivers/CapacitorFastSqlDriver';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type TableSchemaDefinition = string | SQLiteTableDefinition;
|
|
2
|
+
|
|
3
|
+
export interface SQLiteTableDefinition {
|
|
4
|
+
columns: Record<string, SQLiteColumnDefinition>;
|
|
5
|
+
indexes?: SQLiteIndexDefinition[];
|
|
6
|
+
tableConstraints?: string[];
|
|
7
|
+
withoutRowId?: boolean;
|
|
8
|
+
strict?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SQLiteColumnDefinition {
|
|
12
|
+
type?: string;
|
|
13
|
+
length?: number;
|
|
14
|
+
nullable?: boolean;
|
|
15
|
+
unique?: boolean;
|
|
16
|
+
default?: SQLiteDefaultValue;
|
|
17
|
+
check?: string;
|
|
18
|
+
references?: SQLiteForeignKeyReference | string;
|
|
19
|
+
collate?: string;
|
|
20
|
+
generatedAlwaysAs?: string;
|
|
21
|
+
stored?: boolean;
|
|
22
|
+
constraints?: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type SQLiteDefaultValue = string | number | boolean | null;
|
|
26
|
+
|
|
27
|
+
export interface SQLiteForeignKeyReference {
|
|
28
|
+
table: string;
|
|
29
|
+
column?: string;
|
|
30
|
+
onDelete?: SQLiteForeignKeyAction;
|
|
31
|
+
onUpdate?: SQLiteForeignKeyAction;
|
|
32
|
+
match?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type SQLiteForeignKeyAction = 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION' | 'CASCADE';
|
|
36
|
+
|
|
37
|
+
export interface SQLiteIndexDefinition {
|
|
38
|
+
name?: string;
|
|
39
|
+
columns: string[];
|
|
40
|
+
unique?: boolean;
|
|
41
|
+
where?: string;
|
|
42
|
+
collate?: string;
|
|
43
|
+
orders?: Array<'ASC' | 'DESC'>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { SQLiteColumnDefinition, SQLiteTableDefinition } from './schema';
|
|
2
|
+
|
|
3
|
+
export interface SQLiteAdapterOptions {
|
|
4
|
+
debug?: boolean | ((statement: string, parameters?: any[]) => void);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface SQLiteColumnSchema extends SQLiteColumnDefinition {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SQLiteNormalizedTableDefinition extends SQLiteTableDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
columns: Record<string, SQLiteColumnSchema>;
|
|
14
|
+
source: 'dexie' | 'structured';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SQLiteTableSchemaMetadata {
|
|
18
|
+
name: string;
|
|
19
|
+
definition: SQLiteNormalizedTableDefinition;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type SQLiteComparisonOp = '=' | '!=' | '>' | '>=' | '<' | '<=';
|
|
23
|
+
|
|
24
|
+
export interface SQLiteConditionEquals {
|
|
25
|
+
type: 'equals';
|
|
26
|
+
column: string;
|
|
27
|
+
value: unknown;
|
|
28
|
+
caseInsensitive?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SQLiteConditionComparison {
|
|
32
|
+
type: 'comparison';
|
|
33
|
+
column: string;
|
|
34
|
+
op: SQLiteComparisonOp;
|
|
35
|
+
value: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SQLiteConditionBetween {
|
|
39
|
+
type: 'between';
|
|
40
|
+
column: string;
|
|
41
|
+
lower: unknown;
|
|
42
|
+
upper: unknown;
|
|
43
|
+
includeLower: boolean;
|
|
44
|
+
includeUpper: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SQLiteConditionIn {
|
|
48
|
+
type: 'in';
|
|
49
|
+
column: string;
|
|
50
|
+
values: unknown[];
|
|
51
|
+
caseInsensitive?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SQLiteConditionNotIn {
|
|
55
|
+
type: 'notIn';
|
|
56
|
+
column: string;
|
|
57
|
+
values: unknown[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SQLiteConditionLike {
|
|
61
|
+
type: 'like';
|
|
62
|
+
column: string;
|
|
63
|
+
pattern: string;
|
|
64
|
+
caseInsensitive?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SQLiteConditionOr {
|
|
68
|
+
type: 'or';
|
|
69
|
+
conditions: SQLiteCondition[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SQLiteConditionCompoundEquals {
|
|
73
|
+
type: 'compoundEquals';
|
|
74
|
+
columns: string[];
|
|
75
|
+
values: unknown[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type SQLiteCondition =
|
|
79
|
+
| SQLiteConditionEquals
|
|
80
|
+
| SQLiteConditionComparison
|
|
81
|
+
| SQLiteConditionBetween
|
|
82
|
+
| SQLiteConditionIn
|
|
83
|
+
| SQLiteConditionNotIn
|
|
84
|
+
| SQLiteConditionLike
|
|
85
|
+
| SQLiteConditionOr
|
|
86
|
+
| SQLiteConditionCompoundEquals;
|
|
87
|
+
|
|
88
|
+
export interface SQLiteCollectionState<T> {
|
|
89
|
+
/** SQL-expressible WHERE conditions (ANDed together) */
|
|
90
|
+
sqlConditions: SQLiteCondition[];
|
|
91
|
+
/** JavaScript predicate for conditions that can't be expressed in SQL (e.g., arbitrary filter functions) */
|
|
92
|
+
jsPredicate?: (record: T, key: string, index: number) => boolean;
|
|
93
|
+
orderBy?: { index: string | string[]; direction: 'asc' | 'desc' };
|
|
94
|
+
reverse: boolean;
|
|
95
|
+
offset: number;
|
|
96
|
+
limit?: number;
|
|
97
|
+
distinct: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface TableEntry<T> {
|
|
101
|
+
key: string;
|
|
102
|
+
value: T;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface SQLiteOrderByOptions {
|
|
106
|
+
index: string | string[];
|
|
107
|
+
direction: 'asc' | 'desc';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface SQLiteIterateEntriesOptions {
|
|
111
|
+
orderBy?: SQLiteOrderByOptions;
|
|
112
|
+
chunkSize?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface SQLiteRunResult {
|
|
116
|
+
changes?: number;
|
|
117
|
+
lastId?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface SQLiteQueryResult {
|
|
121
|
+
columns?: string[];
|
|
122
|
+
values?: any[][];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface SQLiteDatabaseDriver {
|
|
126
|
+
readonly type: string;
|
|
127
|
+
readonly name: string;
|
|
128
|
+
open(): Promise<void>;
|
|
129
|
+
close(): Promise<void>;
|
|
130
|
+
execute(statement: string): Promise<void>;
|
|
131
|
+
run(statement: string, values?: any[]): Promise<SQLiteRunResult>;
|
|
132
|
+
query(statement: string, values?: any[]): Promise<SQLiteQueryResult>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type SQLiteMigrationDirection = 'upgrade' | 'downgrade';
|
|
136
|
+
|
|
137
|
+
export interface SQLiteMigrationContext {
|
|
138
|
+
direction: SQLiteMigrationDirection;
|
|
139
|
+
fromVersion: number;
|
|
140
|
+
toVersion: number;
|
|
141
|
+
execute: (statement: string) => Promise<void>;
|
|
142
|
+
run: (statement: string, values?: any[]) => Promise<SQLiteRunResult>;
|
|
143
|
+
query: (statement: string, values?: any[]) => Promise<SQLiteQueryResult>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type SQLiteMigrationHandler = (context: SQLiteMigrationContext) => Promise<void> | void;
|
|
147
|
+
|
|
148
|
+
export interface SQLiteVersionMigration {
|
|
149
|
+
upgrade?: SQLiteMigrationHandler;
|
|
150
|
+
downgrade?: SQLiteMigrationHandler;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface SQLiteVersionConfigurator {
|
|
154
|
+
upgrade(handler: SQLiteMigrationHandler): void;
|
|
155
|
+
downgrade(handler: SQLiteMigrationHandler): void;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface SQLiteSchemaDefinitionOptions {
|
|
159
|
+
migrations?: SQLiteVersionMigration;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface StorageSchemaDefinitionOptions {
|
|
163
|
+
sqlite?: SQLiteSchemaDefinitionOptions;
|
|
164
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { DexieQueryContext } from './dexie/DexieQueryContext';
|
|
2
|
+
import type { MemoryQueryContext } from './memory/MemoryQueryContext';
|
|
3
|
+
import type { TableSchemaDefinition } from './sqlite/schema';
|
|
4
|
+
import type { StorageSchemaDefinitionOptions } from './sqlite/types';
|
|
5
|
+
import type { SqliteQueryContext } from './sqlite/SqliteQueryContext';
|
|
6
|
+
|
|
7
|
+
export type TransactionMode = 'r' | 'rw';
|
|
8
|
+
|
|
9
|
+
export interface StorageAdapter {
|
|
10
|
+
readonly type: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
open(): Promise<void>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
delete(): Promise<void>;
|
|
15
|
+
defineSchema(version: number, schema: Record<string, TableSchemaDefinition>, options?: StorageSchemaDefinitionOptions): void;
|
|
16
|
+
table<T = any>(name: string): StorageTable<T>;
|
|
17
|
+
transaction<T>(mode: TransactionMode, tableNames: string[], callback: (context: StorageTransactionContext) => Promise<T>): Promise<T>;
|
|
18
|
+
query<R>(callback: (ctx: DexieQueryContext | SqliteQueryContext | MemoryQueryContext) => Promise<R>): Promise<R>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface StorageTransactionContext {
|
|
22
|
+
tables: Record<string, StorageTable<any>>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StorageCollection<T = any> {
|
|
26
|
+
first(): Promise<T | undefined>;
|
|
27
|
+
last(): Promise<T | undefined>;
|
|
28
|
+
each(callback: (item: T, index: number) => void | Promise<void>): Promise<void>;
|
|
29
|
+
eachKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void>;
|
|
30
|
+
eachPrimaryKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void>;
|
|
31
|
+
eachUniqueKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void>;
|
|
32
|
+
keys(): Promise<unknown[]>;
|
|
33
|
+
primaryKeys(): Promise<unknown[]>;
|
|
34
|
+
uniqueKeys(): Promise<unknown[]>;
|
|
35
|
+
count(): Promise<number>;
|
|
36
|
+
sortBy(key: string): Promise<T[]>;
|
|
37
|
+
distinct(): StorageCollection<T>;
|
|
38
|
+
jsFilter(predicate: (item: T) => boolean): StorageCollection<T>;
|
|
39
|
+
or(index: string): StorageWhereClause<T>;
|
|
40
|
+
clone(props?: Record<string, unknown>): StorageCollection<T>;
|
|
41
|
+
reverse(): StorageCollection<T>;
|
|
42
|
+
offset(offset: number): StorageCollection<T>;
|
|
43
|
+
limit(count: number): StorageCollection<T>;
|
|
44
|
+
toCollection(): StorageCollection<T>;
|
|
45
|
+
delete(): Promise<number>;
|
|
46
|
+
modify(changes: Partial<T> | ((item: T) => void | Promise<void>)): Promise<number>;
|
|
47
|
+
toArray(): Promise<T[]>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface StorageWhereClause<T = any> {
|
|
51
|
+
equals(value: any): StorageCollection<T>;
|
|
52
|
+
above(value: any): StorageCollection<T>;
|
|
53
|
+
aboveOrEqual(value: any): StorageCollection<T>;
|
|
54
|
+
below(value: any): StorageCollection<T>;
|
|
55
|
+
belowOrEqual(value: any): StorageCollection<T>;
|
|
56
|
+
between(lower: any, upper: any, includeLower?: boolean, includeUpper?: boolean): StorageCollection<T>;
|
|
57
|
+
inAnyRange(ranges: Array<[any, any]>, options?: { includeLower?: boolean; includeUpper?: boolean }): StorageCollection<T>;
|
|
58
|
+
startsWith(prefix: string): StorageCollection<T>;
|
|
59
|
+
startsWithIgnoreCase(prefix: string): StorageCollection<T>;
|
|
60
|
+
startsWithAnyOf(...prefixes: string[]): StorageCollection<T>;
|
|
61
|
+
startsWithAnyOf(prefixes: string[]): StorageCollection<T>;
|
|
62
|
+
startsWithAnyOfIgnoreCase(...prefixes: string[]): StorageCollection<T>;
|
|
63
|
+
startsWithAnyOfIgnoreCase(prefixes: string[]): StorageCollection<T>;
|
|
64
|
+
equalsIgnoreCase(value: string): StorageCollection<T>;
|
|
65
|
+
anyOf(...values: any[]): StorageCollection<T>;
|
|
66
|
+
anyOf(values: any[]): StorageCollection<T>;
|
|
67
|
+
anyOfIgnoreCase(...values: string[]): StorageCollection<T>;
|
|
68
|
+
anyOfIgnoreCase(values: string[]): StorageCollection<T>;
|
|
69
|
+
noneOf(...values: any[]): StorageCollection<T>;
|
|
70
|
+
noneOf(values: any[]): StorageCollection<T>;
|
|
71
|
+
notEqual(value: any): StorageCollection<T>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface StorageTable<T = any> {
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly schema: unknown;
|
|
77
|
+
readonly hook: unknown;
|
|
78
|
+
add(item: T): Promise<unknown>;
|
|
79
|
+
put(item: T): Promise<unknown>;
|
|
80
|
+
update(key: unknown, changes: Partial<T>): Promise<number>;
|
|
81
|
+
delete(key: unknown): Promise<void>;
|
|
82
|
+
clear(): Promise<void>;
|
|
83
|
+
get(key: unknown): Promise<T | undefined>;
|
|
84
|
+
toArray(): Promise<T[]>;
|
|
85
|
+
count(): Promise<number>;
|
|
86
|
+
bulkAdd(items: T[]): Promise<unknown>;
|
|
87
|
+
bulkPut(items: T[]): Promise<unknown>;
|
|
88
|
+
bulkGet(keys: Array<unknown>): Promise<Array<T | undefined>>;
|
|
89
|
+
bulkUpdate(keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>): Promise<number>;
|
|
90
|
+
bulkDelete(keys: Array<unknown>): Promise<void>;
|
|
91
|
+
where(index: string | string[]): StorageWhereClause<T>;
|
|
92
|
+
orderBy(index: string | string[]): StorageCollection<T>;
|
|
93
|
+
reverse(): StorageCollection<T>;
|
|
94
|
+
offset(offset: number): StorageCollection<T>;
|
|
95
|
+
limit(count: number): StorageCollection<T>;
|
|
96
|
+
mapToClass(ctor: new (...args: any[]) => any): StorageTable<T>;
|
|
97
|
+
each(callback: (item: T) => void | Promise<void>): Promise<void>;
|
|
98
|
+
jsFilter(predicate: (item: T) => boolean): StorageCollection<T>;
|
|
99
|
+
// The "raw" property exposes the underlying storage operations without Dync sync logic.
|
|
100
|
+
readonly raw: {
|
|
101
|
+
add(item: T): Promise<unknown>;
|
|
102
|
+
put(item: T): Promise<unknown>;
|
|
103
|
+
update(key: unknown, changes: Partial<T>): Promise<number>;
|
|
104
|
+
delete(key: unknown): Promise<void>;
|
|
105
|
+
get(key: unknown): Promise<T | undefined>;
|
|
106
|
+
bulkAdd(items: T[]): Promise<unknown>;
|
|
107
|
+
bulkPut(items: T[]): Promise<unknown>;
|
|
108
|
+
bulkUpdate(keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>): Promise<number>;
|
|
109
|
+
bulkDelete(keys: Array<unknown>): Promise<void>;
|
|
110
|
+
clear(): Promise<void>;
|
|
111
|
+
};
|
|
112
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Logger, LogLevel } from './logger';
|
|
2
|
+
import type { StorageTable } from './storage/types';
|
|
3
|
+
|
|
4
|
+
export const SERVER_PK = 'id';
|
|
5
|
+
export const LOCAL_PK = '_localId';
|
|
6
|
+
export const UPDATED_AT = 'updated_at';
|
|
7
|
+
|
|
8
|
+
export interface SyncedRecord {
|
|
9
|
+
_localId: string;
|
|
10
|
+
id?: any;
|
|
11
|
+
updated_at: string;
|
|
12
|
+
[k: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ApiFunctions {
|
|
16
|
+
add: (item: any) => Promise<any | undefined>;
|
|
17
|
+
update: (id: any, changes: any, item: any) => Promise<boolean>;
|
|
18
|
+
remove: (id: any) => Promise<void>;
|
|
19
|
+
list: (lastUpdatedAt: Date) => Promise<any[]>;
|
|
20
|
+
firstLoad?: (lastId: any) => Promise<any[]>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Batch Sync Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Payload for a single change in a batch push request.
|
|
29
|
+
*/
|
|
30
|
+
export interface BatchPushPayload {
|
|
31
|
+
table: string;
|
|
32
|
+
action: 'add' | 'update' | 'remove';
|
|
33
|
+
localId: string;
|
|
34
|
+
// Server-assigned ID (for update/remove)
|
|
35
|
+
id?: any;
|
|
36
|
+
// The data to sync (for add/update)
|
|
37
|
+
data?: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Result for a single change in a batch push response.
|
|
42
|
+
*/
|
|
43
|
+
export interface BatchPushResult {
|
|
44
|
+
// Client-generated local ID - used to correlate with the request
|
|
45
|
+
localId: string;
|
|
46
|
+
success: boolean;
|
|
47
|
+
// Server-assigned ID (for successful adds)
|
|
48
|
+
id?: any;
|
|
49
|
+
// Server-assigned updated_at (for successful adds/updates)
|
|
50
|
+
updated_at?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Result for batch first load operation.
|
|
56
|
+
*/
|
|
57
|
+
export interface BatchFirstLoadResult {
|
|
58
|
+
// Data grouped by table name
|
|
59
|
+
data: Record<string, any[]>;
|
|
60
|
+
// Pagination cursors by table name. Undefined value = table is complete
|
|
61
|
+
cursors: Record<string, any>;
|
|
62
|
+
// Whether there's more data to load for any table
|
|
63
|
+
hasMore: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Batch sync configuration for
|
|
68
|
+
*/
|
|
69
|
+
export interface BatchSync {
|
|
70
|
+
/**
|
|
71
|
+
* Array of table names to sync.
|
|
72
|
+
*/
|
|
73
|
+
syncTables: string[];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Push all pending changes to the server in a single request.
|
|
77
|
+
* @param changes Array of changes to push
|
|
78
|
+
* @returns Array of results, one per change, correlated by localId
|
|
79
|
+
*/
|
|
80
|
+
push: (changes: BatchPushPayload[]) => Promise<BatchPushResult[]>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Pull changes from server since last sync.
|
|
84
|
+
* @param since Map of table name to last pulled timestamp
|
|
85
|
+
* @returns Map of table name to array of changed records
|
|
86
|
+
*/
|
|
87
|
+
pull: (since: Record<string, Date>) => Promise<Record<string, any[]>>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Initial data load for all tables (optional).
|
|
91
|
+
* @param cursors Map of table name to pagination cursor
|
|
92
|
+
* @returns Batch first load result with data, cursors, and hasMore flag
|
|
93
|
+
*/
|
|
94
|
+
firstLoad?: (cursors: Record<string, any>) => Promise<BatchFirstLoadResult>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type MissingRemoteRecordStrategy = 'ignore' | 'delete-local-record' | 'insert-remote-record';
|
|
98
|
+
export type ConflictResolutionStrategy = 'local-wins' | 'remote-wins' | 'try-shallow-merge';
|
|
99
|
+
|
|
100
|
+
export type AfterRemoteAddCallback = (stateKey: string, item: SyncedRecord) => void;
|
|
101
|
+
export type MissingRemoteRecordDuringUpdateCallback = (strategy: MissingRemoteRecordStrategy, item: SyncedRecord) => void;
|
|
102
|
+
|
|
103
|
+
export interface SyncOptions {
|
|
104
|
+
syncInterval?: number;
|
|
105
|
+
logger?: Logger;
|
|
106
|
+
minLogLevel?: LogLevel;
|
|
107
|
+
onAfterRemoteAdd?: AfterRemoteAddCallback;
|
|
108
|
+
missingRemoteRecordDuringUpdateStrategy?: MissingRemoteRecordStrategy;
|
|
109
|
+
onAfterMissingRemoteRecordDuringUpdate?: MissingRemoteRecordDuringUpdateCallback;
|
|
110
|
+
conflictResolutionStrategy?: ConflictResolutionStrategy;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface FirstLoadProgress {
|
|
114
|
+
table: string;
|
|
115
|
+
inserted: number;
|
|
116
|
+
updated: number;
|
|
117
|
+
total: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type FirstLoadProgressCallback = (progress: FirstLoadProgress) => void;
|
|
121
|
+
|
|
122
|
+
export type SyncApi = {
|
|
123
|
+
enable: (enabled: boolean) => Promise<void>;
|
|
124
|
+
startFirstLoad: (onProgress?: FirstLoadProgressCallback) => Promise<void>;
|
|
125
|
+
getState: () => SyncState;
|
|
126
|
+
resolveConflict: (localId: string, keepLocal: boolean) => Promise<void>;
|
|
127
|
+
onStateChange: (fn: (state: SyncState) => void) => () => void;
|
|
128
|
+
onMutation: (fn: (event: MutationEvent) => void) => () => void;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export interface MutationEvent {
|
|
132
|
+
type: 'add' | 'update' | 'delete' | 'bulkAdd' | 'bulkPut' | 'bulkDelete' | 'clear' | 'put' | 'modify' | 'pull';
|
|
133
|
+
tableName: string;
|
|
134
|
+
keys?: unknown[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface PersistedSyncState {
|
|
138
|
+
firstLoadDone: boolean;
|
|
139
|
+
pendingChanges: PendingChange[];
|
|
140
|
+
lastPulled: Record<string, string>;
|
|
141
|
+
error?: Error;
|
|
142
|
+
conflicts?: Record<string, Conflict>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type SyncStatus = 'disabled' | 'disabling' | 'idle' | 'syncing' | 'error';
|
|
146
|
+
|
|
147
|
+
export interface SyncState extends PersistedSyncState {
|
|
148
|
+
status: SyncStatus;
|
|
149
|
+
hydrated: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export enum SyncAction {
|
|
153
|
+
Create = 'create',
|
|
154
|
+
Update = 'update',
|
|
155
|
+
Remove = 'remove', // Remote removes are a noop if no record found
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface PendingChange {
|
|
159
|
+
action: SyncAction;
|
|
160
|
+
stateKey: string;
|
|
161
|
+
localId: string;
|
|
162
|
+
id?: any;
|
|
163
|
+
version: number;
|
|
164
|
+
changes?: any;
|
|
165
|
+
before?: any;
|
|
166
|
+
after?: any;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface Conflict {
|
|
170
|
+
stateKey: string;
|
|
171
|
+
fields: FieldConflict[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface FieldConflict {
|
|
175
|
+
key: string;
|
|
176
|
+
localValue: any;
|
|
177
|
+
remoteValue: any;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type TableMap<TStoreMap extends Record<string, unknown>> = {
|
|
181
|
+
[K in keyof TStoreMap]: StorageTable<TStoreMap[K]>;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export type VisibilitySubscription = {
|
|
185
|
+
remove: () => void;
|
|
186
|
+
};
|
package/src/wa-sqlite.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// wa-sqlite Browser SQLite Driver
|
|
2
|
+
// Import this entry point for browser/web builds with SQLite support
|
|
3
|
+
export { WaSqliteDriver, type WaSqliteDriverOptions, type WaSqliteVfsType } from './storage/sqlite/drivers/WaSqliteDriver';
|
|
4
|
+
export type { SQLiteDatabaseDriver, SQLiteQueryResult, SQLiteRunResult } from './storage/sqlite/types';
|