@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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/capacitor.cjs +228 -0
  4. package/dist/capacitor.cjs.map +1 -0
  5. package/dist/capacitor.d.cts +62 -0
  6. package/dist/capacitor.d.ts +62 -0
  7. package/dist/capacitor.js +9 -0
  8. package/dist/capacitor.js.map +1 -0
  9. package/dist/chunk-LGHOZECP.js +3884 -0
  10. package/dist/chunk-LGHOZECP.js.map +1 -0
  11. package/dist/chunk-SQB6E7V2.js +191 -0
  12. package/dist/chunk-SQB6E7V2.js.map +1 -0
  13. package/dist/dexie-Bv-fV10P.d.cts +444 -0
  14. package/dist/dexie-DJFApKsM.d.ts +444 -0
  15. package/dist/dexie.cjs +381 -0
  16. package/dist/dexie.cjs.map +1 -0
  17. package/dist/dexie.d.cts +3 -0
  18. package/dist/dexie.d.ts +3 -0
  19. package/dist/dexie.js +343 -0
  20. package/dist/dexie.js.map +1 -0
  21. package/dist/expoSqlite.cjs +98 -0
  22. package/dist/expoSqlite.cjs.map +1 -0
  23. package/dist/expoSqlite.d.cts +17 -0
  24. package/dist/expoSqlite.d.ts +17 -0
  25. package/dist/expoSqlite.js +61 -0
  26. package/dist/expoSqlite.js.map +1 -0
  27. package/dist/index.cjs +3916 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +8 -0
  30. package/dist/index.d.ts +8 -0
  31. package/dist/index.js +20 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/index.shared-CPIge2ZM.d.ts +234 -0
  34. package/dist/index.shared-YSn6c01d.d.cts +234 -0
  35. package/dist/node.cjs +126 -0
  36. package/dist/node.cjs.map +1 -0
  37. package/dist/node.d.cts +80 -0
  38. package/dist/node.d.ts +80 -0
  39. package/dist/node.js +89 -0
  40. package/dist/node.js.map +1 -0
  41. package/dist/react/index.cjs +1754 -0
  42. package/dist/react/index.cjs.map +1 -0
  43. package/dist/react/index.d.cts +40 -0
  44. package/dist/react/index.d.ts +40 -0
  45. package/dist/react/index.js +78 -0
  46. package/dist/react/index.js.map +1 -0
  47. package/dist/types-CSbIAfu2.d.cts +46 -0
  48. package/dist/types-CSbIAfu2.d.ts +46 -0
  49. package/dist/wa-sqlite.cjs +318 -0
  50. package/dist/wa-sqlite.cjs.map +1 -0
  51. package/dist/wa-sqlite.d.cts +175 -0
  52. package/dist/wa-sqlite.d.ts +175 -0
  53. package/dist/wa-sqlite.js +281 -0
  54. package/dist/wa-sqlite.js.map +1 -0
  55. package/package.json +171 -0
  56. package/src/addVisibilityChangeListener.native.ts +33 -0
  57. package/src/addVisibilityChangeListener.ts +24 -0
  58. package/src/capacitor.ts +4 -0
  59. package/src/core/StateManager.ts +272 -0
  60. package/src/core/firstLoad.ts +332 -0
  61. package/src/core/pullOperations.ts +212 -0
  62. package/src/core/pushOperations.ts +290 -0
  63. package/src/core/tableEnhancers.ts +457 -0
  64. package/src/core/types.ts +3 -0
  65. package/src/createLocalId.native.ts +8 -0
  66. package/src/createLocalId.ts +6 -0
  67. package/src/dexie.ts +2 -0
  68. package/src/expoSqlite.ts +2 -0
  69. package/src/helpers.ts +87 -0
  70. package/src/index.native.ts +28 -0
  71. package/src/index.shared.ts +613 -0
  72. package/src/index.ts +28 -0
  73. package/src/logger.ts +26 -0
  74. package/src/node.ts +4 -0
  75. package/src/react/index.ts +2 -0
  76. package/src/react/useDync.ts +156 -0
  77. package/src/storage/dexie/DexieAdapter.ts +72 -0
  78. package/src/storage/dexie/DexieQueryContext.ts +14 -0
  79. package/src/storage/dexie/DexieStorageCollection.ts +124 -0
  80. package/src/storage/dexie/DexieStorageTable.ts +123 -0
  81. package/src/storage/dexie/DexieStorageWhereClause.ts +103 -0
  82. package/src/storage/dexie/helpers.ts +1 -0
  83. package/src/storage/dexie/index.ts +7 -0
  84. package/src/storage/memory/MemoryAdapter.ts +55 -0
  85. package/src/storage/memory/MemoryCollection.ts +215 -0
  86. package/src/storage/memory/MemoryQueryContext.ts +14 -0
  87. package/src/storage/memory/MemoryTable.ts +336 -0
  88. package/src/storage/memory/MemoryWhereClause.ts +134 -0
  89. package/src/storage/memory/index.ts +7 -0
  90. package/src/storage/memory/types.ts +24 -0
  91. package/src/storage/sqlite/SQLiteAdapter.ts +564 -0
  92. package/src/storage/sqlite/SQLiteCollection.ts +294 -0
  93. package/src/storage/sqlite/SQLiteTable.ts +604 -0
  94. package/src/storage/sqlite/SQLiteWhereClause.ts +341 -0
  95. package/src/storage/sqlite/SqliteQueryContext.ts +30 -0
  96. package/src/storage/sqlite/drivers/BetterSqlite3Driver.ts +156 -0
  97. package/src/storage/sqlite/drivers/CapacitorFastSqlDriver.ts +114 -0
  98. package/src/storage/sqlite/drivers/CapacitorSQLiteDriver.ts +137 -0
  99. package/src/storage/sqlite/drivers/ExpoSQLiteDriver.native.ts +67 -0
  100. package/src/storage/sqlite/drivers/WaSqliteDriver.ts +537 -0
  101. package/src/storage/sqlite/drivers/wa-sqlite-vfs.d.ts +46 -0
  102. package/src/storage/sqlite/helpers.ts +144 -0
  103. package/src/storage/sqlite/index.ts +11 -0
  104. package/src/storage/sqlite/schema.ts +44 -0
  105. package/src/storage/sqlite/types.ts +164 -0
  106. package/src/storage/types.ts +112 -0
  107. package/src/types.ts +186 -0
  108. package/src/wa-sqlite.ts +4 -0
@@ -0,0 +1,156 @@
1
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
+ import { Dync } from '../index';
3
+ import type { ApiFunctions, BatchSync, MutationEvent, SyncOptions, SyncState } from '../types';
4
+ import type { StorageAdapter } from '../storage/types';
5
+
6
+ export interface MakeDyncConfigPerTable {
7
+ databaseName: string;
8
+ syncApis: Record<string, ApiFunctions>;
9
+ storageAdapter: StorageAdapter;
10
+ options?: SyncOptions;
11
+ }
12
+
13
+ export interface MakeDyncConfigBatch {
14
+ databaseName: string;
15
+ batchSync: BatchSync;
16
+ storageAdapter: StorageAdapter;
17
+ options?: SyncOptions;
18
+ }
19
+
20
+ export type MakeDyncConfig = MakeDyncConfigPerTable | MakeDyncConfigBatch;
21
+
22
+ export interface UseDyncValue<TStoreMap extends Record<string, any>> {
23
+ db: Dync<TStoreMap>;
24
+ syncState: SyncState;
25
+ }
26
+
27
+ /**
28
+ * A hook returned by makeDync that observes database queries and automatically
29
+ * re-runs them when data changes. The db instance is passed to the querier callback.
30
+ *
31
+ * @param querier - Function that receives db and returns query result
32
+ * @param deps - Optional React dependency list for re-running the query
33
+ * @param tables - Optional array of table names to watch for mutations, otherwise it's all tables
34
+ * @returns The query result, or undefined if not yet available
35
+ */
36
+ export type BoundUseLiveQuery<TStoreMap extends Record<string, any>> = <T>(
37
+ querier: (db: Dync<TStoreMap>) => Promise<T> | T,
38
+ deps?: React.DependencyList,
39
+ tables?: string[],
40
+ ) => T | undefined;
41
+
42
+ export interface MakeDyncResult<TStoreMap extends Record<string, any>> {
43
+ // The Dync database instance
44
+ db: Dync<TStoreMap>;
45
+ // Hook to get db and syncState in components
46
+ useDync: () => UseDyncValue<TStoreMap>;
47
+ // Hook for live queries - db is passed to the querier callback
48
+ useLiveQuery: BoundUseLiveQuery<TStoreMap>;
49
+ }
50
+
51
+ export function makeDync<TStoreMap extends Record<string, any> = Record<string, unknown>>(config: MakeDyncConfig): MakeDyncResult<TStoreMap> {
52
+ // Determine which mode based on config shape
53
+ const db =
54
+ 'syncApis' in config
55
+ ? new Dync<TStoreMap>(config.databaseName, config.syncApis, config.storageAdapter, config.options)
56
+ : new Dync<TStoreMap>(config.databaseName, config.batchSync, config.storageAdapter, config.options);
57
+
58
+ // Cache to provide referential stability (React requires stable references)
59
+ // Updated when subscription fires OR when getSnapshot detects value changes
60
+ let cachedState = db.sync.getState();
61
+
62
+ const subscribe = (listener: () => void) =>
63
+ db.sync.onStateChange((nextState) => {
64
+ // Update cache immediately when notified, before React calls getSnapshot
65
+ cachedState = nextState;
66
+ listener();
67
+ });
68
+
69
+ // getSnapshot returns cached state, but also checks for changes that happened
70
+ // before subscription was active (e.g., hydration during initial mount)
71
+ const getSnapshot = () => {
72
+ const fresh = db.sync.getState();
73
+ if (JSON.stringify(fresh) !== JSON.stringify(cachedState)) {
74
+ cachedState = fresh;
75
+ }
76
+ return cachedState;
77
+ };
78
+
79
+ const useDync = () => {
80
+ const syncState = useSyncExternalStore<SyncState>(subscribe, getSnapshot, getSnapshot);
81
+ return { db, syncState } as UseDyncValue<TStoreMap>;
82
+ };
83
+
84
+ // Create a bound useLiveQuery that passes db to the querier
85
+ const boundUseLiveQuery: BoundUseLiveQuery<TStoreMap> = <T>(
86
+ querier: (db: Dync<TStoreMap>) => Promise<T> | T,
87
+ deps: React.DependencyList = [],
88
+ tables?: string[],
89
+ ): T | undefined => {
90
+ return useLiveQueryImpl(db, querier, deps, tables);
91
+ };
92
+
93
+ return {
94
+ db,
95
+ useDync,
96
+ useLiveQuery: boundUseLiveQuery,
97
+ };
98
+ }
99
+
100
+ function useLiveQueryImpl<TStoreMap extends Record<string, any>, T>(
101
+ db: Dync<TStoreMap>,
102
+ querier: (db: Dync<TStoreMap>) => Promise<T> | T,
103
+ deps: React.DependencyList = [],
104
+ tables?: string[],
105
+ ): T | undefined {
106
+ const [result, setResult] = useState<T | undefined>(undefined);
107
+ const [, setError] = useState<Error | null>(null);
108
+ const isMountedRef = useRef(true);
109
+ const queryVersionRef = useRef(0);
110
+ const querierRef = useRef(querier);
111
+ const tablesRef = useRef(tables);
112
+
113
+ // Keep refs up to date
114
+ querierRef.current = querier;
115
+ tablesRef.current = tables;
116
+
117
+ const runQuery = useCallback(async () => {
118
+ const currentVersion = ++queryVersionRef.current;
119
+ try {
120
+ const queryResult = await querierRef.current(db);
121
+ // Only update if still mounted and this is the latest query
122
+ if (isMountedRef.current && currentVersion === queryVersionRef.current) {
123
+ setResult(queryResult);
124
+ setError(null);
125
+ }
126
+ } catch (err) {
127
+ if (isMountedRef.current && currentVersion === queryVersionRef.current) {
128
+ setError(err as Error);
129
+ }
130
+ }
131
+ }, [db]);
132
+
133
+ // Re-run query when deps change
134
+ useEffect(() => {
135
+ runQuery();
136
+ }, [...deps, runQuery]);
137
+
138
+ useEffect(() => {
139
+ isMountedRef.current = true;
140
+
141
+ // Subscribe to mutation events
142
+ const unsubscribe = db.sync.onMutation((event: MutationEvent) => {
143
+ // Only re-run if no tables filter specified, or if the mutation affects a watched table
144
+ if (!tablesRef.current || tablesRef.current.includes(event.tableName)) {
145
+ runQuery();
146
+ }
147
+ });
148
+
149
+ return () => {
150
+ isMountedRef.current = false;
151
+ unsubscribe();
152
+ };
153
+ }, [db, runQuery]);
154
+
155
+ return result;
156
+ }
@@ -0,0 +1,72 @@
1
+ import Dexie from 'dexie';
2
+ import type { StorageAdapter, StorageTable, StorageTransactionContext, TransactionMode } from '../types';
3
+ import type { StorageSchemaDefinitionOptions } from '../sqlite/types';
4
+ import type { TableSchemaDefinition } from '../sqlite/schema';
5
+ import { DexieQueryContext } from './DexieQueryContext';
6
+ import { DexieStorageTable } from './DexieStorageTable';
7
+
8
+ export class DexieAdapter implements StorageAdapter {
9
+ readonly type = 'DexieAdapter';
10
+ readonly name: string;
11
+ readonly db: Dexie;
12
+ private readonly tableCache = new Map<string, StorageTable<any>>();
13
+
14
+ constructor(databaseName: string) {
15
+ this.db = new Dexie(databaseName);
16
+ this.name = this.db.name;
17
+ }
18
+
19
+ async open(): Promise<void> {
20
+ // Dexie auto-opens on first operation, so this is typically a no-op.
21
+ // However, after delete() we explicitly re-open to ensure continued usability.
22
+ }
23
+
24
+ async close(): Promise<void> {
25
+ if (this.db.isOpen()) {
26
+ this.db.close();
27
+ }
28
+ this.tableCache.clear();
29
+ }
30
+
31
+ async delete(): Promise<void> {
32
+ await this.db.delete();
33
+ this.tableCache.clear();
34
+ // Dexie.delete() closes the database, so re-open it to allow continued use.
35
+ // Without this, subsequent operations would fail with DatabaseClosedError.
36
+ await this.db.open();
37
+ }
38
+
39
+ async query<R>(callback: (ctx: DexieQueryContext) => Promise<R>): Promise<R> {
40
+ return callback(new DexieQueryContext(this));
41
+ }
42
+
43
+ defineSchema(version: number, schema: Record<string, TableSchemaDefinition>, _options?: StorageSchemaDefinitionOptions): void {
44
+ const normalized: Record<string, string> = {};
45
+ for (const [tableName, definition] of Object.entries(schema)) {
46
+ if (typeof definition !== 'string') {
47
+ throw new Error(`DexieAdapter requires string schema definitions. Received non-string definition for table '${tableName}'.`);
48
+ }
49
+ normalized[tableName] = definition;
50
+ }
51
+ const dexieVersion = this.db.version(version);
52
+ dexieVersion.stores(normalized);
53
+ }
54
+
55
+ table<T = any>(name: string): StorageTable<T> {
56
+ if (!this.tableCache.has(name)) {
57
+ const table = this.db.table<T>(name);
58
+ this.tableCache.set(name, new DexieStorageTable(table));
59
+ }
60
+ return this.tableCache.get(name)! as StorageTable<T>;
61
+ }
62
+
63
+ async transaction<T>(mode: TransactionMode, tableNames: string[], callback: (context: StorageTransactionContext) => Promise<T>): Promise<T> {
64
+ return (this.db.transaction as any)(mode, ...tableNames, async () => {
65
+ const tables: Record<string, StorageTable<any>> = {};
66
+ for (const tableName of tableNames) {
67
+ tables[tableName] = this.table(tableName);
68
+ }
69
+ return callback({ tables });
70
+ });
71
+ }
72
+ }
@@ -0,0 +1,14 @@
1
+ import type { StorageTable, StorageTransactionContext, TransactionMode } from '../types';
2
+ import type { DexieAdapter } from './DexieAdapter';
3
+
4
+ export class DexieQueryContext {
5
+ constructor(private readonly adapter: DexieAdapter) {}
6
+
7
+ table<T = any>(name: string): StorageTable<T> {
8
+ return this.adapter.table(name);
9
+ }
10
+
11
+ transaction<T>(mode: TransactionMode, tableNames: string[], callback: (context: StorageTransactionContext) => Promise<T>): Promise<T> {
12
+ return this.adapter.transaction(mode, tableNames, callback);
13
+ }
14
+ }
@@ -0,0 +1,124 @@
1
+ import type { Collection as DexieCollection } from 'dexie';
2
+ import type { StorageCollection, StorageWhereClause } from '../types';
3
+ import { normalizeIndexName } from './helpers';
4
+ import { DexieStorageWhereClause } from './DexieStorageWhereClause';
5
+
6
+ export class DexieStorageCollection<T = any> implements StorageCollection<T> {
7
+ private readonly collection: DexieCollection<T, any, T>;
8
+
9
+ constructor(collection: DexieCollection<T, any, T>) {
10
+ this.collection = collection;
11
+ }
12
+
13
+ first(): Promise<T | undefined> {
14
+ return this.collection.first();
15
+ }
16
+
17
+ async last(): Promise<T | undefined> {
18
+ return this.collection.last();
19
+ }
20
+
21
+ async each(callback: (item: T, index: number) => void | Promise<void>): Promise<void> {
22
+ const tasks: Array<void | Promise<void>> = [];
23
+ let index = 0;
24
+ await this.collection.each((item) => {
25
+ tasks.push(callback(item, index));
26
+ index += 1;
27
+ });
28
+ await Promise.all(tasks.map((task) => (task ? Promise.resolve(task) : Promise.resolve())));
29
+ }
30
+
31
+ async eachKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void> {
32
+ const tasks: Array<void | Promise<void>> = [];
33
+ let index = 0;
34
+ await this.collection.eachKey((key) => {
35
+ tasks.push(callback(key, index));
36
+ index += 1;
37
+ });
38
+ await Promise.all(tasks.map((task) => (task ? Promise.resolve(task) : Promise.resolve())));
39
+ }
40
+
41
+ async eachPrimaryKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void> {
42
+ const tasks: Array<void | Promise<void>> = [];
43
+ let index = 0;
44
+ await this.collection.eachPrimaryKey((key) => {
45
+ tasks.push(callback(key, index));
46
+ index += 1;
47
+ });
48
+ await Promise.all(tasks.map((task) => (task ? Promise.resolve(task) : Promise.resolve())));
49
+ }
50
+
51
+ async eachUniqueKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void> {
52
+ const tasks: Array<void | Promise<void>> = [];
53
+ let index = 0;
54
+ await this.collection.eachUniqueKey((key) => {
55
+ tasks.push(callback(key, index));
56
+ index += 1;
57
+ });
58
+ await Promise.all(tasks.map((task) => (task ? Promise.resolve(task) : Promise.resolve())));
59
+ }
60
+
61
+ keys(): Promise<unknown[]> {
62
+ return this.collection.keys();
63
+ }
64
+
65
+ primaryKeys(): Promise<unknown[]> {
66
+ return this.collection.primaryKeys();
67
+ }
68
+
69
+ uniqueKeys(): Promise<unknown[]> {
70
+ return this.collection.uniqueKeys();
71
+ }
72
+
73
+ count(): Promise<number> {
74
+ return this.collection.count();
75
+ }
76
+
77
+ sortBy(key: string): Promise<T[]> {
78
+ return this.collection.sortBy(key);
79
+ }
80
+
81
+ distinct(): StorageCollection<T> {
82
+ return new DexieStorageCollection(this.collection.distinct());
83
+ }
84
+
85
+ jsFilter(predicate: (item: T) => boolean): StorageCollection<T> {
86
+ return new DexieStorageCollection(this.collection.filter(predicate));
87
+ }
88
+
89
+ or(index: string): StorageWhereClause<T> {
90
+ return new DexieStorageWhereClause(this.collection.or(normalizeIndexName(index)));
91
+ }
92
+
93
+ clone(props?: Record<string, unknown>): StorageCollection<T> {
94
+ return new DexieStorageCollection(this.collection.clone(props));
95
+ }
96
+
97
+ reverse(): StorageCollection<T> {
98
+ return new DexieStorageCollection(this.collection.reverse());
99
+ }
100
+
101
+ offset(offset: number): StorageCollection<T> {
102
+ return new DexieStorageCollection(this.collection.offset(offset));
103
+ }
104
+
105
+ limit(count: number): StorageCollection<T> {
106
+ return new DexieStorageCollection(this.collection.limit(count));
107
+ }
108
+
109
+ toCollection(): StorageCollection<T> {
110
+ return this.clone();
111
+ }
112
+
113
+ delete(): Promise<number> {
114
+ return this.collection.delete();
115
+ }
116
+
117
+ modify(changes: Partial<T> | ((item: T) => void | Promise<void>)): Promise<number> {
118
+ return this.collection.modify(changes as any);
119
+ }
120
+
121
+ toArray(): Promise<T[]> {
122
+ return this.collection.toArray();
123
+ }
124
+ }
@@ -0,0 +1,123 @@
1
+ import type { Table as DexieTable } from 'dexie';
2
+ import type { StorageCollection, StorageTable, StorageWhereClause } from '../types';
3
+ import { normalizeIndexName } from './helpers';
4
+ import { DexieStorageCollection } from './DexieStorageCollection';
5
+ import { DexieStorageWhereClause } from './DexieStorageWhereClause';
6
+
7
+ export class DexieStorageTable<T = any> implements StorageTable<T> {
8
+ readonly name: string;
9
+ readonly schema: unknown;
10
+ readonly primaryKey: unknown;
11
+ readonly hook: unknown;
12
+ readonly raw = Object.freeze({
13
+ add: (item: T) => this.table.add(item),
14
+ put: (item: T) => this.table.put(item),
15
+ update: (key: unknown, changes: Partial<T>) => this.table.update(key as any, changes as any),
16
+ delete: (key: unknown) => this.table.delete(key as any),
17
+ get: (key: unknown) => this.table.get(key as any),
18
+ bulkAdd: (items: T[]) => this.table.bulkAdd(items),
19
+ bulkPut: (items: T[]) => this.table.bulkPut(items),
20
+ bulkUpdate: (keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>) => this.table.bulkUpdate(keysAndChanges as any),
21
+ bulkDelete: (keys: Array<unknown>) => this.table.bulkDelete(keys as any),
22
+ clear: () => this.table.clear(),
23
+ });
24
+
25
+ private readonly table: DexieTable<T, any, T>;
26
+
27
+ constructor(table: DexieTable<T, any, T>) {
28
+ this.table = table;
29
+ this.name = table.name;
30
+ this.schema = table.schema;
31
+ this.primaryKey = table.schema?.primKey;
32
+ this.hook = table.hook;
33
+ }
34
+
35
+ add(item: T): Promise<unknown> {
36
+ return this.table.add(item);
37
+ }
38
+
39
+ put(item: T): Promise<unknown> {
40
+ return this.table.put(item);
41
+ }
42
+
43
+ update(key: unknown, changes: Partial<T>): Promise<number> {
44
+ return this.table.update(key as any, changes as any);
45
+ }
46
+
47
+ delete(key: unknown): Promise<void> {
48
+ return this.table.delete(key as any).then(() => undefined);
49
+ }
50
+
51
+ clear(): Promise<void> {
52
+ return this.table.clear();
53
+ }
54
+
55
+ get(key: unknown): Promise<T | undefined> {
56
+ return this.table.get(key as any);
57
+ }
58
+
59
+ toArray(): Promise<T[]> {
60
+ return this.table.toArray();
61
+ }
62
+
63
+ count(): Promise<number> {
64
+ return this.table.count();
65
+ }
66
+
67
+ bulkAdd(items: T[]): Promise<unknown> {
68
+ return this.table.bulkAdd(items);
69
+ }
70
+
71
+ bulkPut(items: T[]): Promise<unknown> {
72
+ return this.table.bulkPut(items);
73
+ }
74
+
75
+ bulkGet(keys: Array<unknown>): Promise<Array<T | undefined>> {
76
+ return this.table.bulkGet(keys as any);
77
+ }
78
+
79
+ bulkUpdate(keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>): Promise<number> {
80
+ return this.table.bulkUpdate(keysAndChanges as any);
81
+ }
82
+
83
+ bulkDelete(keys: Array<unknown>): Promise<void> {
84
+ return this.table.bulkDelete(keys as any);
85
+ }
86
+
87
+ where(index: string | string[]): StorageWhereClause<T> {
88
+ return new DexieStorageWhereClause(this.table.where(normalizeIndexName(index)));
89
+ }
90
+
91
+ orderBy(index: string | string[]): StorageCollection<T> {
92
+ return new DexieStorageCollection(this.table.orderBy(normalizeIndexName(index)));
93
+ }
94
+
95
+ reverse(): StorageCollection<T> {
96
+ return new DexieStorageCollection(this.table.reverse());
97
+ }
98
+
99
+ offset(offset: number): StorageCollection<T> {
100
+ return new DexieStorageCollection(this.table.offset(offset));
101
+ }
102
+
103
+ limit(count: number): StorageCollection<T> {
104
+ return new DexieStorageCollection(this.table.limit(count));
105
+ }
106
+
107
+ mapToClass(ctor: new (...args: any[]) => any): StorageTable<T> {
108
+ this.table.mapToClass(ctor as any);
109
+ return this;
110
+ }
111
+
112
+ async each(callback: (item: T) => void | Promise<void>): Promise<void> {
113
+ const tasks: Array<void | Promise<void>> = [];
114
+ await this.table.each((item) => {
115
+ tasks.push(callback(item));
116
+ });
117
+ await Promise.all(tasks.map((task) => (task ? Promise.resolve(task) : Promise.resolve())));
118
+ }
119
+
120
+ jsFilter(predicate: (item: T) => boolean): StorageCollection<T> {
121
+ return new DexieStorageCollection(this.table.filter(predicate));
122
+ }
123
+ }
@@ -0,0 +1,103 @@
1
+ import type { WhereClause as DexieWhereClause } from 'dexie';
2
+ import type { StorageCollection, StorageWhereClause } from '../types';
3
+ import { DexieStorageCollection } from './DexieStorageCollection';
4
+
5
+ export class DexieStorageWhereClause<T = any> implements StorageWhereClause<T> {
6
+ private readonly clause: DexieWhereClause<T, any, T>;
7
+
8
+ constructor(clause: DexieWhereClause<T, any, T>) {
9
+ this.clause = clause;
10
+ }
11
+
12
+ equals(value: any): StorageCollection<T> {
13
+ return new DexieStorageCollection(this.clause.equals(value));
14
+ }
15
+
16
+ above(value: any): StorageCollection<T> {
17
+ return new DexieStorageCollection(this.clause.above(value));
18
+ }
19
+
20
+ aboveOrEqual(value: any): StorageCollection<T> {
21
+ return new DexieStorageCollection(this.clause.aboveOrEqual(value));
22
+ }
23
+
24
+ below(value: any): StorageCollection<T> {
25
+ return new DexieStorageCollection(this.clause.below(value));
26
+ }
27
+
28
+ belowOrEqual(value: any): StorageCollection<T> {
29
+ return new DexieStorageCollection(this.clause.belowOrEqual(value));
30
+ }
31
+
32
+ between(lower: any, upper: any, includeLower?: boolean, includeUpper?: boolean): StorageCollection<T> {
33
+ return new DexieStorageCollection(this.clause.between(lower, upper, includeLower, includeUpper));
34
+ }
35
+
36
+ inAnyRange(ranges: Array<[any, any]>, options?: { includeLower?: boolean; includeUpper?: boolean }): StorageCollection<T> {
37
+ const normalizedOptions = options
38
+ ? {
39
+ includeLowers: options.includeLower,
40
+ includeUppers: options.includeUpper,
41
+ }
42
+ : undefined;
43
+ return new DexieStorageCollection(this.clause.inAnyRange(ranges as any, normalizedOptions));
44
+ }
45
+
46
+ startsWith(prefix: string): StorageCollection<T> {
47
+ return new DexieStorageCollection(this.clause.startsWith(prefix));
48
+ }
49
+
50
+ startsWithIgnoreCase(prefix: string): StorageCollection<T> {
51
+ return new DexieStorageCollection(this.clause.startsWithIgnoreCase(prefix));
52
+ }
53
+
54
+ startsWithAnyOf(...prefixes: string[]): StorageCollection<T>;
55
+ startsWithAnyOf(prefixes: string[]): StorageCollection<T>;
56
+ startsWithAnyOf(...args: any[]): StorageCollection<T> {
57
+ const values = this.flattenArgs<string>(args);
58
+ return new DexieStorageCollection(this.clause.startsWithAnyOf(...values));
59
+ }
60
+
61
+ startsWithAnyOfIgnoreCase(...prefixes: string[]): StorageCollection<T>;
62
+ startsWithAnyOfIgnoreCase(prefixes: string[]): StorageCollection<T>;
63
+ startsWithAnyOfIgnoreCase(...args: any[]): StorageCollection<T> {
64
+ const values = this.flattenArgs<string>(args);
65
+ return new DexieStorageCollection(this.clause.startsWithAnyOfIgnoreCase(...values));
66
+ }
67
+
68
+ equalsIgnoreCase(value: string): StorageCollection<T> {
69
+ return new DexieStorageCollection(this.clause.equalsIgnoreCase(value));
70
+ }
71
+
72
+ anyOf(...values: any[]): StorageCollection<T>;
73
+ anyOf(values: any[]): StorageCollection<T>;
74
+ anyOf(...args: any[]): StorageCollection<T> {
75
+ const values = this.flattenArgs<any>(args);
76
+ return new DexieStorageCollection(this.clause.anyOf(...values));
77
+ }
78
+
79
+ anyOfIgnoreCase(...values: string[]): StorageCollection<T>;
80
+ anyOfIgnoreCase(values: string[]): StorageCollection<T>;
81
+ anyOfIgnoreCase(...args: any[]): StorageCollection<T> {
82
+ const values = this.flattenArgs<string>(args);
83
+ return new DexieStorageCollection(this.clause.anyOfIgnoreCase(...values));
84
+ }
85
+
86
+ noneOf(...values: any[]): StorageCollection<T>;
87
+ noneOf(values: any[]): StorageCollection<T>;
88
+ noneOf(...args: any[]): StorageCollection<T> {
89
+ const values = this.flattenArgs<any>(args);
90
+ return new DexieStorageCollection(this.clause.noneOf(values as any));
91
+ }
92
+
93
+ notEqual(value: any): StorageCollection<T> {
94
+ return new DexieStorageCollection(this.clause.notEqual(value));
95
+ }
96
+
97
+ private flattenArgs<TValue>(args: any[]): TValue[] {
98
+ if (args.length === 1 && Array.isArray(args[0])) {
99
+ return args[0] as TValue[];
100
+ }
101
+ return args as TValue[];
102
+ }
103
+ }
@@ -0,0 +1 @@
1
+ export const normalizeIndexName = (index: string | string[]): string => (Array.isArray(index) ? `[${index.join('+')}]` : index);
@@ -0,0 +1,7 @@
1
+ // Export all classes
2
+ export { DexieAdapter } from './DexieAdapter';
3
+ export { DexieQueryContext } from './DexieQueryContext';
4
+ export { DexieStorageCollection } from './DexieStorageCollection';
5
+ export { DexieStorageWhereClause } from './DexieStorageWhereClause';
6
+ export { DexieStorageTable } from './DexieStorageTable';
7
+ export { normalizeIndexName } from './helpers';
@@ -0,0 +1,55 @@
1
+ import type { StorageAdapter, StorageTable, StorageTransactionContext, TransactionMode } from '../types';
2
+ import type { StorageSchemaDefinitionOptions } from '../sqlite/types';
3
+ import { MemoryQueryContext } from './MemoryQueryContext';
4
+ import { MemoryTable } from './MemoryTable';
5
+
6
+ export class MemoryAdapter implements StorageAdapter {
7
+ readonly type = 'MemoryAdapter';
8
+ readonly name: string;
9
+ readonly tables = new Map<string, MemoryTable<any>>();
10
+
11
+ constructor(name: string) {
12
+ this.name = name;
13
+ }
14
+
15
+ async open(): Promise<void> {
16
+ // No-op for memory adapter - always "open"
17
+ }
18
+
19
+ async close(): Promise<void> {
20
+ this.tables.clear();
21
+ }
22
+
23
+ async delete(): Promise<void> {
24
+ this.tables.clear();
25
+ }
26
+
27
+ async query<R>(callback: (ctx: MemoryQueryContext) => Promise<R>): Promise<R> {
28
+ return callback(new MemoryQueryContext(this));
29
+ }
30
+
31
+ defineSchema(_version: number, schema: Record<string, string>, _options?: StorageSchemaDefinitionOptions): void {
32
+ for (const tableName of Object.keys(schema)) {
33
+ this.ensureTable(tableName);
34
+ }
35
+ }
36
+
37
+ table<T = any>(name: string): StorageTable<T> {
38
+ return this.ensureTable(name) as StorageTable<T>;
39
+ }
40
+
41
+ async transaction<T>(_mode: TransactionMode, tableNames: string[], callback: (context: StorageTransactionContext) => Promise<T>): Promise<T> {
42
+ const tables: Record<string, StorageTable<any>> = {};
43
+ for (const tableName of tableNames) {
44
+ tables[tableName] = this.ensureTable(tableName);
45
+ }
46
+ return callback({ tables });
47
+ }
48
+
49
+ private ensureTable(name: string): MemoryTable<any> {
50
+ if (!this.tables.has(name)) {
51
+ this.tables.set(name, new MemoryTable(name));
52
+ }
53
+ return this.tables.get(name)!;
54
+ }
55
+ }