@laboverwire/stitch 0.4.3 → 0.5.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/CHANGELOG.md +40 -0
- package/README.md +8 -5
- package/dist/index.d.ts +1 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +1 -1
- package/dist/react/provider.d.ts +2 -2
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/store.d.ts +2 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +190 -724
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +23 -161
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -6
- package/dist/types.js.map +1 -1
- package/package.json +16 -7
- package/dist/internal-utils.d.ts +0 -4
- package/dist/internal-utils.d.ts.map +0 -1
- package/dist/internal-utils.js +0 -20
- package/dist/internal-utils.js.map +0 -1
- package/dist/internal-wasm-error.d.ts +0 -9
- package/dist/internal-wasm-error.d.ts.map +0 -1
- package/dist/internal-wasm-error.js +0 -48
- package/dist/internal-wasm-error.js.map +0 -1
- package/dist/memory-store.d.ts +0 -3
- package/dist/memory-store.d.ts.map +0 -1
- package/dist/memory-store.js +0 -572
- package/dist/memory-store.js.map +0 -1
- package/dist/offline-queue.d.ts +0 -6
- package/dist/offline-queue.d.ts.map +0 -1
- package/dist/offline-queue.js +0 -363
- package/dist/offline-queue.js.map +0 -1
- package/dist/persistence-layer.d.ts +0 -7
- package/dist/persistence-layer.d.ts.map +0 -1
- package/dist/persistence-layer.js +0 -409
- package/dist/persistence-layer.js.map +0 -1
- package/dist/remote-sync-layer.d.ts +0 -8
- package/dist/remote-sync-layer.d.ts.map +0 -1
- package/dist/remote-sync-layer.js +0 -439
- package/dist/remote-sync-layer.js.map +0 -1
- package/dist/sync-engine.d.ts +0 -3
- package/dist/sync-engine.d.ts.map +0 -1
- package/dist/sync-engine.js +0 -643
- package/dist/sync-engine.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"internal-utils.js","sourceRoot":"","sources":["../src/internal-utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,GAA4B;IACrD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,OAAO,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAY;IACnD,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,OAAO,oHAAoH,CAAC,IAAI,CAC9H,GAAG,CAAC,OAAO,CACZ,CAAC;AACJ,CAAC","sourcesContent":["export function stripNulls(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (value != null) {\n result[key] = value;\n }\n }\n return result;\n}\n\nexport function isTransientSyncError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n return /timeout|disconnected/i.test(err.message);\n}\n\nexport function isPermanentMutationError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n return /unique constraint violation|foreign key violation|not null violation|cascade blocked|referenced by other entities/i.test(\n err.message\n );\n}\n"]}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare class MqdbError extends Error {
|
|
2
|
-
readonly method: string;
|
|
3
|
-
constructor(method: string, cause: unknown);
|
|
4
|
-
}
|
|
5
|
-
export declare function wrapWasmError(method: string, cause: unknown): MqdbError;
|
|
6
|
-
export declare function wrapSync<T>(method: string, fn: () => T): T;
|
|
7
|
-
export declare function wrapAsync<T>(method: string, fn: () => Promise<T>): Promise<T>;
|
|
8
|
-
export declare function isCorruptionError(err: unknown, extraPatterns?: readonly string[]): boolean;
|
|
9
|
-
//# sourceMappingURL=internal-wasm-error.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"internal-wasm-error.d.ts","sourceRoot":"","sources":["../src/internal-wasm-error.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAM3C;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAGvE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAM1D;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAMnF;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,GAAE,SAAS,MAAM,EAAO,GAAG,OAAO,CAY9F"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export class MqdbError extends Error {
|
|
2
|
-
method;
|
|
3
|
-
constructor(method, cause) {
|
|
4
|
-
const rawMessage = cause instanceof Error ? cause.message : String(cause);
|
|
5
|
-
super(`mqdb.${method}: ${rawMessage}`, { cause });
|
|
6
|
-
this.name = 'MqdbError';
|
|
7
|
-
this.method = method;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export function wrapWasmError(method, cause) {
|
|
11
|
-
if (cause instanceof MqdbError)
|
|
12
|
-
return cause;
|
|
13
|
-
return new MqdbError(method, cause);
|
|
14
|
-
}
|
|
15
|
-
export function wrapSync(method, fn) {
|
|
16
|
-
try {
|
|
17
|
-
return fn();
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
throw wrapWasmError(method, err);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export async function wrapAsync(method, fn) {
|
|
24
|
-
try {
|
|
25
|
-
return await fn();
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
throw wrapWasmError(method, err);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
const BASE_CORRUPTION_PATTERNS = ['arg0 is null', 'transaction error', 'unreachable'];
|
|
32
|
-
export function isCorruptionError(err, extraPatterns = []) {
|
|
33
|
-
const inner = err instanceof MqdbError ? err.cause : err;
|
|
34
|
-
if (inner instanceof Error && inner.name === 'RuntimeError')
|
|
35
|
-
return true;
|
|
36
|
-
const msg = (inner instanceof Error ? inner.message : String(inner)).toLowerCase();
|
|
37
|
-
for (const pattern of BASE_CORRUPTION_PATTERNS) {
|
|
38
|
-
if (msg.includes(pattern))
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
for (const pattern of extraPatterns) {
|
|
42
|
-
if (msg.includes(pattern))
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
const txIdx = msg.indexOf('transaction');
|
|
46
|
-
return txIdx !== -1 && msg.indexOf('null', txIdx) !== -1;
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=internal-wasm-error.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"internal-wasm-error.js","sourceRoot":"","sources":["../src/internal-wasm-error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,KAAc;QACxC,MAAM,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1E,KAAK,CAAC,QAAQ,MAAM,KAAK,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,KAAc;IAC1D,IAAI,KAAK,YAAY,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,MAAc,EAAE,EAAW;IACrD,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,MAAc,EAAE,EAAoB;IACrE,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,wBAAwB,GAAG,CAAC,cAAc,EAAE,mBAAmB,EAAE,aAAa,CAAU,CAAC;AAE/F,MAAM,UAAU,iBAAiB,CAAC,GAAY,EAAE,gBAAmC,EAAE;IACnF,MAAM,KAAK,GAAG,GAAG,YAAY,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IACzD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IACzE,MAAM,GAAG,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACnF,KAAK,MAAM,OAAO,IAAI,wBAAwB,EAAE,CAAC;QAC/C,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;IACzC,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;IACzC,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACzC,OAAO,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["export class MqdbError extends Error {\n readonly method: string;\n\n constructor(method: string, cause: unknown) {\n const rawMessage = cause instanceof Error ? cause.message : String(cause);\n super(`mqdb.${method}: ${rawMessage}`, { cause });\n this.name = 'MqdbError';\n this.method = method;\n }\n}\n\nexport function wrapWasmError(method: string, cause: unknown): MqdbError {\n if (cause instanceof MqdbError) return cause;\n return new MqdbError(method, cause);\n}\n\nexport function wrapSync<T>(method: string, fn: () => T): T {\n try {\n return fn();\n } catch (err) {\n throw wrapWasmError(method, err);\n }\n}\n\nexport async function wrapAsync<T>(method: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n throw wrapWasmError(method, err);\n }\n}\n\nconst BASE_CORRUPTION_PATTERNS = ['arg0 is null', 'transaction error', 'unreachable'] as const;\n\nexport function isCorruptionError(err: unknown, extraPatterns: readonly string[] = []): boolean {\n const inner = err instanceof MqdbError ? err.cause : err;\n if (inner instanceof Error && inner.name === 'RuntimeError') return true;\n const msg = (inner instanceof Error ? inner.message : String(inner)).toLowerCase();\n for (const pattern of BASE_CORRUPTION_PATTERNS) {\n if (msg.includes(pattern)) return true;\n }\n for (const pattern of extraPatterns) {\n if (msg.includes(pattern)) return true;\n }\n const txIdx = msg.indexOf('transaction');\n return txIdx !== -1 && msg.indexOf('null', txIdx) !== -1;\n}\n"]}
|
package/dist/memory-store.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../src/memory-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAiB,WAAW,EAAa,MAAM,YAAY,CAAC;AAumBrF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAElE"}
|
package/dist/memory-store.js
DELETED
|
@@ -1,572 +0,0 @@
|
|
|
1
|
-
import { wrapWasmError, isCorruptionError } from "./internal-wasm-error.js";
|
|
2
|
-
function stripNulls(obj) {
|
|
3
|
-
const result = {};
|
|
4
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
5
|
-
if (value != null) {
|
|
6
|
-
result[key] = value;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
return result;
|
|
10
|
-
}
|
|
11
|
-
function topologicalDeleteOrder(entities, config) {
|
|
12
|
-
const entitySet = new Set(entities);
|
|
13
|
-
const refCounts = new Map();
|
|
14
|
-
for (const e of entities)
|
|
15
|
-
refCounts.set(e, 0);
|
|
16
|
-
for (const e of entities) {
|
|
17
|
-
const def = config.entities[e];
|
|
18
|
-
if (!def?.foreignKeys)
|
|
19
|
-
continue;
|
|
20
|
-
for (const fk of def.foreignKeys) {
|
|
21
|
-
if (entitySet.has(fk.references)) {
|
|
22
|
-
refCounts.set(e, (refCounts.get(e) ?? 0) + 1);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return [...entities].sort((a, b) => (refCounts.get(b) ?? 0) - (refCounts.get(a) ?? 0));
|
|
27
|
-
}
|
|
28
|
-
class MemoryStoreImpl {
|
|
29
|
-
_db = null;
|
|
30
|
-
_dbReady = false;
|
|
31
|
-
_initPromise = null;
|
|
32
|
-
_wasmReady = false;
|
|
33
|
-
_corrupted = false;
|
|
34
|
-
_initWasm = null;
|
|
35
|
-
_Database = null;
|
|
36
|
-
config;
|
|
37
|
-
allEntities;
|
|
38
|
-
versions = new Map();
|
|
39
|
-
listSnapshots = new Map();
|
|
40
|
-
mapSnapshots = new Map();
|
|
41
|
-
subscribers = new Map();
|
|
42
|
-
globalSubscribers = new Map();
|
|
43
|
-
mutationListeners = new Set();
|
|
44
|
-
batchDepth = 0;
|
|
45
|
-
batchedKeys = new Set();
|
|
46
|
-
batchedMutations = [];
|
|
47
|
-
originTag = null;
|
|
48
|
-
wasmSubIds = [];
|
|
49
|
-
lastOriginTag = null;
|
|
50
|
-
pendingDeleteContext = null;
|
|
51
|
-
pendingInitCallbacks = [];
|
|
52
|
-
corruptionCallbacks = new Set();
|
|
53
|
-
constructor(config) {
|
|
54
|
-
this.config = config;
|
|
55
|
-
this.allEntities = [
|
|
56
|
-
config.scope.rootEntity,
|
|
57
|
-
...config.scope.childEntities,
|
|
58
|
-
...(config.topLevelEntities?.map((t) => t.entity) ?? []),
|
|
59
|
-
];
|
|
60
|
-
}
|
|
61
|
-
ensureReady() {
|
|
62
|
-
if (this._dbReady)
|
|
63
|
-
return Promise.resolve();
|
|
64
|
-
if (!this._initPromise) {
|
|
65
|
-
this._initPromise = this.doInit();
|
|
66
|
-
}
|
|
67
|
-
return this._initPromise;
|
|
68
|
-
}
|
|
69
|
-
async doInit() {
|
|
70
|
-
try {
|
|
71
|
-
if (!this._initWasm) {
|
|
72
|
-
const mod = await import('mqdb-wasm');
|
|
73
|
-
this._initWasm = mod.default;
|
|
74
|
-
this._Database = mod.Database;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
await this._initWasm();
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
throw wrapWasmError('init', err);
|
|
81
|
-
}
|
|
82
|
-
this._wasmReady = true;
|
|
83
|
-
this.initDb();
|
|
84
|
-
this.notifyAllGlobalSubscribers();
|
|
85
|
-
const cbs = this.pendingInitCallbacks;
|
|
86
|
-
this.pendingInitCallbacks = [];
|
|
87
|
-
for (const cb of cbs)
|
|
88
|
-
cb();
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
this._initPromise = null;
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
initDb() {
|
|
96
|
-
if (this._dbReady)
|
|
97
|
-
return;
|
|
98
|
-
if (!this._wasmReady || !this._Database)
|
|
99
|
-
return;
|
|
100
|
-
this._db = new this._Database();
|
|
101
|
-
this._dbReady = true;
|
|
102
|
-
this.registerSchemas();
|
|
103
|
-
this.setupSubscriptions();
|
|
104
|
-
}
|
|
105
|
-
get isReady() {
|
|
106
|
-
return this._dbReady;
|
|
107
|
-
}
|
|
108
|
-
get corrupted() {
|
|
109
|
-
return this._corrupted;
|
|
110
|
-
}
|
|
111
|
-
onCorruption(callback) {
|
|
112
|
-
this.corruptionCallbacks.add(callback);
|
|
113
|
-
return () => {
|
|
114
|
-
this.corruptionCallbacks.delete(callback);
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
get db() {
|
|
118
|
-
if (this._corrupted) {
|
|
119
|
-
if (!this.tryRecover()) {
|
|
120
|
-
throw new Error('WASM database is corrupted');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!this._dbReady) {
|
|
124
|
-
this.ensureReady();
|
|
125
|
-
throw new Error('WASM not initialized yet');
|
|
126
|
-
}
|
|
127
|
-
return this._db;
|
|
128
|
-
}
|
|
129
|
-
tryRecover() {
|
|
130
|
-
if (!this._wasmReady || !this._Database)
|
|
131
|
-
return false;
|
|
132
|
-
try {
|
|
133
|
-
const testDb = new this._Database();
|
|
134
|
-
testDb.listSync('__test__', {});
|
|
135
|
-
testDb.free();
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
this._db = new this._Database();
|
|
142
|
-
this._corrupted = false;
|
|
143
|
-
this._dbReady = true;
|
|
144
|
-
this.registerSchemas();
|
|
145
|
-
this.setupSubscriptions();
|
|
146
|
-
this.clearAllCaches();
|
|
147
|
-
console.warn('[MemoryStore] WASM recovered — reinitialized in-memory database');
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
isWasmCorrupted(err) {
|
|
155
|
-
return isCorruptionError(err);
|
|
156
|
-
}
|
|
157
|
-
notifyCorruption() {
|
|
158
|
-
for (const cb of this.corruptionCallbacks) {
|
|
159
|
-
try {
|
|
160
|
-
cb();
|
|
161
|
-
}
|
|
162
|
-
catch { }
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
clearAllCaches() {
|
|
166
|
-
this.listSnapshots.clear();
|
|
167
|
-
this.mapSnapshots.clear();
|
|
168
|
-
this.versions.clear();
|
|
169
|
-
}
|
|
170
|
-
notifyAllGlobalSubscribers() {
|
|
171
|
-
for (const entity of this.allEntities) {
|
|
172
|
-
const subs = this.globalSubscribers.get(entity);
|
|
173
|
-
if (subs) {
|
|
174
|
-
for (const cb of subs)
|
|
175
|
-
cb();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
for (const [, scopeMap] of this.subscribers) {
|
|
179
|
-
for (const [, entitySubs] of scopeMap) {
|
|
180
|
-
for (const cb of entitySubs)
|
|
181
|
-
cb();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
onReady(callback) {
|
|
186
|
-
if (this._dbReady) {
|
|
187
|
-
callback();
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
this.pendingInitCallbacks.push(callback);
|
|
191
|
-
this.ensureReady();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
registerSchemasOn(db) {
|
|
195
|
-
const { entities } = this.config;
|
|
196
|
-
for (const [entity, definition] of Object.entries(entities)) {
|
|
197
|
-
db.addSchema(entity, definition);
|
|
198
|
-
if (definition.indexes) {
|
|
199
|
-
for (const field of definition.indexes) {
|
|
200
|
-
db.addIndex(entity, [field]);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
registerSchemas() {
|
|
206
|
-
this.registerSchemasOn(this._db);
|
|
207
|
-
}
|
|
208
|
-
unsubscribeAll() {
|
|
209
|
-
if (this._db) {
|
|
210
|
-
for (const subId of this.wasmSubIds) {
|
|
211
|
-
try {
|
|
212
|
-
this._db.unsubscribe(subId);
|
|
213
|
-
}
|
|
214
|
-
catch { }
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
this.wasmSubIds = [];
|
|
218
|
-
}
|
|
219
|
-
setupSubscriptions() {
|
|
220
|
-
this.unsubscribeAll();
|
|
221
|
-
for (const entity of this.allEntities) {
|
|
222
|
-
const subId = this.db.subscribe('#', entity, (event) => {
|
|
223
|
-
try {
|
|
224
|
-
this.handleChangeEvent(entity, event);
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
console.error(`[MemoryStore] subscription callback error for ${entity}:`, err);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
this.wasmSubIds.push(subId);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
handleChangeEvent(entity, event) {
|
|
234
|
-
this.lastOriginTag = this.originTag;
|
|
235
|
-
const { rootEntity, scopeField } = this.config.scope;
|
|
236
|
-
let scopeId;
|
|
237
|
-
if (event.operation === 'delete') {
|
|
238
|
-
scopeId =
|
|
239
|
-
this.pendingDeleteContext?.scopeId ?? (entity === rootEntity ? event.id : undefined);
|
|
240
|
-
if (!scopeId)
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
else if (entity === rootEntity) {
|
|
244
|
-
scopeId = event.id;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
scopeId = event.data?.[scopeField];
|
|
248
|
-
if (!scopeId)
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
this.bumpVersion(scopeId, entity);
|
|
252
|
-
const mutation = {
|
|
253
|
-
operation: event.operation,
|
|
254
|
-
entity,
|
|
255
|
-
id: event.id,
|
|
256
|
-
scopeId,
|
|
257
|
-
data: event.data,
|
|
258
|
-
originTag: this.originTag,
|
|
259
|
-
};
|
|
260
|
-
if (this.batchDepth > 0) {
|
|
261
|
-
this.batchedKeys.add(`${scopeId}\0${entity}`);
|
|
262
|
-
this.batchedMutations.push(mutation);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
this.notifySubscribers(scopeId, entity);
|
|
266
|
-
this.emitMutation(mutation);
|
|
267
|
-
}
|
|
268
|
-
bumpVersion(scopeId, entity) {
|
|
269
|
-
if (!this.versions.has(scopeId)) {
|
|
270
|
-
this.versions.set(scopeId, new Map());
|
|
271
|
-
}
|
|
272
|
-
const entityVersions = this.versions.get(scopeId);
|
|
273
|
-
entityVersions.set(entity, (entityVersions.get(entity) ?? 0) + 1);
|
|
274
|
-
}
|
|
275
|
-
getVersion(scopeId, entity) {
|
|
276
|
-
return this.versions.get(scopeId)?.get(entity) ?? 0;
|
|
277
|
-
}
|
|
278
|
-
notifySubscribers(scopeId, entity) {
|
|
279
|
-
const scopeSubs = this.subscribers.get(scopeId)?.get(entity);
|
|
280
|
-
if (scopeSubs) {
|
|
281
|
-
for (const cb of scopeSubs)
|
|
282
|
-
cb();
|
|
283
|
-
}
|
|
284
|
-
const globalSubs = this.globalSubscribers.get(entity);
|
|
285
|
-
if (globalSubs) {
|
|
286
|
-
for (const cb of globalSubs)
|
|
287
|
-
cb();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
emitMutation(event) {
|
|
291
|
-
for (const listener of this.mutationListeners) {
|
|
292
|
-
try {
|
|
293
|
-
listener(event);
|
|
294
|
-
}
|
|
295
|
-
catch (err) {
|
|
296
|
-
console.error('[MemoryStore] mutation listener error:', err);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
onMutation(listener) {
|
|
301
|
-
this.mutationListeners.add(listener);
|
|
302
|
-
return () => {
|
|
303
|
-
this.mutationListeners.delete(listener);
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
getLastOriginTag() {
|
|
307
|
-
return this.lastOriginTag;
|
|
308
|
-
}
|
|
309
|
-
subscribeToScope(scopeId, entity, callback) {
|
|
310
|
-
if (!this.subscribers.has(scopeId)) {
|
|
311
|
-
this.subscribers.set(scopeId, new Map());
|
|
312
|
-
}
|
|
313
|
-
const scopeMap = this.subscribers.get(scopeId);
|
|
314
|
-
if (!scopeMap.has(entity)) {
|
|
315
|
-
scopeMap.set(entity, new Set());
|
|
316
|
-
}
|
|
317
|
-
scopeMap.get(entity).add(callback);
|
|
318
|
-
return () => {
|
|
319
|
-
scopeMap.get(entity)?.delete(callback);
|
|
320
|
-
if (scopeMap.get(entity)?.size === 0) {
|
|
321
|
-
scopeMap.delete(entity);
|
|
322
|
-
}
|
|
323
|
-
if (scopeMap.size === 0) {
|
|
324
|
-
this.subscribers.delete(scopeId);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
subscribeToEntity(entity, callback) {
|
|
329
|
-
if (!this.globalSubscribers.has(entity)) {
|
|
330
|
-
this.globalSubscribers.set(entity, new Set());
|
|
331
|
-
}
|
|
332
|
-
this.globalSubscribers.get(entity).add(callback);
|
|
333
|
-
return () => {
|
|
334
|
-
this.globalSubscribers.get(entity)?.delete(callback);
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
listRecords(entity, scopeId) {
|
|
338
|
-
if (!this._dbReady || this._corrupted)
|
|
339
|
-
return [];
|
|
340
|
-
const { rootEntity, scopeField } = this.config.scope;
|
|
341
|
-
const filterField = entity === rootEntity ? 'id' : scopeField;
|
|
342
|
-
try {
|
|
343
|
-
return this._db.listSync(entity, {
|
|
344
|
-
filters: [{ field: filterField, op: 'eq', value: scopeId }],
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
catch (err) {
|
|
348
|
-
const wrapped = wrapWasmError(`listSync:${entity}`, err);
|
|
349
|
-
if (this.isWasmCorrupted(err)) {
|
|
350
|
-
console.error('[MemoryStore] WASM corruption:', wrapped);
|
|
351
|
-
this._corrupted = true;
|
|
352
|
-
this.notifyCorruption();
|
|
353
|
-
if (this.tryRecover()) {
|
|
354
|
-
return [];
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
console.error(wrapped);
|
|
359
|
-
}
|
|
360
|
-
return [];
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
getSnapshot(entity, scopeId) {
|
|
364
|
-
const version = this.getVersion(scopeId, entity);
|
|
365
|
-
if (!this.listSnapshots.has(scopeId)) {
|
|
366
|
-
this.listSnapshots.set(scopeId, new Map());
|
|
367
|
-
}
|
|
368
|
-
const entityMap = this.listSnapshots.get(scopeId);
|
|
369
|
-
const cached = entityMap.get(entity);
|
|
370
|
-
if (cached && cached.version === version)
|
|
371
|
-
return cached.data;
|
|
372
|
-
const records = this.listRecords(entity, scopeId);
|
|
373
|
-
entityMap.set(entity, { version, data: records });
|
|
374
|
-
return records;
|
|
375
|
-
}
|
|
376
|
-
getSnapshotAsMap(entity, scopeId) {
|
|
377
|
-
const version = this.getVersion(scopeId, entity);
|
|
378
|
-
if (!this.mapSnapshots.has(scopeId)) {
|
|
379
|
-
this.mapSnapshots.set(scopeId, new Map());
|
|
380
|
-
}
|
|
381
|
-
const entityMap = this.mapSnapshots.get(scopeId);
|
|
382
|
-
const cached = entityMap.get(entity);
|
|
383
|
-
if (cached && cached.version === version)
|
|
384
|
-
return cached.data;
|
|
385
|
-
const records = this.listRecords(entity, scopeId);
|
|
386
|
-
const map = {};
|
|
387
|
-
for (const r of records) {
|
|
388
|
-
map[r.id] = r;
|
|
389
|
-
}
|
|
390
|
-
entityMap.set(entity, { version, data: map });
|
|
391
|
-
return map;
|
|
392
|
-
}
|
|
393
|
-
create(entity, scopeId, data, tag) {
|
|
394
|
-
if (this._corrupted)
|
|
395
|
-
return;
|
|
396
|
-
try {
|
|
397
|
-
this.originTag = tag ?? null;
|
|
398
|
-
const { rootEntity, scopeField } = this.config.scope;
|
|
399
|
-
const record = entity === rootEntity ? stripNulls(data) : stripNulls({ ...data, [scopeField]: scopeId });
|
|
400
|
-
this.db.createSync(entity, record);
|
|
401
|
-
}
|
|
402
|
-
catch (err) {
|
|
403
|
-
console.error(wrapWasmError(`createSync:${entity}`, err));
|
|
404
|
-
}
|
|
405
|
-
finally {
|
|
406
|
-
this.originTag = null;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
update(entity, id, fields, tag) {
|
|
410
|
-
if (this._corrupted)
|
|
411
|
-
return;
|
|
412
|
-
try {
|
|
413
|
-
this.originTag = tag ?? null;
|
|
414
|
-
this.db.updateSync(entity, id, stripNulls(fields));
|
|
415
|
-
}
|
|
416
|
-
catch (err) {
|
|
417
|
-
console.error(wrapWasmError(`updateSync:${entity}`, err));
|
|
418
|
-
}
|
|
419
|
-
finally {
|
|
420
|
-
this.originTag = null;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
read(entity, id) {
|
|
424
|
-
if (this._corrupted)
|
|
425
|
-
return null;
|
|
426
|
-
try {
|
|
427
|
-
return this.db.readSync(entity, id);
|
|
428
|
-
}
|
|
429
|
-
catch {
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
delete(entity, id, tag) {
|
|
434
|
-
if (this._corrupted)
|
|
435
|
-
return;
|
|
436
|
-
try {
|
|
437
|
-
this.originTag = tag ?? null;
|
|
438
|
-
const record = this.read(entity, id);
|
|
439
|
-
if (!record)
|
|
440
|
-
return;
|
|
441
|
-
const scopeField = this.config.scope.scopeField;
|
|
442
|
-
this.pendingDeleteContext = { scopeId: record[scopeField] };
|
|
443
|
-
this.db.deleteSync(entity, id);
|
|
444
|
-
}
|
|
445
|
-
catch (err) {
|
|
446
|
-
console.error(wrapWasmError(`deleteSync:${entity}`, err));
|
|
447
|
-
}
|
|
448
|
-
finally {
|
|
449
|
-
this.originTag = null;
|
|
450
|
-
this.pendingDeleteContext = null;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
list(entity, scopeId) {
|
|
454
|
-
return this.listRecords(entity, scopeId);
|
|
455
|
-
}
|
|
456
|
-
loadScope(scopeId, data, tag) {
|
|
457
|
-
if (!this._Database)
|
|
458
|
-
return;
|
|
459
|
-
const newDb = new this._Database();
|
|
460
|
-
this.registerSchemasOn(newDb);
|
|
461
|
-
const scopeField = this.config.scope.scopeField;
|
|
462
|
-
for (const [entity, records] of Object.entries(data)) {
|
|
463
|
-
for (const record of records) {
|
|
464
|
-
try {
|
|
465
|
-
const rec = { ...record, [scopeField]: scopeId };
|
|
466
|
-
newDb.createSync(entity, rec);
|
|
467
|
-
}
|
|
468
|
-
catch (err) {
|
|
469
|
-
console.error(wrapWasmError(`loadScope.createSync:${entity}`, err));
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
this.unsubscribeAll();
|
|
474
|
-
this._db = newDb;
|
|
475
|
-
this._corrupted = false;
|
|
476
|
-
this._dbReady = true;
|
|
477
|
-
this.setupSubscriptions();
|
|
478
|
-
this.clearAllCaches();
|
|
479
|
-
this.originTag = tag ?? null;
|
|
480
|
-
try {
|
|
481
|
-
for (const entity of this.allEntities) {
|
|
482
|
-
this.bumpVersion(scopeId, entity);
|
|
483
|
-
this.notifySubscribers(scopeId, entity);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
finally {
|
|
487
|
-
this.originTag = null;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
beginBatch() {
|
|
491
|
-
this.batchDepth++;
|
|
492
|
-
}
|
|
493
|
-
endBatch() {
|
|
494
|
-
if (this.batchDepth <= 0)
|
|
495
|
-
return;
|
|
496
|
-
this.batchDepth--;
|
|
497
|
-
if (this.batchDepth > 0)
|
|
498
|
-
return;
|
|
499
|
-
const keys = this.batchedKeys;
|
|
500
|
-
const mutations = this.batchedMutations;
|
|
501
|
-
this.batchedKeys = new Set();
|
|
502
|
-
this.batchedMutations = [];
|
|
503
|
-
for (const key of keys) {
|
|
504
|
-
try {
|
|
505
|
-
const [scopeId, entity] = key.split('\0');
|
|
506
|
-
this.notifySubscribers(scopeId, entity);
|
|
507
|
-
}
|
|
508
|
-
catch (err) {
|
|
509
|
-
console.error('[MemoryStore] endBatch subscriber error:', err);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
for (const mutation of mutations) {
|
|
513
|
-
this.emitMutation(mutation);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
clearScope(scopeId) {
|
|
517
|
-
if (this._corrupted) {
|
|
518
|
-
this.clearAllCaches();
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
let recoveredDuringClear = false;
|
|
522
|
-
this.originTag = 'clear';
|
|
523
|
-
this.beginBatch();
|
|
524
|
-
try {
|
|
525
|
-
for (const entity of topologicalDeleteOrder(this.allEntities, this.config)) {
|
|
526
|
-
if (this._corrupted) {
|
|
527
|
-
recoveredDuringClear = true;
|
|
528
|
-
break;
|
|
529
|
-
}
|
|
530
|
-
const records = this.listRecords(entity, scopeId);
|
|
531
|
-
if (this._corrupted) {
|
|
532
|
-
recoveredDuringClear = true;
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
for (const record of records) {
|
|
536
|
-
try {
|
|
537
|
-
this.pendingDeleteContext = { scopeId };
|
|
538
|
-
this.db.deleteSync(entity, record.id);
|
|
539
|
-
}
|
|
540
|
-
catch {
|
|
541
|
-
// cascade
|
|
542
|
-
}
|
|
543
|
-
finally {
|
|
544
|
-
this.pendingDeleteContext = null;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
finally {
|
|
550
|
-
this.originTag = null;
|
|
551
|
-
if (recoveredDuringClear) {
|
|
552
|
-
this.batchDepth = Math.max(0, this.batchDepth - 1);
|
|
553
|
-
this.batchedKeys = new Set();
|
|
554
|
-
this.batchedMutations = [];
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
this.endBatch();
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
if (this.listSnapshots.has(scopeId)) {
|
|
561
|
-
this.listSnapshots.delete(scopeId);
|
|
562
|
-
}
|
|
563
|
-
if (this.mapSnapshots.has(scopeId)) {
|
|
564
|
-
this.mapSnapshots.delete(scopeId);
|
|
565
|
-
}
|
|
566
|
-
this.versions.delete(scopeId);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
export function createMemoryStore(config) {
|
|
570
|
-
return new MemoryStoreImpl(config);
|
|
571
|
-
}
|
|
572
|
-
//# sourceMappingURL=memory-store.js.map
|