mvc-kit 2.5.3 → 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Channel.cjs +291 -0
- package/dist/Channel.cjs.map +1 -0
- package/dist/Channel.js +291 -0
- package/dist/Channel.js.map +1 -0
- package/dist/Collection.cjs +452 -0
- package/dist/Collection.cjs.map +1 -0
- package/dist/Collection.js +452 -0
- package/dist/Collection.js.map +1 -0
- package/dist/Controller.cjs +57 -0
- package/dist/Controller.cjs.map +1 -0
- package/dist/Controller.js +57 -0
- package/dist/Controller.js.map +1 -0
- package/dist/EventBus.cjs +84 -0
- package/dist/EventBus.cjs.map +1 -0
- package/dist/EventBus.js +84 -0
- package/dist/EventBus.js.map +1 -0
- package/dist/Model.cjs +175 -0
- package/dist/Model.cjs.map +1 -0
- package/dist/Model.js +175 -0
- package/dist/Model.js.map +1 -0
- package/dist/PersistentCollection.cjs +285 -0
- package/dist/PersistentCollection.cjs.map +1 -0
- package/dist/PersistentCollection.js +285 -0
- package/dist/PersistentCollection.js.map +1 -0
- package/dist/Resource.cjs +308 -0
- package/dist/Resource.cjs.map +1 -0
- package/dist/Resource.js +308 -0
- package/dist/Resource.js.map +1 -0
- package/dist/Service.cjs +51 -0
- package/dist/Service.cjs.map +1 -0
- package/dist/Service.js +51 -0
- package/dist/Service.js.map +1 -0
- package/dist/ViewModel.cjs +582 -0
- package/dist/ViewModel.cjs.map +1 -0
- package/dist/ViewModel.d.ts +1 -7
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/ViewModel.js +582 -0
- package/dist/ViewModel.js.map +1 -0
- package/dist/errors.cjs +79 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.js +79 -0
- package/dist/errors.js.map +1 -0
- package/dist/mvc-kit.cjs +29 -1
- package/dist/mvc-kit.cjs.map +1 -1
- package/dist/mvc-kit.js +27 -1132
- package/dist/mvc-kit.js.map +1 -1
- package/dist/react/guards.cjs +7 -0
- package/dist/react/guards.cjs.map +1 -0
- package/dist/react/guards.js +7 -0
- package/dist/react/guards.js.map +1 -0
- package/dist/react/provider.cjs +26 -0
- package/dist/react/provider.cjs.map +1 -0
- package/dist/react/provider.js +26 -0
- package/dist/react/provider.js.map +1 -0
- package/dist/react/use-event-bus.cjs +26 -0
- package/dist/react/use-event-bus.cjs.map +1 -0
- package/dist/react/use-event-bus.js +26 -0
- package/dist/react/use-event-bus.js.map +1 -0
- package/dist/react/use-instance.cjs +31 -0
- package/dist/react/use-instance.cjs.map +1 -0
- package/dist/react/use-instance.js +31 -0
- package/dist/react/use-instance.js.map +1 -0
- package/dist/react/use-local.cjs +64 -0
- package/dist/react/use-local.cjs.map +1 -0
- package/dist/react/use-local.js +64 -0
- package/dist/react/use-local.js.map +1 -0
- package/dist/react/use-model.cjs +80 -0
- package/dist/react/use-model.cjs.map +1 -0
- package/dist/react/use-model.js +80 -0
- package/dist/react/use-model.js.map +1 -0
- package/dist/react/use-singleton.cjs +21 -0
- package/dist/react/use-singleton.cjs.map +1 -0
- package/dist/react/use-singleton.js +21 -0
- package/dist/react/use-singleton.js.map +1 -0
- package/dist/react/use-teardown.cjs +22 -0
- package/dist/react/use-teardown.cjs.map +1 -0
- package/dist/react/use-teardown.js +22 -0
- package/dist/react/use-teardown.js.map +1 -0
- package/dist/react-native/NativeCollection.cjs +76 -0
- package/dist/react-native/NativeCollection.cjs.map +1 -0
- package/dist/react-native/NativeCollection.js +76 -0
- package/dist/react-native/NativeCollection.js.map +1 -0
- package/dist/react-native.cjs +4 -1
- package/dist/react-native.cjs.map +1 -1
- package/dist/react-native.js +2 -60
- package/dist/react-native.js.map +1 -1
- package/dist/react.cjs +19 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +17 -145
- package/dist/react.js.map +1 -1
- package/dist/singleton.cjs +34 -0
- package/dist/singleton.cjs.map +1 -0
- package/dist/singleton.js +34 -0
- package/dist/singleton.js.map +1 -0
- package/dist/walkPrototypeChain.cjs +15 -0
- package/dist/walkPrototypeChain.cjs.map +1 -0
- package/dist/walkPrototypeChain.d.ts +9 -0
- package/dist/walkPrototypeChain.d.ts.map +1 -0
- package/dist/walkPrototypeChain.js +15 -0
- package/dist/walkPrototypeChain.js.map +1 -0
- package/dist/web/IndexedDBCollection.cjs +37 -0
- package/dist/web/IndexedDBCollection.cjs.map +1 -0
- package/dist/web/IndexedDBCollection.js +37 -0
- package/dist/web/IndexedDBCollection.js.map +1 -0
- package/dist/web/WebStorageCollection.cjs +85 -0
- package/dist/web/WebStorageCollection.cjs.map +1 -0
- package/dist/web/WebStorageCollection.js +85 -0
- package/dist/web/WebStorageCollection.js.map +1 -0
- package/dist/web/idb.cjs +121 -0
- package/dist/web/idb.cjs.map +1 -0
- package/dist/web/idb.js +121 -0
- package/dist/web/idb.js.map +1 -0
- package/dist/web.cjs +6 -1
- package/dist/web.cjs.map +1 -1
- package/dist/web.js +4 -178
- package/dist/web.js.map +1 -1
- package/package.json +4 -2
- package/dist/PersistentCollection-B8kNECDj.cjs +0 -2
- package/dist/PersistentCollection-B8kNECDj.cjs.map +0 -1
- package/dist/PersistentCollection-CbYqzFHc.js +0 -542
- package/dist/PersistentCollection-CbYqzFHc.js.map +0 -1
- package/dist/singleton-CaEXSbYg.js +0 -89
- package/dist/singleton-CaEXSbYg.js.map +0 -1
- package/dist/singleton-L-u2W_lX.cjs +0 -2
- package/dist/singleton-L-u2W_lX.cjs.map +0 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const Collection = require("./Collection.cjs");
|
|
4
|
+
const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
|
|
5
|
+
const _registeredKeys = __DEV__ ? /* @__PURE__ */ new Map() : null;
|
|
6
|
+
class PersistentCollection extends Collection.Collection {
|
|
7
|
+
/** Debounce delay in ms for storage writes. 0 = immediate. */
|
|
8
|
+
static WRITE_DELAY = 100;
|
|
9
|
+
// ── Serialization hooks ──
|
|
10
|
+
/** Serialize items to a string. Used by string-based adapters (WebStorage, NativeCollection). */
|
|
11
|
+
serialize(items) {
|
|
12
|
+
return JSON.stringify(items);
|
|
13
|
+
}
|
|
14
|
+
/** Deserialize a string back to items. Used by string-based adapters. */
|
|
15
|
+
deserialize(raw) {
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
// ── Internal state ──
|
|
19
|
+
_hydrated = false;
|
|
20
|
+
_hydrating = false;
|
|
21
|
+
_persistenceReady = false;
|
|
22
|
+
_preHydrationWarned = false;
|
|
23
|
+
_pendingWrites = /* @__PURE__ */ new Map();
|
|
24
|
+
_pendingRemoves = /* @__PURE__ */ new Set();
|
|
25
|
+
_pendingClear = false;
|
|
26
|
+
_flushTimer = null;
|
|
27
|
+
constructor(initialItems = []) {
|
|
28
|
+
super(initialItems);
|
|
29
|
+
const unsub = this.subscribe((current, prev) => {
|
|
30
|
+
if (this._hydrating) return;
|
|
31
|
+
this._ensurePersistenceReady();
|
|
32
|
+
this._diffAndQueue(current, prev);
|
|
33
|
+
this._scheduleSave();
|
|
34
|
+
});
|
|
35
|
+
this.addCleanup(unsub);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* DEV check for duplicate storageKey. Called lazily since storageKey is an abstract
|
|
39
|
+
* field that isn't available during the parent constructor chain.
|
|
40
|
+
*/
|
|
41
|
+
_ensurePersistenceReady() {
|
|
42
|
+
if (this._persistenceReady) return;
|
|
43
|
+
this._persistenceReady = true;
|
|
44
|
+
if (__DEV__ && _registeredKeys) {
|
|
45
|
+
const className = this.constructor.name;
|
|
46
|
+
const existing = _registeredKeys.get(this.storageKey);
|
|
47
|
+
if (existing && existing !== className) {
|
|
48
|
+
console.warn(
|
|
49
|
+
`[mvc-kit] Duplicate storageKey "${this.storageKey}" used by "${className}" and "${existing}". Each PersistentCollection should have a unique storageKey.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
_registeredKeys.set(this.storageKey, className);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ── Public API ──
|
|
56
|
+
/** Whether storage data has been loaded. */
|
|
57
|
+
get hydrated() {
|
|
58
|
+
return this._hydrated;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load data from storage into the collection. Idempotent — subsequent calls return current items.
|
|
62
|
+
* Returns the items after hydration.
|
|
63
|
+
*/
|
|
64
|
+
async hydrate() {
|
|
65
|
+
if (this._hydrated) return this.items;
|
|
66
|
+
this._ensurePersistenceReady();
|
|
67
|
+
this._hydrating = true;
|
|
68
|
+
try {
|
|
69
|
+
const stored = await this.persistGetAll();
|
|
70
|
+
if (stored.length > 0) {
|
|
71
|
+
super.reset(stored);
|
|
72
|
+
}
|
|
73
|
+
this._hydrated = true;
|
|
74
|
+
return this.items;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
this._handlePersistError(err);
|
|
77
|
+
this._hydrated = true;
|
|
78
|
+
return this.items;
|
|
79
|
+
} finally {
|
|
80
|
+
this._hydrating = false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Synchronous hydration for sync adapters (e.g., WebStorage).
|
|
85
|
+
* Call from the **leaf class** constructor (after field initializers have run).
|
|
86
|
+
*/
|
|
87
|
+
_hydrateSync() {
|
|
88
|
+
if (this._hydrated) return;
|
|
89
|
+
this._ensurePersistenceReady();
|
|
90
|
+
this._hydrating = true;
|
|
91
|
+
try {
|
|
92
|
+
const stored = this.persistGetAll();
|
|
93
|
+
if (stored instanceof Promise) {
|
|
94
|
+
throw new Error("[mvc-kit] _hydrateSync called with async persistGetAll");
|
|
95
|
+
}
|
|
96
|
+
if (stored.length > 0) {
|
|
97
|
+
super.reset(stored);
|
|
98
|
+
}
|
|
99
|
+
this._hydrated = true;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
this._handlePersistError(err);
|
|
102
|
+
this._hydrated = true;
|
|
103
|
+
} finally {
|
|
104
|
+
this._hydrating = false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Clear all data from storage AND from the in-memory collection.
|
|
109
|
+
*/
|
|
110
|
+
clearStorage() {
|
|
111
|
+
this._ensurePersistenceReady();
|
|
112
|
+
this._pendingWrites.clear();
|
|
113
|
+
this._pendingRemoves.clear();
|
|
114
|
+
this._pendingClear = false;
|
|
115
|
+
this._cancelSave();
|
|
116
|
+
if (this.length > 0) {
|
|
117
|
+
super.clear();
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const result = this.persistClear();
|
|
121
|
+
if (result instanceof Promise) {
|
|
122
|
+
return result.catch((err) => this._handlePersistError(err));
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
this._handlePersistError(err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ── Overrides for clear/reset tracking ──
|
|
129
|
+
reset(items) {
|
|
130
|
+
this._pendingClear = true;
|
|
131
|
+
this._pendingWrites.clear();
|
|
132
|
+
this._pendingRemoves.clear();
|
|
133
|
+
for (const item of items) {
|
|
134
|
+
this._pendingWrites.set(item.id, item);
|
|
135
|
+
}
|
|
136
|
+
this._hydrating = true;
|
|
137
|
+
try {
|
|
138
|
+
super.reset(items);
|
|
139
|
+
} finally {
|
|
140
|
+
this._hydrating = false;
|
|
141
|
+
}
|
|
142
|
+
this._scheduleSave();
|
|
143
|
+
}
|
|
144
|
+
clear() {
|
|
145
|
+
this._pendingClear = true;
|
|
146
|
+
this._pendingWrites.clear();
|
|
147
|
+
this._pendingRemoves.clear();
|
|
148
|
+
this._hydrating = true;
|
|
149
|
+
try {
|
|
150
|
+
super.clear();
|
|
151
|
+
} finally {
|
|
152
|
+
this._hydrating = false;
|
|
153
|
+
}
|
|
154
|
+
this._scheduleSave();
|
|
155
|
+
}
|
|
156
|
+
// ── Override items getter for DEV pre-hydration warning ──
|
|
157
|
+
get items() {
|
|
158
|
+
if (__DEV__ && !this._hydrated && !this._hydrating && !this._preHydrationWarned) {
|
|
159
|
+
this._preHydrationWarned = true;
|
|
160
|
+
console.warn(
|
|
161
|
+
`[mvc-kit] Accessing items on "${this.constructor.name}" before hydrate() has been called. Data may be incomplete. Call hydrate() first.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return super.items;
|
|
165
|
+
}
|
|
166
|
+
get state() {
|
|
167
|
+
return this.items;
|
|
168
|
+
}
|
|
169
|
+
// ── Dispose ──
|
|
170
|
+
dispose() {
|
|
171
|
+
if (this.disposed) return;
|
|
172
|
+
this._cancelSave();
|
|
173
|
+
if (this._hasPending()) {
|
|
174
|
+
try {
|
|
175
|
+
const result = this._flush();
|
|
176
|
+
if (result instanceof Promise) {
|
|
177
|
+
result.catch((err) => this._handlePersistError(err));
|
|
178
|
+
}
|
|
179
|
+
} catch (err) {
|
|
180
|
+
this._handlePersistError(err);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (__DEV__ && _registeredKeys && this._persistenceReady) {
|
|
184
|
+
_registeredKeys.delete(this.storageKey);
|
|
185
|
+
}
|
|
186
|
+
super.dispose();
|
|
187
|
+
}
|
|
188
|
+
// ── Private: delta tracking ──
|
|
189
|
+
_diffAndQueue(current, prev) {
|
|
190
|
+
const prevMap = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const item of prev) {
|
|
192
|
+
prevMap.set(item.id, item);
|
|
193
|
+
}
|
|
194
|
+
const currentMap = /* @__PURE__ */ new Map();
|
|
195
|
+
for (const item of current) {
|
|
196
|
+
currentMap.set(item.id, item);
|
|
197
|
+
}
|
|
198
|
+
for (const item of current) {
|
|
199
|
+
const prevItem = prevMap.get(item.id);
|
|
200
|
+
if (!prevItem || prevItem !== item) {
|
|
201
|
+
this._pendingWrites.set(item.id, item);
|
|
202
|
+
this._pendingRemoves.delete(item.id);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const item of prev) {
|
|
206
|
+
if (!currentMap.has(item.id)) {
|
|
207
|
+
this._pendingRemoves.add(item.id);
|
|
208
|
+
this._pendingWrites.delete(item.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
_hasPending() {
|
|
213
|
+
return this._pendingClear || this._pendingWrites.size > 0 || this._pendingRemoves.size > 0;
|
|
214
|
+
}
|
|
215
|
+
// ── Private: debounce + flush ──
|
|
216
|
+
_scheduleSave() {
|
|
217
|
+
this._cancelSave();
|
|
218
|
+
const delay = this.constructor.WRITE_DELAY;
|
|
219
|
+
if (delay <= 0) {
|
|
220
|
+
this._doFlush();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this._flushTimer = setTimeout(() => this._doFlush(), delay);
|
|
224
|
+
}
|
|
225
|
+
_cancelSave() {
|
|
226
|
+
if (this._flushTimer !== null) {
|
|
227
|
+
clearTimeout(this._flushTimer);
|
|
228
|
+
this._flushTimer = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
_doFlush() {
|
|
232
|
+
if (!this._hasPending()) return;
|
|
233
|
+
try {
|
|
234
|
+
const result = this._flush();
|
|
235
|
+
if (result instanceof Promise) {
|
|
236
|
+
result.catch((err) => this._handlePersistError(err));
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
this._handlePersistError(err);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
_flush() {
|
|
243
|
+
const doClear = this._pendingClear;
|
|
244
|
+
const writes = this._pendingWrites.size > 0 ? [...this._pendingWrites.values()] : null;
|
|
245
|
+
const removes = this._pendingRemoves.size > 0 ? [...this._pendingRemoves] : null;
|
|
246
|
+
this._pendingClear = false;
|
|
247
|
+
this._pendingWrites.clear();
|
|
248
|
+
this._pendingRemoves.clear();
|
|
249
|
+
if (doClear) {
|
|
250
|
+
const clearResult = this.persistClear();
|
|
251
|
+
if (clearResult instanceof Promise) {
|
|
252
|
+
return clearResult.then(() => {
|
|
253
|
+
if (writes) return this.persistSet(writes);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (writes) {
|
|
257
|
+
return this.persistSet(writes);
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (removes) {
|
|
262
|
+
const removeResult = this.persistRemove(removes);
|
|
263
|
+
if (removeResult instanceof Promise) {
|
|
264
|
+
return removeResult.then(() => {
|
|
265
|
+
if (writes) return this.persistSet(writes);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (writes) {
|
|
270
|
+
return this.persistSet(writes);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// ── Private: error handling ──
|
|
274
|
+
_handlePersistError(err) {
|
|
275
|
+
if (this.onPersistError) {
|
|
276
|
+
this.onPersistError(err);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (__DEV__) {
|
|
280
|
+
console.warn("[mvc-kit] Storage error:", err);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
exports.PersistentCollection = PersistentCollection;
|
|
285
|
+
//# sourceMappingURL=PersistentCollection.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PersistentCollection.cjs","sources":["../src/PersistentCollection.ts"],"sourcesContent":["import { Collection } from './Collection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// Track storageKey uniqueness in DEV\nconst _registeredKeys: Map<string, string> | null = __DEV__ ? new Map() : null;\n\n/**\n * Abstract base for Collections that persist to external storage.\n * Tracks deltas per mutation and flushes via debounced writes.\n * Subclasses implement the storage-specific `persist*` methods.\n */\nexport abstract class PersistentCollection<\n T extends { id: string | number },\n> extends Collection<T> {\n /** Debounce delay in ms for storage writes. 0 = immediate. */\n static WRITE_DELAY = 100;\n\n /** Unique key identifying this collection in storage. */\n protected abstract readonly storageKey: string;\n\n // ── Abstract persistence methods ──\n\n protected abstract persistGet(id: T['id']): T | null | Promise<T | null>;\n protected abstract persistGetAll(): T[] | Promise<T[]>;\n /** Upsert semantics — insert or replace the given items. */\n protected abstract persistSet(items: T[]): void | Promise<void>;\n protected abstract persistRemove(ids: T['id'][]): void | Promise<void>;\n protected abstract persistClear(): void | Promise<void>;\n\n // ── Serialization hooks ──\n\n /** Serialize items to a string. Used by string-based adapters (WebStorage, NativeCollection). */\n protected serialize(items: T[]): string {\n return JSON.stringify(items);\n }\n\n /** Deserialize a string back to items. Used by string-based adapters. */\n protected deserialize(raw: string): T[] {\n return JSON.parse(raw);\n }\n\n // ── Error hook ──\n\n /** Called when a storage operation fails. Override for custom error handling. */\n protected onPersistError?(error: unknown): void;\n\n // ── Internal state ──\n\n private _hydrated = false;\n private _hydrating = false;\n private _persistenceReady = false;\n private _preHydrationWarned = false;\n private _pendingWrites = new Map<T['id'], T>();\n private _pendingRemoves = new Set<T['id']>();\n private _pendingClear = false;\n private _flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(initialItems: T[] = []) {\n super(initialItems);\n\n // Self-subscribe to detect mutations via diff.\n // storageKey may not be available yet (class field initializers run after super()),\n // but that's fine — the subscriber only fires on mutations, not during construction.\n const unsub = this.subscribe((current, prev) => {\n if (this._hydrating) return;\n this._ensurePersistenceReady();\n this._diffAndQueue(current, prev);\n this._scheduleSave();\n });\n this.addCleanup(unsub);\n }\n\n /**\n * DEV check for duplicate storageKey. Called lazily since storageKey is an abstract\n * field that isn't available during the parent constructor chain.\n */\n private _ensurePersistenceReady(): void {\n if (this._persistenceReady) return;\n this._persistenceReady = true;\n\n if (__DEV__ && _registeredKeys) {\n const className = this.constructor.name;\n const existing = _registeredKeys.get(this.storageKey);\n if (existing && existing !== className) {\n console.warn(\n `[mvc-kit] Duplicate storageKey \"${this.storageKey}\" used by \"${className}\" ` +\n `and \"${existing}\". Each PersistentCollection should have a unique storageKey.`,\n );\n }\n _registeredKeys.set(this.storageKey, className);\n }\n }\n\n // ── Public API ──\n\n /** Whether storage data has been loaded. */\n get hydrated(): boolean {\n return this._hydrated;\n }\n\n /**\n * Load data from storage into the collection. Idempotent — subsequent calls return current items.\n * Returns the items after hydration.\n */\n async hydrate(): Promise<T[]> {\n if (this._hydrated) return this.items;\n this._ensurePersistenceReady();\n\n this._hydrating = true;\n try {\n const stored = await this.persistGetAll();\n if (stored.length > 0) {\n super.reset(stored);\n }\n this._hydrated = true;\n return this.items;\n } catch (err) {\n this._handlePersistError(err);\n this._hydrated = true;\n return this.items;\n } finally {\n this._hydrating = false;\n }\n }\n\n /**\n * Synchronous hydration for sync adapters (e.g., WebStorage).\n * Call from the **leaf class** constructor (after field initializers have run).\n */\n protected _hydrateSync(): void {\n if (this._hydrated) return;\n this._ensurePersistenceReady();\n\n this._hydrating = true;\n try {\n const stored = this.persistGetAll();\n if (stored instanceof Promise) {\n throw new Error('[mvc-kit] _hydrateSync called with async persistGetAll');\n }\n if (stored.length > 0) {\n super.reset(stored);\n }\n this._hydrated = true;\n } catch (err) {\n this._handlePersistError(err);\n this._hydrated = true;\n } finally {\n this._hydrating = false;\n }\n }\n\n /**\n * Clear all data from storage AND from the in-memory collection.\n */\n clearStorage(): void | Promise<void> {\n this._ensurePersistenceReady();\n\n // Clear pending queues — we're wiping everything\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n this._pendingClear = false;\n this._cancelSave();\n\n // Clear in-memory\n if (this.length > 0) {\n super.clear();\n }\n\n try {\n const result = this.persistClear();\n if (result instanceof Promise) {\n return result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n // ── Overrides for clear/reset tracking ──\n\n reset(items: T[]): void {\n this._pendingClear = true;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n for (const item of items) {\n this._pendingWrites.set(item.id, item);\n }\n this._hydrating = true; // Re-use flag to skip subscriber\n try {\n super.reset(items);\n } finally {\n this._hydrating = false;\n }\n this._scheduleSave();\n }\n\n clear(): void {\n this._pendingClear = true;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n this._hydrating = true;\n try {\n super.clear();\n } finally {\n this._hydrating = false;\n }\n this._scheduleSave();\n }\n\n // ── Override items getter for DEV pre-hydration warning ──\n\n get items(): T[] {\n if (__DEV__ && !this._hydrated && !this._hydrating && !this._preHydrationWarned) {\n this._preHydrationWarned = true;\n console.warn(\n `[mvc-kit] Accessing items on \"${this.constructor.name}\" before hydrate() has been called. ` +\n `Data may be incomplete. Call hydrate() first.`,\n );\n }\n return super.items;\n }\n\n get state(): T[] {\n return this.items;\n }\n\n // ── Dispose ──\n\n dispose(): void {\n if (this.disposed) return;\n\n // Flush any pending saves before disposing\n this._cancelSave();\n if (this._hasPending()) {\n try {\n const result = this._flush();\n if (result instanceof Promise) {\n result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n // DEV: unregister storageKey\n if (__DEV__ && _registeredKeys && this._persistenceReady) {\n _registeredKeys.delete(this.storageKey);\n }\n\n super.dispose();\n }\n\n // ── Private: delta tracking ──\n\n private _diffAndQueue(current: readonly T[], prev: readonly T[]): void {\n const prevMap = new Map<T['id'], T>();\n for (const item of prev) {\n prevMap.set(item.id, item);\n }\n\n const currentMap = new Map<T['id'], T>();\n for (const item of current) {\n currentMap.set(item.id, item);\n }\n\n // Added or updated: in current but not in prev, or different reference\n for (const item of current) {\n const prevItem = prevMap.get(item.id);\n if (!prevItem || prevItem !== item) {\n this._pendingWrites.set(item.id, item);\n this._pendingRemoves.delete(item.id);\n }\n }\n\n // Removed: in prev but not in current\n for (const item of prev) {\n if (!currentMap.has(item.id)) {\n this._pendingRemoves.add(item.id);\n this._pendingWrites.delete(item.id);\n }\n }\n }\n\n private _hasPending(): boolean {\n return this._pendingClear || this._pendingWrites.size > 0 || this._pendingRemoves.size > 0;\n }\n\n // ── Private: debounce + flush ──\n\n private _scheduleSave(): void {\n this._cancelSave();\n const delay = (this.constructor as typeof PersistentCollection).WRITE_DELAY;\n if (delay <= 0) {\n this._doFlush();\n return;\n }\n this._flushTimer = setTimeout(() => this._doFlush(), delay);\n }\n\n private _cancelSave(): void {\n if (this._flushTimer !== null) {\n clearTimeout(this._flushTimer);\n this._flushTimer = null;\n }\n }\n\n private _doFlush(): void {\n if (!this._hasPending()) return;\n try {\n const result = this._flush();\n if (result instanceof Promise) {\n result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n private _flush(): void | Promise<void> {\n const doClear = this._pendingClear;\n const writes = this._pendingWrites.size > 0 ? [...this._pendingWrites.values()] : null;\n const removes = this._pendingRemoves.size > 0 ? [...this._pendingRemoves] : null;\n\n // Clear queues\n this._pendingClear = false;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n\n if (doClear) {\n const clearResult = this.persistClear();\n if (clearResult instanceof Promise) {\n return clearResult.then(() => {\n if (writes) return this.persistSet(writes);\n });\n }\n if (writes) {\n return this.persistSet(writes);\n }\n return;\n }\n\n // Non-clear: removes then writes\n if (removes) {\n const removeResult = this.persistRemove(removes);\n if (removeResult instanceof Promise) {\n return removeResult.then(() => {\n if (writes) return this.persistSet(writes);\n });\n }\n }\n if (writes) {\n return this.persistSet(writes);\n }\n }\n\n // ── Private: error handling ──\n\n private _handlePersistError(err: unknown): void {\n if (this.onPersistError) {\n this.onPersistError(err);\n return;\n }\n if (__DEV__) {\n console.warn('[mvc-kit] Storage error:', err);\n }\n }\n}\n"],"names":["Collection"],"mappings":"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"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Collection } from "./Collection.js";
|
|
2
|
+
const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
|
|
3
|
+
const _registeredKeys = __DEV__ ? /* @__PURE__ */ new Map() : null;
|
|
4
|
+
class PersistentCollection extends Collection {
|
|
5
|
+
/** Debounce delay in ms for storage writes. 0 = immediate. */
|
|
6
|
+
static WRITE_DELAY = 100;
|
|
7
|
+
// ── Serialization hooks ──
|
|
8
|
+
/** Serialize items to a string. Used by string-based adapters (WebStorage, NativeCollection). */
|
|
9
|
+
serialize(items) {
|
|
10
|
+
return JSON.stringify(items);
|
|
11
|
+
}
|
|
12
|
+
/** Deserialize a string back to items. Used by string-based adapters. */
|
|
13
|
+
deserialize(raw) {
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
// ── Internal state ──
|
|
17
|
+
_hydrated = false;
|
|
18
|
+
_hydrating = false;
|
|
19
|
+
_persistenceReady = false;
|
|
20
|
+
_preHydrationWarned = false;
|
|
21
|
+
_pendingWrites = /* @__PURE__ */ new Map();
|
|
22
|
+
_pendingRemoves = /* @__PURE__ */ new Set();
|
|
23
|
+
_pendingClear = false;
|
|
24
|
+
_flushTimer = null;
|
|
25
|
+
constructor(initialItems = []) {
|
|
26
|
+
super(initialItems);
|
|
27
|
+
const unsub = this.subscribe((current, prev) => {
|
|
28
|
+
if (this._hydrating) return;
|
|
29
|
+
this._ensurePersistenceReady();
|
|
30
|
+
this._diffAndQueue(current, prev);
|
|
31
|
+
this._scheduleSave();
|
|
32
|
+
});
|
|
33
|
+
this.addCleanup(unsub);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* DEV check for duplicate storageKey. Called lazily since storageKey is an abstract
|
|
37
|
+
* field that isn't available during the parent constructor chain.
|
|
38
|
+
*/
|
|
39
|
+
_ensurePersistenceReady() {
|
|
40
|
+
if (this._persistenceReady) return;
|
|
41
|
+
this._persistenceReady = true;
|
|
42
|
+
if (__DEV__ && _registeredKeys) {
|
|
43
|
+
const className = this.constructor.name;
|
|
44
|
+
const existing = _registeredKeys.get(this.storageKey);
|
|
45
|
+
if (existing && existing !== className) {
|
|
46
|
+
console.warn(
|
|
47
|
+
`[mvc-kit] Duplicate storageKey "${this.storageKey}" used by "${className}" and "${existing}". Each PersistentCollection should have a unique storageKey.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
_registeredKeys.set(this.storageKey, className);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ── Public API ──
|
|
54
|
+
/** Whether storage data has been loaded. */
|
|
55
|
+
get hydrated() {
|
|
56
|
+
return this._hydrated;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load data from storage into the collection. Idempotent — subsequent calls return current items.
|
|
60
|
+
* Returns the items after hydration.
|
|
61
|
+
*/
|
|
62
|
+
async hydrate() {
|
|
63
|
+
if (this._hydrated) return this.items;
|
|
64
|
+
this._ensurePersistenceReady();
|
|
65
|
+
this._hydrating = true;
|
|
66
|
+
try {
|
|
67
|
+
const stored = await this.persistGetAll();
|
|
68
|
+
if (stored.length > 0) {
|
|
69
|
+
super.reset(stored);
|
|
70
|
+
}
|
|
71
|
+
this._hydrated = true;
|
|
72
|
+
return this.items;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
this._handlePersistError(err);
|
|
75
|
+
this._hydrated = true;
|
|
76
|
+
return this.items;
|
|
77
|
+
} finally {
|
|
78
|
+
this._hydrating = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Synchronous hydration for sync adapters (e.g., WebStorage).
|
|
83
|
+
* Call from the **leaf class** constructor (after field initializers have run).
|
|
84
|
+
*/
|
|
85
|
+
_hydrateSync() {
|
|
86
|
+
if (this._hydrated) return;
|
|
87
|
+
this._ensurePersistenceReady();
|
|
88
|
+
this._hydrating = true;
|
|
89
|
+
try {
|
|
90
|
+
const stored = this.persistGetAll();
|
|
91
|
+
if (stored instanceof Promise) {
|
|
92
|
+
throw new Error("[mvc-kit] _hydrateSync called with async persistGetAll");
|
|
93
|
+
}
|
|
94
|
+
if (stored.length > 0) {
|
|
95
|
+
super.reset(stored);
|
|
96
|
+
}
|
|
97
|
+
this._hydrated = true;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
this._handlePersistError(err);
|
|
100
|
+
this._hydrated = true;
|
|
101
|
+
} finally {
|
|
102
|
+
this._hydrating = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Clear all data from storage AND from the in-memory collection.
|
|
107
|
+
*/
|
|
108
|
+
clearStorage() {
|
|
109
|
+
this._ensurePersistenceReady();
|
|
110
|
+
this._pendingWrites.clear();
|
|
111
|
+
this._pendingRemoves.clear();
|
|
112
|
+
this._pendingClear = false;
|
|
113
|
+
this._cancelSave();
|
|
114
|
+
if (this.length > 0) {
|
|
115
|
+
super.clear();
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const result = this.persistClear();
|
|
119
|
+
if (result instanceof Promise) {
|
|
120
|
+
return result.catch((err) => this._handlePersistError(err));
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
this._handlePersistError(err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ── Overrides for clear/reset tracking ──
|
|
127
|
+
reset(items) {
|
|
128
|
+
this._pendingClear = true;
|
|
129
|
+
this._pendingWrites.clear();
|
|
130
|
+
this._pendingRemoves.clear();
|
|
131
|
+
for (const item of items) {
|
|
132
|
+
this._pendingWrites.set(item.id, item);
|
|
133
|
+
}
|
|
134
|
+
this._hydrating = true;
|
|
135
|
+
try {
|
|
136
|
+
super.reset(items);
|
|
137
|
+
} finally {
|
|
138
|
+
this._hydrating = false;
|
|
139
|
+
}
|
|
140
|
+
this._scheduleSave();
|
|
141
|
+
}
|
|
142
|
+
clear() {
|
|
143
|
+
this._pendingClear = true;
|
|
144
|
+
this._pendingWrites.clear();
|
|
145
|
+
this._pendingRemoves.clear();
|
|
146
|
+
this._hydrating = true;
|
|
147
|
+
try {
|
|
148
|
+
super.clear();
|
|
149
|
+
} finally {
|
|
150
|
+
this._hydrating = false;
|
|
151
|
+
}
|
|
152
|
+
this._scheduleSave();
|
|
153
|
+
}
|
|
154
|
+
// ── Override items getter for DEV pre-hydration warning ──
|
|
155
|
+
get items() {
|
|
156
|
+
if (__DEV__ && !this._hydrated && !this._hydrating && !this._preHydrationWarned) {
|
|
157
|
+
this._preHydrationWarned = true;
|
|
158
|
+
console.warn(
|
|
159
|
+
`[mvc-kit] Accessing items on "${this.constructor.name}" before hydrate() has been called. Data may be incomplete. Call hydrate() first.`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return super.items;
|
|
163
|
+
}
|
|
164
|
+
get state() {
|
|
165
|
+
return this.items;
|
|
166
|
+
}
|
|
167
|
+
// ── Dispose ──
|
|
168
|
+
dispose() {
|
|
169
|
+
if (this.disposed) return;
|
|
170
|
+
this._cancelSave();
|
|
171
|
+
if (this._hasPending()) {
|
|
172
|
+
try {
|
|
173
|
+
const result = this._flush();
|
|
174
|
+
if (result instanceof Promise) {
|
|
175
|
+
result.catch((err) => this._handlePersistError(err));
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
this._handlePersistError(err);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (__DEV__ && _registeredKeys && this._persistenceReady) {
|
|
182
|
+
_registeredKeys.delete(this.storageKey);
|
|
183
|
+
}
|
|
184
|
+
super.dispose();
|
|
185
|
+
}
|
|
186
|
+
// ── Private: delta tracking ──
|
|
187
|
+
_diffAndQueue(current, prev) {
|
|
188
|
+
const prevMap = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const item of prev) {
|
|
190
|
+
prevMap.set(item.id, item);
|
|
191
|
+
}
|
|
192
|
+
const currentMap = /* @__PURE__ */ new Map();
|
|
193
|
+
for (const item of current) {
|
|
194
|
+
currentMap.set(item.id, item);
|
|
195
|
+
}
|
|
196
|
+
for (const item of current) {
|
|
197
|
+
const prevItem = prevMap.get(item.id);
|
|
198
|
+
if (!prevItem || prevItem !== item) {
|
|
199
|
+
this._pendingWrites.set(item.id, item);
|
|
200
|
+
this._pendingRemoves.delete(item.id);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const item of prev) {
|
|
204
|
+
if (!currentMap.has(item.id)) {
|
|
205
|
+
this._pendingRemoves.add(item.id);
|
|
206
|
+
this._pendingWrites.delete(item.id);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
_hasPending() {
|
|
211
|
+
return this._pendingClear || this._pendingWrites.size > 0 || this._pendingRemoves.size > 0;
|
|
212
|
+
}
|
|
213
|
+
// ── Private: debounce + flush ──
|
|
214
|
+
_scheduleSave() {
|
|
215
|
+
this._cancelSave();
|
|
216
|
+
const delay = this.constructor.WRITE_DELAY;
|
|
217
|
+
if (delay <= 0) {
|
|
218
|
+
this._doFlush();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this._flushTimer = setTimeout(() => this._doFlush(), delay);
|
|
222
|
+
}
|
|
223
|
+
_cancelSave() {
|
|
224
|
+
if (this._flushTimer !== null) {
|
|
225
|
+
clearTimeout(this._flushTimer);
|
|
226
|
+
this._flushTimer = null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
_doFlush() {
|
|
230
|
+
if (!this._hasPending()) return;
|
|
231
|
+
try {
|
|
232
|
+
const result = this._flush();
|
|
233
|
+
if (result instanceof Promise) {
|
|
234
|
+
result.catch((err) => this._handlePersistError(err));
|
|
235
|
+
}
|
|
236
|
+
} catch (err) {
|
|
237
|
+
this._handlePersistError(err);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
_flush() {
|
|
241
|
+
const doClear = this._pendingClear;
|
|
242
|
+
const writes = this._pendingWrites.size > 0 ? [...this._pendingWrites.values()] : null;
|
|
243
|
+
const removes = this._pendingRemoves.size > 0 ? [...this._pendingRemoves] : null;
|
|
244
|
+
this._pendingClear = false;
|
|
245
|
+
this._pendingWrites.clear();
|
|
246
|
+
this._pendingRemoves.clear();
|
|
247
|
+
if (doClear) {
|
|
248
|
+
const clearResult = this.persistClear();
|
|
249
|
+
if (clearResult instanceof Promise) {
|
|
250
|
+
return clearResult.then(() => {
|
|
251
|
+
if (writes) return this.persistSet(writes);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (writes) {
|
|
255
|
+
return this.persistSet(writes);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (removes) {
|
|
260
|
+
const removeResult = this.persistRemove(removes);
|
|
261
|
+
if (removeResult instanceof Promise) {
|
|
262
|
+
return removeResult.then(() => {
|
|
263
|
+
if (writes) return this.persistSet(writes);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (writes) {
|
|
268
|
+
return this.persistSet(writes);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// ── Private: error handling ──
|
|
272
|
+
_handlePersistError(err) {
|
|
273
|
+
if (this.onPersistError) {
|
|
274
|
+
this.onPersistError(err);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (__DEV__) {
|
|
278
|
+
console.warn("[mvc-kit] Storage error:", err);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
PersistentCollection
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=PersistentCollection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PersistentCollection.js","sources":["../src/PersistentCollection.ts"],"sourcesContent":["import { Collection } from './Collection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// Track storageKey uniqueness in DEV\nconst _registeredKeys: Map<string, string> | null = __DEV__ ? new Map() : null;\n\n/**\n * Abstract base for Collections that persist to external storage.\n * Tracks deltas per mutation and flushes via debounced writes.\n * Subclasses implement the storage-specific `persist*` methods.\n */\nexport abstract class PersistentCollection<\n T extends { id: string | number },\n> extends Collection<T> {\n /** Debounce delay in ms for storage writes. 0 = immediate. */\n static WRITE_DELAY = 100;\n\n /** Unique key identifying this collection in storage. */\n protected abstract readonly storageKey: string;\n\n // ── Abstract persistence methods ──\n\n protected abstract persistGet(id: T['id']): T | null | Promise<T | null>;\n protected abstract persistGetAll(): T[] | Promise<T[]>;\n /** Upsert semantics — insert or replace the given items. */\n protected abstract persistSet(items: T[]): void | Promise<void>;\n protected abstract persistRemove(ids: T['id'][]): void | Promise<void>;\n protected abstract persistClear(): void | Promise<void>;\n\n // ── Serialization hooks ──\n\n /** Serialize items to a string. Used by string-based adapters (WebStorage, NativeCollection). */\n protected serialize(items: T[]): string {\n return JSON.stringify(items);\n }\n\n /** Deserialize a string back to items. Used by string-based adapters. */\n protected deserialize(raw: string): T[] {\n return JSON.parse(raw);\n }\n\n // ── Error hook ──\n\n /** Called when a storage operation fails. Override for custom error handling. */\n protected onPersistError?(error: unknown): void;\n\n // ── Internal state ──\n\n private _hydrated = false;\n private _hydrating = false;\n private _persistenceReady = false;\n private _preHydrationWarned = false;\n private _pendingWrites = new Map<T['id'], T>();\n private _pendingRemoves = new Set<T['id']>();\n private _pendingClear = false;\n private _flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(initialItems: T[] = []) {\n super(initialItems);\n\n // Self-subscribe to detect mutations via diff.\n // storageKey may not be available yet (class field initializers run after super()),\n // but that's fine — the subscriber only fires on mutations, not during construction.\n const unsub = this.subscribe((current, prev) => {\n if (this._hydrating) return;\n this._ensurePersistenceReady();\n this._diffAndQueue(current, prev);\n this._scheduleSave();\n });\n this.addCleanup(unsub);\n }\n\n /**\n * DEV check for duplicate storageKey. Called lazily since storageKey is an abstract\n * field that isn't available during the parent constructor chain.\n */\n private _ensurePersistenceReady(): void {\n if (this._persistenceReady) return;\n this._persistenceReady = true;\n\n if (__DEV__ && _registeredKeys) {\n const className = this.constructor.name;\n const existing = _registeredKeys.get(this.storageKey);\n if (existing && existing !== className) {\n console.warn(\n `[mvc-kit] Duplicate storageKey \"${this.storageKey}\" used by \"${className}\" ` +\n `and \"${existing}\". Each PersistentCollection should have a unique storageKey.`,\n );\n }\n _registeredKeys.set(this.storageKey, className);\n }\n }\n\n // ── Public API ──\n\n /** Whether storage data has been loaded. */\n get hydrated(): boolean {\n return this._hydrated;\n }\n\n /**\n * Load data from storage into the collection. Idempotent — subsequent calls return current items.\n * Returns the items after hydration.\n */\n async hydrate(): Promise<T[]> {\n if (this._hydrated) return this.items;\n this._ensurePersistenceReady();\n\n this._hydrating = true;\n try {\n const stored = await this.persistGetAll();\n if (stored.length > 0) {\n super.reset(stored);\n }\n this._hydrated = true;\n return this.items;\n } catch (err) {\n this._handlePersistError(err);\n this._hydrated = true;\n return this.items;\n } finally {\n this._hydrating = false;\n }\n }\n\n /**\n * Synchronous hydration for sync adapters (e.g., WebStorage).\n * Call from the **leaf class** constructor (after field initializers have run).\n */\n protected _hydrateSync(): void {\n if (this._hydrated) return;\n this._ensurePersistenceReady();\n\n this._hydrating = true;\n try {\n const stored = this.persistGetAll();\n if (stored instanceof Promise) {\n throw new Error('[mvc-kit] _hydrateSync called with async persistGetAll');\n }\n if (stored.length > 0) {\n super.reset(stored);\n }\n this._hydrated = true;\n } catch (err) {\n this._handlePersistError(err);\n this._hydrated = true;\n } finally {\n this._hydrating = false;\n }\n }\n\n /**\n * Clear all data from storage AND from the in-memory collection.\n */\n clearStorage(): void | Promise<void> {\n this._ensurePersistenceReady();\n\n // Clear pending queues — we're wiping everything\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n this._pendingClear = false;\n this._cancelSave();\n\n // Clear in-memory\n if (this.length > 0) {\n super.clear();\n }\n\n try {\n const result = this.persistClear();\n if (result instanceof Promise) {\n return result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n // ── Overrides for clear/reset tracking ──\n\n reset(items: T[]): void {\n this._pendingClear = true;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n for (const item of items) {\n this._pendingWrites.set(item.id, item);\n }\n this._hydrating = true; // Re-use flag to skip subscriber\n try {\n super.reset(items);\n } finally {\n this._hydrating = false;\n }\n this._scheduleSave();\n }\n\n clear(): void {\n this._pendingClear = true;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n this._hydrating = true;\n try {\n super.clear();\n } finally {\n this._hydrating = false;\n }\n this._scheduleSave();\n }\n\n // ── Override items getter for DEV pre-hydration warning ──\n\n get items(): T[] {\n if (__DEV__ && !this._hydrated && !this._hydrating && !this._preHydrationWarned) {\n this._preHydrationWarned = true;\n console.warn(\n `[mvc-kit] Accessing items on \"${this.constructor.name}\" before hydrate() has been called. ` +\n `Data may be incomplete. Call hydrate() first.`,\n );\n }\n return super.items;\n }\n\n get state(): T[] {\n return this.items;\n }\n\n // ── Dispose ──\n\n dispose(): void {\n if (this.disposed) return;\n\n // Flush any pending saves before disposing\n this._cancelSave();\n if (this._hasPending()) {\n try {\n const result = this._flush();\n if (result instanceof Promise) {\n result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n // DEV: unregister storageKey\n if (__DEV__ && _registeredKeys && this._persistenceReady) {\n _registeredKeys.delete(this.storageKey);\n }\n\n super.dispose();\n }\n\n // ── Private: delta tracking ──\n\n private _diffAndQueue(current: readonly T[], prev: readonly T[]): void {\n const prevMap = new Map<T['id'], T>();\n for (const item of prev) {\n prevMap.set(item.id, item);\n }\n\n const currentMap = new Map<T['id'], T>();\n for (const item of current) {\n currentMap.set(item.id, item);\n }\n\n // Added or updated: in current but not in prev, or different reference\n for (const item of current) {\n const prevItem = prevMap.get(item.id);\n if (!prevItem || prevItem !== item) {\n this._pendingWrites.set(item.id, item);\n this._pendingRemoves.delete(item.id);\n }\n }\n\n // Removed: in prev but not in current\n for (const item of prev) {\n if (!currentMap.has(item.id)) {\n this._pendingRemoves.add(item.id);\n this._pendingWrites.delete(item.id);\n }\n }\n }\n\n private _hasPending(): boolean {\n return this._pendingClear || this._pendingWrites.size > 0 || this._pendingRemoves.size > 0;\n }\n\n // ── Private: debounce + flush ──\n\n private _scheduleSave(): void {\n this._cancelSave();\n const delay = (this.constructor as typeof PersistentCollection).WRITE_DELAY;\n if (delay <= 0) {\n this._doFlush();\n return;\n }\n this._flushTimer = setTimeout(() => this._doFlush(), delay);\n }\n\n private _cancelSave(): void {\n if (this._flushTimer !== null) {\n clearTimeout(this._flushTimer);\n this._flushTimer = null;\n }\n }\n\n private _doFlush(): void {\n if (!this._hasPending()) return;\n try {\n const result = this._flush();\n if (result instanceof Promise) {\n result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n private _flush(): void | Promise<void> {\n const doClear = this._pendingClear;\n const writes = this._pendingWrites.size > 0 ? [...this._pendingWrites.values()] : null;\n const removes = this._pendingRemoves.size > 0 ? [...this._pendingRemoves] : null;\n\n // Clear queues\n this._pendingClear = false;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n\n if (doClear) {\n const clearResult = this.persistClear();\n if (clearResult instanceof Promise) {\n return clearResult.then(() => {\n if (writes) return this.persistSet(writes);\n });\n }\n if (writes) {\n return this.persistSet(writes);\n }\n return;\n }\n\n // Non-clear: removes then writes\n if (removes) {\n const removeResult = this.persistRemove(removes);\n if (removeResult instanceof Promise) {\n return removeResult.then(() => {\n if (writes) return this.persistSet(writes);\n });\n }\n }\n if (writes) {\n return this.persistSet(writes);\n }\n }\n\n // ── Private: error handling ──\n\n private _handlePersistError(err: unknown): void {\n if (this.onPersistError) {\n this.onPersistError(err);\n return;\n }\n if (__DEV__) {\n console.warn('[mvc-kit] Storage error:', err);\n }\n }\n}\n"],"names":[],"mappings":"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"}
|