@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,215 @@
|
|
|
1
|
+
import type { StorageCollection, StorageWhereClause } from '../types';
|
|
2
|
+
import type { MemoryCollectionState, MemoryRecord } from './types';
|
|
3
|
+
import type { MemoryTable } from './MemoryTable';
|
|
4
|
+
|
|
5
|
+
export class MemoryCollection<T extends MemoryRecord = MemoryRecord> implements StorageCollection<T> {
|
|
6
|
+
private readonly table: MemoryTable<T>;
|
|
7
|
+
private readonly state: MemoryCollectionState<T>;
|
|
8
|
+
|
|
9
|
+
constructor(table: MemoryTable<T>, state: MemoryCollectionState<T>) {
|
|
10
|
+
this.table = table;
|
|
11
|
+
this.state = state;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getState(): MemoryCollectionState<T> {
|
|
15
|
+
return { ...this.state };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
matches(record: T, key: string): boolean {
|
|
19
|
+
return this.state.predicate(record, key);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
clone(_props?: Record<string, unknown>): StorageCollection<T> {
|
|
23
|
+
return new MemoryCollection(this.table, { ...this.state });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
reverse(): StorageCollection<T> {
|
|
27
|
+
return this.withState({ reverse: !this.state.reverse });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
offset(offset: number): StorageCollection<T> {
|
|
31
|
+
return this.withState({ offset });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
limit(count: number): StorageCollection<T> {
|
|
35
|
+
return this.withState({ limit: count });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
toCollection(): StorageCollection<T> {
|
|
39
|
+
return this.clone();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
distinct(): StorageCollection<T> {
|
|
43
|
+
return this.withState({ distinct: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
jsFilter(predicate: (item: T) => boolean): StorageCollection<T> {
|
|
47
|
+
return this.withState({ predicate: this.combinePredicate(predicate, 'and') });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
or(index: string): StorageWhereClause<T> {
|
|
51
|
+
return this.table.createWhereClause(index, this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async first(): Promise<T | undefined> {
|
|
55
|
+
const entries = this.materializeEntries(true);
|
|
56
|
+
return entries.at(0)?.[1];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async last(): Promise<T | undefined> {
|
|
60
|
+
const entries = this.materializeEntries(true);
|
|
61
|
+
return entries.at(-1)?.[1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async each(callback: (item: T, index: number) => void | Promise<void>): Promise<void> {
|
|
65
|
+
const entries = this.materializeEntries(true);
|
|
66
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
67
|
+
const [, record] = entries[index]!;
|
|
68
|
+
await callback(record, index);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async eachKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void> {
|
|
73
|
+
const keys = await this.keys();
|
|
74
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
75
|
+
await callback(keys[index], index);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async eachPrimaryKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void> {
|
|
80
|
+
const keys = await this.primaryKeys();
|
|
81
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
82
|
+
await callback(keys[index], index);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async eachUniqueKey(callback: (key: unknown, index: number) => void | Promise<void>): Promise<void> {
|
|
87
|
+
const keys = await this.uniqueKeys();
|
|
88
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
89
|
+
await callback(keys[index], index);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async keys(): Promise<unknown[]> {
|
|
94
|
+
return this.materializeEntries(false).map(([key, record]) => this.table.resolvePublicKey(record, key));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async primaryKeys(): Promise<unknown[]> {
|
|
98
|
+
return this.keys();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async uniqueKeys(): Promise<unknown[]> {
|
|
102
|
+
const seen = new Set<string>();
|
|
103
|
+
const keys: unknown[] = [];
|
|
104
|
+
for (const [key, record] of this.materializeEntries(false)) {
|
|
105
|
+
const publicKey = this.table.resolvePublicKey(record, key);
|
|
106
|
+
const signature = JSON.stringify(publicKey);
|
|
107
|
+
if (!seen.has(signature)) {
|
|
108
|
+
seen.add(signature);
|
|
109
|
+
keys.push(publicKey);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return keys;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async count(): Promise<number> {
|
|
116
|
+
return this.materializeEntries(false).length;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async sortBy(key: string): Promise<T[]> {
|
|
120
|
+
const entries = this.materializeEntries(true);
|
|
121
|
+
entries.sort((a, b) => this.table.compareValues((a[1] as any)[key], (b[1] as any)[key]));
|
|
122
|
+
return entries.map(([, record]) => record);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async delete(): Promise<number> {
|
|
126
|
+
const entries = this.materializeEntries(false);
|
|
127
|
+
for (const [key] of entries) {
|
|
128
|
+
this.table.deleteByKey(key);
|
|
129
|
+
}
|
|
130
|
+
return entries.length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async modify(changes: Partial<T> | ((item: T) => void | Promise<void>)): Promise<number> {
|
|
134
|
+
const entries = this.materializeEntries(false);
|
|
135
|
+
let modified = 0;
|
|
136
|
+
for (const [key] of entries) {
|
|
137
|
+
const current = this.table.getMutableRecord(key);
|
|
138
|
+
if (!current) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (typeof changes === 'function') {
|
|
142
|
+
const clone = this.table.cloneRecord(current);
|
|
143
|
+
await changes(clone);
|
|
144
|
+
(clone as MemoryRecord)._localId = current._localId ?? key;
|
|
145
|
+
this.table.setMutableRecord(key, clone);
|
|
146
|
+
} else {
|
|
147
|
+
const updated = { ...current, ...changes, _localId: current._localId ?? key } as T;
|
|
148
|
+
this.table.setMutableRecord(key, updated);
|
|
149
|
+
}
|
|
150
|
+
modified += 1;
|
|
151
|
+
}
|
|
152
|
+
return modified;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async toArray(): Promise<T[]> {
|
|
156
|
+
return this.materializeEntries(true).map(([, record]) => record);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private withState(overrides: Partial<MemoryCollectionState<T>>): MemoryCollection<T> {
|
|
160
|
+
return new MemoryCollection(this.table, {
|
|
161
|
+
...this.state,
|
|
162
|
+
...overrides,
|
|
163
|
+
predicate: overrides.predicate ?? this.state.predicate,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private combinePredicate(predicate: (record: T) => boolean, mode: 'and' | 'or'): (record: T, key: string) => boolean {
|
|
168
|
+
if (mode === 'and') {
|
|
169
|
+
return (record: T, key: string) => this.state.predicate(record, key) && predicate(record);
|
|
170
|
+
}
|
|
171
|
+
return (record: T, key: string) => this.state.predicate(record, key) || predicate(record);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private materializeEntries(clone: boolean): Array<[string, T]> {
|
|
175
|
+
let entries = this.table.entries().filter(([key, record]) => this.state.predicate(record, key));
|
|
176
|
+
|
|
177
|
+
if (this.state.orderBy) {
|
|
178
|
+
const { index, direction } = this.state.orderBy;
|
|
179
|
+
entries = [...entries].sort((a, b) => this.table.compareEntries(a[1], b[1], index));
|
|
180
|
+
if (direction === 'desc') {
|
|
181
|
+
entries.reverse();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this.state.reverse) {
|
|
186
|
+
entries = [...entries].reverse();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (this.state.distinct) {
|
|
190
|
+
const seen = new Set<string>();
|
|
191
|
+
entries = entries.filter(([, record]) => {
|
|
192
|
+
const signature = JSON.stringify(record);
|
|
193
|
+
if (seen.has(signature)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
seen.add(signature);
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (this.state.offset > 0) {
|
|
202
|
+
entries = entries.slice(this.state.offset);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof this.state.limit === 'number') {
|
|
206
|
+
entries = entries.slice(0, this.state.limit);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (clone) {
|
|
210
|
+
return entries.map(([key, record]) => [key, this.table.cloneRecord(record)] as [string, T]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return entries;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StorageTable, StorageTransactionContext, TransactionMode } from '../types';
|
|
2
|
+
import type { MemoryAdapter } from './MemoryAdapter';
|
|
3
|
+
|
|
4
|
+
export class MemoryQueryContext {
|
|
5
|
+
constructor(private readonly adapter: MemoryAdapter) {}
|
|
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,336 @@
|
|
|
1
|
+
import type { StorageCollection, StorageTable, StorageWhereClause } from '../types';
|
|
2
|
+
import { LOCAL_PK } from '../../types';
|
|
3
|
+
import { createLocalId } from '../../helpers';
|
|
4
|
+
import type { MemoryCollectionState, MemoryRecord } from './types';
|
|
5
|
+
import { createDefaultState } from './types';
|
|
6
|
+
import { MemoryCollection } from './MemoryCollection';
|
|
7
|
+
import { MemoryWhereClause } from './MemoryWhereClause';
|
|
8
|
+
|
|
9
|
+
export class MemoryTable<T extends MemoryRecord = MemoryRecord> implements StorageTable<T> {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly schema: unknown = undefined;
|
|
12
|
+
readonly primaryKey: unknown = LOCAL_PK;
|
|
13
|
+
readonly hook: unknown = Object.freeze({});
|
|
14
|
+
readonly raw: {
|
|
15
|
+
add: (item: T) => Promise<unknown>;
|
|
16
|
+
put: (item: T) => Promise<unknown>;
|
|
17
|
+
update: (key: unknown, changes: Partial<T>) => Promise<number>;
|
|
18
|
+
delete: (key: unknown) => Promise<void>;
|
|
19
|
+
get: (key: unknown) => Promise<T | undefined>;
|
|
20
|
+
bulkAdd: (items: T[]) => Promise<unknown>;
|
|
21
|
+
bulkPut: (items: T[]) => Promise<unknown>;
|
|
22
|
+
bulkUpdate: (keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>) => Promise<number>;
|
|
23
|
+
bulkDelete: (keys: Array<unknown>) => Promise<void>;
|
|
24
|
+
clear: () => Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
private readonly records = new Map<string, T>();
|
|
27
|
+
|
|
28
|
+
constructor(name: string) {
|
|
29
|
+
this.name = name;
|
|
30
|
+
this.raw = {
|
|
31
|
+
add: async (item: T) => this.baseAdd(item),
|
|
32
|
+
put: async (item: T) => this.basePut(item),
|
|
33
|
+
update: async (key: unknown, changes: Partial<T>) => this.baseUpdate(key, changes),
|
|
34
|
+
delete: async (key: unknown) => {
|
|
35
|
+
this.baseDelete(key);
|
|
36
|
+
},
|
|
37
|
+
get: async (key: unknown) => this.baseGet(key),
|
|
38
|
+
bulkAdd: async (items: T[]) => this.baseBulkAdd(items),
|
|
39
|
+
bulkPut: async (items: T[]) => this.baseBulkPut(items),
|
|
40
|
+
bulkUpdate: async (keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>) => this.baseBulkUpdate(keysAndChanges),
|
|
41
|
+
bulkDelete: async (keys: Array<unknown>) => this.baseBulkDelete(keys),
|
|
42
|
+
clear: async () => this.baseClear(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async add(item: T): Promise<unknown> {
|
|
47
|
+
return this.baseAdd(item);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async put(item: T): Promise<unknown> {
|
|
51
|
+
return this.basePut(item);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async update(key: unknown, changes: Partial<T>): Promise<number> {
|
|
55
|
+
return this.baseUpdate(key, changes);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(key: unknown): Promise<void> {
|
|
59
|
+
this.baseDelete(key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async clear(): Promise<void> {
|
|
63
|
+
this.baseClear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private baseClear(): void {
|
|
67
|
+
this.records.clear();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async get(key: unknown): Promise<T | undefined> {
|
|
71
|
+
const stored = this.baseGet(key);
|
|
72
|
+
return stored ? this.cloneRecord(stored) : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async toArray(): Promise<T[]> {
|
|
76
|
+
return this.entries().map(([, record]) => this.cloneRecord(record));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async count(): Promise<number> {
|
|
80
|
+
return this.records.size;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async bulkAdd(items: T[]): Promise<unknown> {
|
|
84
|
+
return this.baseBulkAdd(items);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private baseBulkAdd(items: T[]): unknown {
|
|
88
|
+
let lastKey: unknown = undefined;
|
|
89
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
90
|
+
const item = items[index]!;
|
|
91
|
+
lastKey = this.baseAdd(item);
|
|
92
|
+
}
|
|
93
|
+
return lastKey;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async bulkPut(items: T[]): Promise<unknown> {
|
|
97
|
+
return this.baseBulkPut(items);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private baseBulkPut(items: T[]): unknown {
|
|
101
|
+
let lastKey: unknown = undefined;
|
|
102
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
103
|
+
const item = items[index]!;
|
|
104
|
+
lastKey = this.basePut(item);
|
|
105
|
+
}
|
|
106
|
+
return lastKey;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async bulkGet(keys: Array<unknown>): Promise<Array<T | undefined>> {
|
|
110
|
+
return Promise.all(keys.map((key) => this.get(key)));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async bulkUpdate(keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>): Promise<number> {
|
|
114
|
+
return this.baseBulkUpdate(keysAndChanges);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private baseBulkUpdate(keysAndChanges: Array<{ key: unknown; changes: Partial<T> }>): number {
|
|
118
|
+
let updatedCount = 0;
|
|
119
|
+
for (const { key, changes } of keysAndChanges) {
|
|
120
|
+
const result = this.baseUpdate(key, changes);
|
|
121
|
+
updatedCount += result;
|
|
122
|
+
}
|
|
123
|
+
return updatedCount;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async bulkDelete(keys: Array<unknown>): Promise<void> {
|
|
127
|
+
this.baseBulkDelete(keys);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private baseBulkDelete(keys: Array<unknown>): void {
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
this.baseDelete(key);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
where(index: string | string[]): StorageWhereClause<T> {
|
|
137
|
+
return this.createWhereClause(index);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
orderBy(index: string | string[]): StorageCollection<T> {
|
|
141
|
+
return this.createCollection({
|
|
142
|
+
orderBy: { index, direction: 'asc' },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
reverse(): StorageCollection<T> {
|
|
147
|
+
return this.createCollection({ reverse: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
offset(offset: number): StorageCollection<T> {
|
|
151
|
+
return this.createCollection({ offset });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
limit(count: number): StorageCollection<T> {
|
|
155
|
+
return this.createCollection({ limit: count });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
mapToClass(_ctor: new (...args: any[]) => any): StorageTable<T> {
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async each(callback: (item: T) => void | Promise<void>): Promise<void> {
|
|
163
|
+
for (const [, record] of this.entries()) {
|
|
164
|
+
await callback(this.cloneRecord(record));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
jsFilter(predicate: (item: T) => boolean): StorageCollection<T> {
|
|
169
|
+
return this.createCollection({ predicate: (record) => predicate(record) });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private createCollection(stateOverrides?: Partial<MemoryCollectionState<T>>): MemoryCollection<T> {
|
|
173
|
+
const baseState = createDefaultState<T>();
|
|
174
|
+
const state: MemoryCollectionState<T> = {
|
|
175
|
+
...baseState,
|
|
176
|
+
...stateOverrides,
|
|
177
|
+
predicate: stateOverrides?.predicate ?? baseState.predicate,
|
|
178
|
+
};
|
|
179
|
+
return new MemoryCollection(this, state);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
createCollectionFromPredicate(predicate: (record: T, key: string) => boolean, template?: MemoryCollection<T>): MemoryCollection<T> {
|
|
183
|
+
const baseState = template ? template.getState() : createDefaultState<T>();
|
|
184
|
+
return new MemoryCollection(this, {
|
|
185
|
+
...baseState,
|
|
186
|
+
predicate,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
createWhereClause(index: string | string[], baseCollection?: MemoryCollection<T>): MemoryWhereClause<T> {
|
|
191
|
+
return new MemoryWhereClause(this, index, baseCollection);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
entries(): Array<[string, T]> {
|
|
195
|
+
return Array.from(this.records.entries());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
cloneRecord(record: T): T {
|
|
199
|
+
return { ...(record as object) } as T;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
deleteByKey(key: string): void {
|
|
203
|
+
this.records.delete(key);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getMutableRecord(key: string): T | undefined {
|
|
207
|
+
return this.records.get(key);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
setMutableRecord(key: string, record: T): void {
|
|
211
|
+
this.records.set(key, { ...record, _localId: record._localId ?? key } as T);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
resolvePublicKey(record: T, key: string): unknown {
|
|
215
|
+
if (record._localId !== undefined) {
|
|
216
|
+
return record._localId;
|
|
217
|
+
}
|
|
218
|
+
if (record.id !== undefined) {
|
|
219
|
+
return record.id;
|
|
220
|
+
}
|
|
221
|
+
return key;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
getIndexValue(record: T, index: string | string[]): unknown {
|
|
225
|
+
if (Array.isArray(index)) {
|
|
226
|
+
return index.map((key) => (record as any)[key]);
|
|
227
|
+
}
|
|
228
|
+
return (record as any)[index];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
compareEntries(left: T, right: T, index: string | string[]): number {
|
|
232
|
+
if (Array.isArray(index)) {
|
|
233
|
+
for (const key of index) {
|
|
234
|
+
const diff = this.compareValues((left as any)[key], (right as any)[key]);
|
|
235
|
+
if (diff !== 0) {
|
|
236
|
+
return diff;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
return this.compareValues((left as any)[index], (right as any)[index]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
compareValues(left: unknown, right: unknown): number {
|
|
245
|
+
const normalizedLeft = this.normalizeComparableValue(left) as any;
|
|
246
|
+
const normalizedRight = this.normalizeComparableValue(right) as any;
|
|
247
|
+
if (normalizedLeft < normalizedRight) {
|
|
248
|
+
return -1;
|
|
249
|
+
}
|
|
250
|
+
if (normalizedLeft > normalizedRight) {
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private normalizeComparableValue(value: unknown): unknown {
|
|
257
|
+
if (Array.isArray(value)) {
|
|
258
|
+
return value.map((entry) => this.normalizeComparableValue(entry));
|
|
259
|
+
}
|
|
260
|
+
if (value instanceof Date) {
|
|
261
|
+
return value.valueOf();
|
|
262
|
+
}
|
|
263
|
+
return value ?? null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private baseAdd(item: T): string {
|
|
267
|
+
const primaryKey = this.createPrimaryKey(item);
|
|
268
|
+
const stored = { ...item, _localId: primaryKey } as T;
|
|
269
|
+
this.records.set(primaryKey, stored);
|
|
270
|
+
return primaryKey;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private basePut(item: T): string {
|
|
274
|
+
const primaryKey = this.createPrimaryKey(item);
|
|
275
|
+
const stored = { ...item, _localId: primaryKey } as T;
|
|
276
|
+
this.records.set(primaryKey, stored);
|
|
277
|
+
return primaryKey;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private baseUpdate(key: unknown, changes: Partial<T>): number {
|
|
281
|
+
const primaryKey = this.resolveKey(key);
|
|
282
|
+
if (!primaryKey) {
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
const existing = this.records.get(primaryKey);
|
|
286
|
+
if (!existing) {
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
const updated = { ...existing, ...changes, _localId: existing._localId ?? primaryKey } as T;
|
|
290
|
+
this.records.set(primaryKey, updated);
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private baseDelete(key: unknown): void {
|
|
295
|
+
const primaryKey = this.resolveKey(key);
|
|
296
|
+
if (primaryKey) {
|
|
297
|
+
this.records.delete(primaryKey);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private baseGet(key: unknown): T | undefined {
|
|
302
|
+
const primaryKey = this.resolveKey(key);
|
|
303
|
+
if (!primaryKey) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
return this.records.get(primaryKey);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private createPrimaryKey(item: T): string {
|
|
310
|
+
if (item._localId && typeof item._localId === 'string') {
|
|
311
|
+
return item._localId;
|
|
312
|
+
}
|
|
313
|
+
if (item.id !== undefined && (typeof item.id === 'string' || typeof item.id === 'number' || typeof item.id === 'bigint')) {
|
|
314
|
+
return String(item.id);
|
|
315
|
+
}
|
|
316
|
+
return createLocalId();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private resolveKey(key: unknown): string | undefined {
|
|
320
|
+
if (typeof key === 'string') {
|
|
321
|
+
return key;
|
|
322
|
+
}
|
|
323
|
+
if (typeof key === 'number' || typeof key === 'bigint') {
|
|
324
|
+
return String(key);
|
|
325
|
+
}
|
|
326
|
+
if (key && typeof key === 'object' && 'id' in (key as Record<string, unknown>)) {
|
|
327
|
+
const lookup = (key as Record<string, unknown>).id;
|
|
328
|
+
for (const [storedKey, record] of this.records.entries()) {
|
|
329
|
+
if (record.id === lookup) {
|
|
330
|
+
return storedKey;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { StorageCollection, StorageWhereClause } from '../types';
|
|
2
|
+
import type { MemoryRecord } from './types';
|
|
3
|
+
import type { MemoryTable } from './MemoryTable';
|
|
4
|
+
import type { MemoryCollection } from './MemoryCollection';
|
|
5
|
+
|
|
6
|
+
export class MemoryWhereClause<T extends MemoryRecord = MemoryRecord> implements StorageWhereClause<T> {
|
|
7
|
+
private readonly table: MemoryTable<T>;
|
|
8
|
+
private readonly index: string | string[];
|
|
9
|
+
private readonly baseCollection?: MemoryCollection<T>;
|
|
10
|
+
|
|
11
|
+
constructor(table: MemoryTable<T>, index: string | string[], baseCollection?: MemoryCollection<T>) {
|
|
12
|
+
this.table = table;
|
|
13
|
+
this.index = index;
|
|
14
|
+
this.baseCollection = baseCollection;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
equals(value: any): StorageCollection<T> {
|
|
18
|
+
return this.createCollection((current) => this.table.compareValues(current, value) === 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
above(value: any): StorageCollection<T> {
|
|
22
|
+
return this.createCollection((current) => this.table.compareValues(current, value) > 0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
aboveOrEqual(value: any): StorageCollection<T> {
|
|
26
|
+
return this.createCollection((current) => this.table.compareValues(current, value) >= 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
below(value: any): StorageCollection<T> {
|
|
30
|
+
return this.createCollection((current) => this.table.compareValues(current, value) < 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
belowOrEqual(value: any): StorageCollection<T> {
|
|
34
|
+
return this.createCollection((current) => this.table.compareValues(current, value) <= 0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
between(lower: any, upper: any, includeLower = true, includeUpper = false): StorageCollection<T> {
|
|
38
|
+
return this.createCollection((current) => {
|
|
39
|
+
const lowerCmp = this.table.compareValues(current, lower);
|
|
40
|
+
const upperCmp = this.table.compareValues(current, upper);
|
|
41
|
+
const lowerOk = includeLower ? lowerCmp >= 0 : lowerCmp > 0;
|
|
42
|
+
const upperOk = includeUpper ? upperCmp <= 0 : upperCmp < 0;
|
|
43
|
+
return lowerOk && upperOk;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
inAnyRange(ranges: Array<[any, any]>, options?: { includeLower?: boolean; includeUpper?: boolean }): StorageCollection<T> {
|
|
48
|
+
const includeLower = options?.includeLower ?? true;
|
|
49
|
+
const includeUpper = options?.includeUpper ?? false;
|
|
50
|
+
return this.createCollection((current) => {
|
|
51
|
+
for (const [lower, upper] of ranges) {
|
|
52
|
+
const lowerCmp = this.table.compareValues(current, lower);
|
|
53
|
+
const upperCmp = this.table.compareValues(current, upper);
|
|
54
|
+
const lowerOk = includeLower ? lowerCmp >= 0 : lowerCmp > 0;
|
|
55
|
+
const upperOk = includeUpper ? upperCmp <= 0 : upperCmp < 0;
|
|
56
|
+
if (lowerOk && upperOk) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
startsWith(prefix: string): StorageCollection<T> {
|
|
65
|
+
return this.createCollection((current) => typeof current === 'string' && current.startsWith(prefix));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
startsWithIgnoreCase(prefix: string): StorageCollection<T> {
|
|
69
|
+
return this.createCollection((current) => typeof current === 'string' && current.toLowerCase().startsWith(prefix.toLowerCase()));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
startsWithAnyOf(...prefixes: string[]): StorageCollection<T>;
|
|
73
|
+
startsWithAnyOf(prefixes: string[]): StorageCollection<T>;
|
|
74
|
+
startsWithAnyOf(...args: any[]): StorageCollection<T> {
|
|
75
|
+
const prefixes = this.flattenArgs<string>(args);
|
|
76
|
+
return this.createCollection((current) => typeof current === 'string' && prefixes.some((prefix) => current.startsWith(prefix)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
startsWithAnyOfIgnoreCase(...prefixes: string[]): StorageCollection<T>;
|
|
80
|
+
startsWithAnyOfIgnoreCase(prefixes: string[]): StorageCollection<T>;
|
|
81
|
+
startsWithAnyOfIgnoreCase(...args: any[]): StorageCollection<T> {
|
|
82
|
+
const prefixes = this.flattenArgs<string>(args).map((prefix) => prefix.toLowerCase());
|
|
83
|
+
return this.createCollection((current) => typeof current === 'string' && prefixes.some((prefix) => current.toLowerCase().startsWith(prefix)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
equalsIgnoreCase(value: string): StorageCollection<T> {
|
|
87
|
+
return this.createCollection((current) => typeof current === 'string' && current.toLowerCase() === value.toLowerCase());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
anyOf(...values: any[]): StorageCollection<T>;
|
|
91
|
+
anyOf(values: any[]): StorageCollection<T>;
|
|
92
|
+
anyOf(...args: any[]): StorageCollection<T> {
|
|
93
|
+
const values = this.flattenArgs<any>(args);
|
|
94
|
+
const valueSet = new Set(values.map((entry) => JSON.stringify(entry)));
|
|
95
|
+
return this.createCollection((current) => valueSet.has(JSON.stringify(current)));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
anyOfIgnoreCase(...values: string[]): StorageCollection<T>;
|
|
99
|
+
anyOfIgnoreCase(values: string[]): StorageCollection<T>;
|
|
100
|
+
anyOfIgnoreCase(...args: any[]): StorageCollection<T> {
|
|
101
|
+
const values = this.flattenArgs<string>(args).map((value) => value.toLowerCase());
|
|
102
|
+
const valueSet = new Set(values);
|
|
103
|
+
return this.createCollection((current) => typeof current === 'string' && valueSet.has(current.toLowerCase()));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
noneOf(...values: any[]): StorageCollection<T>;
|
|
107
|
+
noneOf(values: any[]): StorageCollection<T>;
|
|
108
|
+
noneOf(...args: any[]): StorageCollection<T> {
|
|
109
|
+
const values = this.flattenArgs<any>(args);
|
|
110
|
+
const valueSet = new Set(values.map((entry) => JSON.stringify(entry)));
|
|
111
|
+
return this.createCollection((current) => !valueSet.has(JSON.stringify(current)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
notEqual(value: any): StorageCollection<T> {
|
|
115
|
+
return this.createCollection((current) => this.table.compareValues(current, value) !== 0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private createCollection(predicate: (indexValue: unknown) => boolean): StorageCollection<T> {
|
|
119
|
+
const condition = (record: T, _key: string): boolean => predicate(this.table.getIndexValue(record, this.index));
|
|
120
|
+
if (this.baseCollection) {
|
|
121
|
+
const combined = (record: T, key: string): boolean =>
|
|
122
|
+
this.baseCollection!.matches(record, key) || predicate(this.table.getIndexValue(record, this.index));
|
|
123
|
+
return this.table.createCollectionFromPredicate(combined, this.baseCollection);
|
|
124
|
+
}
|
|
125
|
+
return this.table.createCollectionFromPredicate(condition);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private flattenArgs<TValue>(args: any[]): TValue[] {
|
|
129
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
130
|
+
return args[0] as TValue[];
|
|
131
|
+
}
|
|
132
|
+
return args as TValue[];
|
|
133
|
+
}
|
|
134
|
+
}
|