@yagejs/save 0.5.0 → 0.6.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/README.md +72 -19
- package/dist/index.cjs +414 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -19
- package/dist/index.d.ts +163 -19
- package/dist/index.js +391 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,11 +3,360 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { VERSION } from "@yagejs/core";
|
|
6
|
+
import {
|
|
7
|
+
defineStore,
|
|
8
|
+
defineSet,
|
|
9
|
+
defineMap,
|
|
10
|
+
defineCounter,
|
|
11
|
+
jsonCodec,
|
|
12
|
+
setCodec,
|
|
13
|
+
mapCodec,
|
|
14
|
+
dateCodec,
|
|
15
|
+
StoreVersionTooNewError,
|
|
16
|
+
StoreMigrationMissingError
|
|
17
|
+
} from "@yagejs/core";
|
|
18
|
+
|
|
19
|
+
// src/Save.ts
|
|
20
|
+
var SlotNotFoundError = class extends Error {
|
|
21
|
+
static {
|
|
22
|
+
__name(this, "SlotNotFoundError");
|
|
23
|
+
}
|
|
24
|
+
storeId;
|
|
25
|
+
slot;
|
|
26
|
+
constructor(storeId, slot) {
|
|
27
|
+
super(`No save found for store "${storeId}" in slot "${slot}".`);
|
|
28
|
+
this.name = "SlotNotFoundError";
|
|
29
|
+
this.storeId = storeId;
|
|
30
|
+
this.slot = slot;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var InvalidKeyError = class extends Error {
|
|
34
|
+
static {
|
|
35
|
+
__name(this, "InvalidKeyError");
|
|
36
|
+
}
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "InvalidKeyError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var SEP = "/";
|
|
43
|
+
var DOC_TAG = "d";
|
|
44
|
+
var SLOT_TAG = "s";
|
|
45
|
+
var MANIFEST_TAG = "m";
|
|
46
|
+
function validateStoreId(id) {
|
|
47
|
+
if (id.length === 0) {
|
|
48
|
+
throw new InvalidKeyError("Save: store id must be non-empty.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
__name(validateStoreId, "validateStoreId");
|
|
52
|
+
function validateSlotName(slot) {
|
|
53
|
+
if (slot.length === 0) {
|
|
54
|
+
throw new InvalidKeyError("Save: slot name must be non-empty.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
__name(validateSlotName, "validateSlotName");
|
|
58
|
+
function docKey(id) {
|
|
59
|
+
validateStoreId(id);
|
|
60
|
+
return `${encodeURIComponent(id)}${SEP}${DOC_TAG}`;
|
|
61
|
+
}
|
|
62
|
+
__name(docKey, "docKey");
|
|
63
|
+
function slotKey(id, slot) {
|
|
64
|
+
validateStoreId(id);
|
|
65
|
+
validateSlotName(slot);
|
|
66
|
+
return `${encodeURIComponent(id)}${SEP}${SLOT_TAG}${SEP}${encodeURIComponent(slot)}`;
|
|
67
|
+
}
|
|
68
|
+
__name(slotKey, "slotKey");
|
|
69
|
+
function manifestKey(id) {
|
|
70
|
+
validateStoreId(id);
|
|
71
|
+
return `${encodeURIComponent(id)}${SEP}${MANIFEST_TAG}`;
|
|
72
|
+
}
|
|
73
|
+
__name(manifestKey, "manifestKey");
|
|
74
|
+
async function readManifest(adapter, id) {
|
|
75
|
+
const raw = await adapter.read(manifestKey(id));
|
|
76
|
+
if (raw == null) return { version: 1, slots: {} };
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(raw);
|
|
79
|
+
if (parsed && typeof parsed === "object" && parsed.slots) {
|
|
80
|
+
return { version: 1, slots: parsed.slots };
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
return { version: 1, slots: {} };
|
|
85
|
+
}
|
|
86
|
+
__name(readManifest, "readManifest");
|
|
87
|
+
async function writeManifest(adapter, id, manifest) {
|
|
88
|
+
await adapter.write(manifestKey(id), JSON.stringify(manifest));
|
|
89
|
+
}
|
|
90
|
+
__name(writeManifest, "writeManifest");
|
|
91
|
+
var Save = class {
|
|
92
|
+
static {
|
|
93
|
+
__name(this, "Save");
|
|
94
|
+
}
|
|
95
|
+
adapter;
|
|
96
|
+
/**
|
|
97
|
+
* Per-store manifest update queue. Two concurrent saveSlot/deleteSlot calls
|
|
98
|
+
* against the same store would otherwise read-modify-write the manifest
|
|
99
|
+
* blindly — the later writer wins and the earlier change is silently lost.
|
|
100
|
+
* Funnelling manifest mutations through a per-store promise chain serializes
|
|
101
|
+
* them while leaving slot data writes (which target distinct keys)
|
|
102
|
+
* unaffected. Per-store, not global, because manifests for different stores
|
|
103
|
+
* never collide.
|
|
104
|
+
*/
|
|
105
|
+
manifestQueues = /* @__PURE__ */ new Map();
|
|
106
|
+
constructor(opts) {
|
|
107
|
+
this.adapter = opts.adapter;
|
|
108
|
+
}
|
|
109
|
+
/** Persist the store as an unslotted document. */
|
|
110
|
+
async persist(store) {
|
|
111
|
+
const payload = store.serialize();
|
|
112
|
+
await this.adapter.write(docKey(store.id), JSON.stringify(payload));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Restore an unslotted document into the store. No-op when the document
|
|
116
|
+
* doesn't exist — the store keeps its current (default) value.
|
|
117
|
+
*/
|
|
118
|
+
async restore(store) {
|
|
119
|
+
const raw = await this.adapter.read(docKey(store.id));
|
|
120
|
+
if (raw == null) return;
|
|
121
|
+
store.hydrate(JSON.parse(raw));
|
|
122
|
+
}
|
|
123
|
+
/** Restore many stores in parallel. */
|
|
124
|
+
async restoreAll(stores) {
|
|
125
|
+
await Promise.all(stores.map((s) => this.restore(s)));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Save the store into a named slot. The slot manifest is updated with the
|
|
129
|
+
* timestamp and optional metadata.
|
|
130
|
+
*/
|
|
131
|
+
async saveSlot(store, slot, opts) {
|
|
132
|
+
const payload = store.serialize();
|
|
133
|
+
await this.adapter.write(slotKey(store.id, slot), JSON.stringify(payload));
|
|
134
|
+
const entry = {
|
|
135
|
+
name: slot,
|
|
136
|
+
savedAt: Date.now()
|
|
137
|
+
};
|
|
138
|
+
if (opts?.metadata !== void 0) entry.metadata = opts.metadata;
|
|
139
|
+
await this.updateManifest(store.id, (manifest) => {
|
|
140
|
+
manifest.slots[slot] = entry;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** Load a slot into the store. Throws `SlotNotFoundError` when missing. */
|
|
144
|
+
async loadSlot(store, slot) {
|
|
145
|
+
const raw = await this.adapter.read(slotKey(store.id, slot));
|
|
146
|
+
if (raw == null) throw new SlotNotFoundError(store.id, slot);
|
|
147
|
+
store.hydrate(JSON.parse(raw));
|
|
148
|
+
}
|
|
149
|
+
/** List slots for a store, optionally filtered by prefix. */
|
|
150
|
+
async listSlots(store, opts) {
|
|
151
|
+
const manifest = await readManifest(this.adapter, store.id);
|
|
152
|
+
const entries = Object.values(manifest.slots);
|
|
153
|
+
const filtered = opts?.prefix !== void 0 ? entries.filter((e) => e.name.startsWith(opts.prefix)) : entries;
|
|
154
|
+
return filtered.map((e) => {
|
|
155
|
+
const info = { name: e.name, savedAt: e.savedAt };
|
|
156
|
+
if (e.metadata !== void 0) info.metadata = e.metadata;
|
|
157
|
+
return info;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** Delete a slot. No-op when the slot doesn't exist. */
|
|
161
|
+
async deleteSlot(store, slot) {
|
|
162
|
+
await this.adapter.delete(slotKey(store.id, slot));
|
|
163
|
+
await this.updateManifest(store.id, (manifest) => {
|
|
164
|
+
if (!(slot in manifest.slots)) return;
|
|
165
|
+
const next = {};
|
|
166
|
+
for (const [name, entry] of Object.entries(manifest.slots)) {
|
|
167
|
+
if (name !== slot) next[name] = entry;
|
|
168
|
+
}
|
|
169
|
+
manifest.slots = next;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Read-modify-write the manifest for a store, serialized through the
|
|
174
|
+
* per-store queue. The mutator runs on the freshly-read manifest; if it's
|
|
175
|
+
* a no-op the write is skipped (the mutator can opt out by leaving the
|
|
176
|
+
* passed manifest unchanged — but we always write back to keep the contract
|
|
177
|
+
* predictable; a no-op write is cheap).
|
|
178
|
+
*/
|
|
179
|
+
updateManifest(storeId, mutate) {
|
|
180
|
+
const prev = this.manifestQueues.get(storeId) ?? Promise.resolve();
|
|
181
|
+
const next = prev.then(async () => {
|
|
182
|
+
const manifest = await readManifest(this.adapter, storeId);
|
|
183
|
+
mutate(manifest);
|
|
184
|
+
await writeManifest(this.adapter, storeId, manifest);
|
|
185
|
+
});
|
|
186
|
+
this.manifestQueues.set(
|
|
187
|
+
storeId,
|
|
188
|
+
next.catch(() => void 0)
|
|
189
|
+
);
|
|
190
|
+
return next;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Subscribe to the store and persist on every change, coalesced to a single
|
|
194
|
+
* in-flight write per store. Returns a stop function — call it to
|
|
195
|
+
* unsubscribe.
|
|
196
|
+
*
|
|
197
|
+
* Writes are serialized: while a `persist()` is in flight, further changes
|
|
198
|
+
* mark the store dirty and trigger one more write *after* the current one
|
|
199
|
+
* resolves. The last-set state always wins, even on a slow async adapter,
|
|
200
|
+
* because each flush re-reads `store.serialize()` rather than capturing the
|
|
201
|
+
* value at scheduling time. Multiple synchronous `set` calls collapse into
|
|
202
|
+
* one write because the dirty flag is consumed atomically.
|
|
203
|
+
*
|
|
204
|
+
* `setTimeout` is intentionally not used here: `Save` runs alongside the
|
|
205
|
+
* page lifecycle, not the engine loop, and may be active before the engine
|
|
206
|
+
* starts or after it stops (e.g. settings menus on a paused game). Using
|
|
207
|
+
* engine-time processes here would tie persistence to a running scheduler.
|
|
208
|
+
*/
|
|
209
|
+
autoPersist(store) {
|
|
210
|
+
let inFlight = false;
|
|
211
|
+
let dirty = false;
|
|
212
|
+
let stopped = false;
|
|
213
|
+
const flush = /* @__PURE__ */ __name(async () => {
|
|
214
|
+
while (dirty && !stopped) {
|
|
215
|
+
dirty = false;
|
|
216
|
+
try {
|
|
217
|
+
await this.persist(store);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error(
|
|
220
|
+
`autoPersist: failed to persist store "${store.id}":`,
|
|
221
|
+
err
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
inFlight = false;
|
|
226
|
+
}, "flush");
|
|
227
|
+
const off = store.subscribe(() => {
|
|
228
|
+
if (stopped) return;
|
|
229
|
+
dirty = true;
|
|
230
|
+
if (inFlight) return;
|
|
231
|
+
inFlight = true;
|
|
232
|
+
queueMicrotask(() => {
|
|
233
|
+
if (stopped) {
|
|
234
|
+
inFlight = false;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
void flush();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
return () => {
|
|
241
|
+
stopped = true;
|
|
242
|
+
off();
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
function createSave(opts) {
|
|
247
|
+
return new Save(opts);
|
|
248
|
+
}
|
|
249
|
+
__name(createSave, "createSave");
|
|
6
250
|
|
|
7
|
-
// src/
|
|
8
|
-
|
|
251
|
+
// src/keys.ts
|
|
252
|
+
import { ServiceKey } from "@yagejs/core";
|
|
253
|
+
var SaveServiceKey = new ServiceKey("save");
|
|
254
|
+
|
|
255
|
+
// src/SavePlugin.ts
|
|
256
|
+
var SavePlugin = class {
|
|
9
257
|
static {
|
|
10
|
-
__name(this, "
|
|
258
|
+
__name(this, "SavePlugin");
|
|
259
|
+
}
|
|
260
|
+
name = "save";
|
|
261
|
+
version = "1.0.0";
|
|
262
|
+
options;
|
|
263
|
+
constructor(options) {
|
|
264
|
+
this.options = options;
|
|
265
|
+
}
|
|
266
|
+
install(context) {
|
|
267
|
+
context.register(SaveServiceKey, this.options.save);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// src/adapters/memory.ts
|
|
272
|
+
function memoryAdapter() {
|
|
273
|
+
const data = /* @__PURE__ */ new Map();
|
|
274
|
+
return {
|
|
275
|
+
async read(key) {
|
|
276
|
+
return data.get(key) ?? null;
|
|
277
|
+
},
|
|
278
|
+
async write(key, value) {
|
|
279
|
+
data.set(key, value);
|
|
280
|
+
},
|
|
281
|
+
async delete(key) {
|
|
282
|
+
data.delete(key);
|
|
283
|
+
},
|
|
284
|
+
async list(prefix) {
|
|
285
|
+
const out = [];
|
|
286
|
+
for (const k of data.keys()) {
|
|
287
|
+
if (k.startsWith(prefix)) out.push(k);
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
__name(memoryAdapter, "memoryAdapter");
|
|
294
|
+
|
|
295
|
+
// src/adapters/localStorage.ts
|
|
296
|
+
function localStorageAdapter(opts = {}) {
|
|
297
|
+
const namespace = opts.namespace ?? "yage";
|
|
298
|
+
const prefix = `${namespace}:`;
|
|
299
|
+
const ls = /* @__PURE__ */ __name(() => {
|
|
300
|
+
if (typeof window === "undefined") {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"localStorageAdapter: window is not available in this environment."
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const storage = window.localStorage;
|
|
307
|
+
if (!storage) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
"localStorageAdapter: window.localStorage is not available in this environment."
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return storage;
|
|
313
|
+
} catch (err) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
"localStorageAdapter: window.localStorage is not available in this environment.",
|
|
316
|
+
{ cause: err }
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}, "ls");
|
|
320
|
+
return {
|
|
321
|
+
async read(key) {
|
|
322
|
+
return ls().getItem(prefix + key);
|
|
323
|
+
},
|
|
324
|
+
async write(key, value) {
|
|
325
|
+
try {
|
|
326
|
+
ls().setItem(prefix + key, value);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
if (err instanceof DOMException && (err.name === "QuotaExceededError" || err.name === "NS_ERROR_DOM_QUOTA_REACHED")) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`localStorageAdapter: quota exceeded while writing "${prefix + key}". Consider deleting old slots or using a different adapter.`,
|
|
331
|
+
{ cause: err }
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
async delete(key) {
|
|
338
|
+
ls().removeItem(prefix + key);
|
|
339
|
+
},
|
|
340
|
+
async list(keyPrefix) {
|
|
341
|
+
const storage = ls();
|
|
342
|
+
const full = prefix + keyPrefix;
|
|
343
|
+
const out = [];
|
|
344
|
+
for (let i = 0; i < storage.length; i += 1) {
|
|
345
|
+
const k = storage.key(i);
|
|
346
|
+
if (k != null && k.startsWith(full)) {
|
|
347
|
+
out.push(k.slice(prefix.length));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
__name(localStorageAdapter, "localStorageAdapter");
|
|
355
|
+
|
|
356
|
+
// src/snapshot/LocalStorageSnapshotStorage.ts
|
|
357
|
+
var LocalStorageSnapshotStorage = class {
|
|
358
|
+
static {
|
|
359
|
+
__name(this, "LocalStorageSnapshotStorage");
|
|
11
360
|
}
|
|
12
361
|
load(key) {
|
|
13
362
|
return localStorage.getItem(key);
|
|
@@ -30,7 +379,7 @@ var LocalStorageSaveStorage = class {
|
|
|
30
379
|
}
|
|
31
380
|
};
|
|
32
381
|
|
|
33
|
-
// src/
|
|
382
|
+
// src/snapshot/SnapshotService.ts
|
|
34
383
|
import {
|
|
35
384
|
SceneManagerKey,
|
|
36
385
|
SerializableRegistry,
|
|
@@ -50,9 +399,9 @@ var COMPONENT_ORDER = [
|
|
|
50
399
|
"ParticleEmitterComponent",
|
|
51
400
|
"TilemapComponent"
|
|
52
401
|
];
|
|
53
|
-
var
|
|
402
|
+
var SnapshotService = class {
|
|
54
403
|
static {
|
|
55
|
-
__name(this, "
|
|
404
|
+
__name(this, "SnapshotService");
|
|
56
405
|
}
|
|
57
406
|
storage;
|
|
58
407
|
context;
|
|
@@ -201,7 +550,7 @@ var SaveService = class {
|
|
|
201
550
|
}
|
|
202
551
|
} catch (err) {
|
|
203
552
|
console.error(
|
|
204
|
-
`
|
|
553
|
+
`SnapshotService: contributor "${key}" serialize failed; omitting its extras from this snapshot.`,
|
|
205
554
|
err
|
|
206
555
|
);
|
|
207
556
|
}
|
|
@@ -248,7 +597,7 @@ var SaveService = class {
|
|
|
248
597
|
await contributor.restore(extras[key]);
|
|
249
598
|
} catch (err) {
|
|
250
599
|
console.error(
|
|
251
|
-
`
|
|
600
|
+
`SnapshotService: contributor "${key}" restore failed; continuing.`,
|
|
252
601
|
err
|
|
253
602
|
);
|
|
254
603
|
}
|
|
@@ -256,7 +605,7 @@ var SaveService = class {
|
|
|
256
605
|
for (const key of Object.keys(extras)) {
|
|
257
606
|
if (!this.contributors.has(key)) {
|
|
258
607
|
console.warn(
|
|
259
|
-
`
|
|
608
|
+
`SnapshotService: snapshot contains extras for "${key}" but no contributor is registered \u2014 skipping.`
|
|
260
609
|
);
|
|
261
610
|
}
|
|
262
611
|
}
|
|
@@ -362,32 +711,52 @@ var SaveService = class {
|
|
|
362
711
|
}
|
|
363
712
|
};
|
|
364
713
|
|
|
365
|
-
// src/keys.ts
|
|
366
|
-
import { ServiceKey } from "@yagejs/core";
|
|
367
|
-
var
|
|
714
|
+
// src/snapshot/keys.ts
|
|
715
|
+
import { ServiceKey as ServiceKey2 } from "@yagejs/core";
|
|
716
|
+
var SnapshotServiceKey = new ServiceKey2(
|
|
717
|
+
"snapshotService"
|
|
718
|
+
);
|
|
368
719
|
|
|
369
|
-
// src/
|
|
370
|
-
var
|
|
720
|
+
// src/snapshot/SnapshotPlugin.ts
|
|
721
|
+
var SnapshotPlugin = class {
|
|
371
722
|
static {
|
|
372
|
-
__name(this, "
|
|
723
|
+
__name(this, "SnapshotPlugin");
|
|
373
724
|
}
|
|
374
|
-
name = "
|
|
725
|
+
name = "snapshot";
|
|
375
726
|
version = "1.0.0";
|
|
376
727
|
options;
|
|
377
728
|
constructor(options) {
|
|
378
729
|
this.options = options ?? {};
|
|
379
730
|
}
|
|
380
731
|
install(context) {
|
|
381
|
-
const storage = this.options.storage ?? new
|
|
382
|
-
const service = new
|
|
383
|
-
context.register(
|
|
732
|
+
const storage = this.options.storage ?? new LocalStorageSnapshotStorage();
|
|
733
|
+
const service = new SnapshotService(storage, context, this.options.namespace);
|
|
734
|
+
context.register(SnapshotServiceKey, service);
|
|
384
735
|
}
|
|
385
736
|
};
|
|
386
737
|
export {
|
|
387
|
-
|
|
738
|
+
InvalidKeyError,
|
|
739
|
+
LocalStorageSnapshotStorage,
|
|
740
|
+
Save,
|
|
388
741
|
SavePlugin,
|
|
389
|
-
SaveService,
|
|
390
742
|
SaveServiceKey,
|
|
391
|
-
|
|
743
|
+
SlotNotFoundError,
|
|
744
|
+
SnapshotPlugin,
|
|
745
|
+
SnapshotService,
|
|
746
|
+
SnapshotServiceKey,
|
|
747
|
+
StoreMigrationMissingError,
|
|
748
|
+
StoreVersionTooNewError,
|
|
749
|
+
VERSION,
|
|
750
|
+
createSave,
|
|
751
|
+
dateCodec,
|
|
752
|
+
defineCounter,
|
|
753
|
+
defineMap,
|
|
754
|
+
defineSet,
|
|
755
|
+
defineStore,
|
|
756
|
+
jsonCodec,
|
|
757
|
+
localStorageAdapter,
|
|
758
|
+
mapCodec,
|
|
759
|
+
memoryAdapter,
|
|
760
|
+
setCodec
|
|
392
761
|
};
|
|
393
762
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/LocalStorageAdapter.ts","../src/SaveService.ts","../src/keys.ts","../src/SavePlugin.ts"],"sourcesContent":["export { VERSION } from \"@yagejs/core\";\n\n// Types\nexport type {\n SaveStorage,\n UntypedSlots,\n GameSnapshot,\n SceneSnapshotEntry,\n EntitySnapshotEntry,\n ComponentSnapshot,\n SnapshotContributor,\n} from \"./types.js\";\nexport type { SnapshotResolver } from \"@yagejs/core\";\n\n// Storage\nexport { LocalStorageSaveStorage } from \"./LocalStorageAdapter.js\";\n\n// Service\nexport { SaveService } from \"./SaveService.js\";\nexport { SavePlugin } from \"./SavePlugin.js\";\nexport type { SavePluginOptions } from \"./SavePlugin.js\";\nexport { SaveServiceKey } from \"./keys.js\";\n","import type { SaveStorage } from \"./types.js\";\n\n/** SaveStorage backed by browser localStorage. */\nexport class LocalStorageSaveStorage implements SaveStorage {\n load(key: string): string | null {\n return localStorage.getItem(key);\n }\n\n save(key: string, data: string): void {\n localStorage.setItem(key, data);\n }\n\n delete(key: string): void {\n localStorage.removeItem(key);\n }\n\n list(prefix?: string): string[] {\n const result: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key !== null && (!prefix || key.startsWith(prefix))) {\n result.push(key);\n }\n }\n return result;\n }\n}\n","import type { Scene, Entity, Component, SnapshotResolver } from \"@yagejs/core\";\nimport {\n SceneManagerKey,\n SerializableRegistry,\n isSerializable,\n getSerializableType,\n} from \"@yagejs/core\";\nimport type { EngineContext } from \"@yagejs/core\";\nimport type {\n SaveStorage,\n UntypedSlots,\n GameSnapshot,\n SceneSnapshotEntry,\n EntitySnapshotEntry,\n ComponentSnapshot,\n SnapshotContributor,\n} from \"./types.js\";\n\n/** Current snapshot format version. */\nconst SNAPSHOT_VERSION = 4;\n\n/**\n * Component restoration priority. Components listed here are added first\n * (in order) to satisfy onAdd() dependencies.\n */\nconst COMPONENT_ORDER = [\n \"Transform\",\n \"RigidBodyComponent\",\n \"ColliderComponent\",\n \"SpriteComponent\",\n \"GraphicsComponent\",\n \"AnimatedSpriteComponent\",\n \"AnimationController\",\n \"SoundComponent\",\n \"ParticleEmitterComponent\",\n \"TilemapComponent\",\n];\n\n/** Orchestrates full game-state serialization and hydration. */\nexport class SaveService<TSlots extends UntypedSlots = UntypedSlots> {\n private readonly storage: SaveStorage;\n private readonly context: EngineContext;\n private readonly namespace: string;\n private readonly contributors = new Map<string, SnapshotContributor>();\n private _loading = false;\n\n constructor(storage: SaveStorage, context: EngineContext, namespace = \"yage\") {\n this.storage = storage;\n this.context = context;\n this.namespace = namespace;\n }\n\n // ---- Extension API ----\n\n /**\n * Register a plugin to contribute extra data to every snapshot under\n * `key`. The contributor's `serialize()` is invoked during `saveSnapshot`,\n * and its `restore(data)` runs after every scene + entity in the snapshot\n * has been hydrated. Re-registering an existing key replaces the previous\n * contributor.\n */\n registerSnapshotExtra(key: string, contributor: SnapshotContributor): void {\n this.contributors.set(key, contributor);\n }\n\n /** Remove a previously registered contributor. */\n unregisterSnapshotExtra(key: string): void {\n this.contributors.delete(key);\n }\n\n // ---- Snapshot API ----\n\n /** Save a snapshot of the current scene stack to the given slot. */\n saveSnapshot(slot: string): void {\n const snapshot = this.buildSnapshot();\n this.storage.save(\n this.key(\"snapshot\", slot),\n JSON.stringify(snapshot),\n );\n }\n\n /** Load a snapshot from the given slot, rebuilding the scene stack. */\n async loadSnapshot(slot: string): Promise<void> {\n const snapshot = this.readSnapshot(slot);\n if (!snapshot) {\n throw new Error(`No save found in slot \"${slot}\".`);\n }\n await this.hydrateSnapshot(snapshot);\n }\n\n /** Export a previously saved snapshot from the given slot. */\n exportSnapshot(slot: string): GameSnapshot | null {\n return this.readSnapshot(slot);\n }\n\n /** Import a snapshot into the given slot and hydrate the scene stack. */\n async importSnapshot(slot: string, snapshot: GameSnapshot): Promise<void> {\n if (this._loading) {\n throw new Error(\"loadSnapshot already in progress.\");\n }\n if (snapshot.version !== SNAPSHOT_VERSION) {\n throw new Error(\n `Save version mismatch: expected ${SNAPSHOT_VERSION}, got ${snapshot.version}.`,\n );\n }\n this.storage.save(\n this.key(\"snapshot\", slot),\n JSON.stringify(snapshot),\n );\n await this.hydrateSnapshot(snapshot);\n }\n\n // ---- User Data API ----\n\n /** Save arbitrary structured data to a named slot. */\n saveData<K extends keyof TSlots & string>(slot: K, data: TSlots[K]): void {\n this.storage.save(this.key(\"data\", slot), JSON.stringify(data));\n }\n\n /** Load structured data from a named slot. Returns null if not found. */\n loadData<K extends keyof TSlots & string>(slot: K): TSlots[K] | null {\n const raw = this.storage.load(this.key(\"data\", slot));\n if (raw === null) return null;\n try {\n return JSON.parse(raw) as TSlots[K];\n } catch {\n return null;\n }\n }\n\n /** Read data from a slot for external use (cloud upload, file export). Alias for `loadData`. */\n exportData<K extends keyof TSlots & string>(slot: K): TSlots[K] | null {\n return this.loadData(slot);\n }\n\n /** Write externally-sourced data into a slot. Alias for `saveData` — no version check or hydration. */\n importData<K extends keyof TSlots & string>(slot: K, data: TSlots[K]): void {\n this.saveData(slot, data);\n }\n\n // ---- Snapshot management ----\n\n /** Check if a snapshot exists in the given slot. */\n hasSnapshot(slot: string): boolean {\n return this.storage.load(this.key(\"snapshot\", slot)) !== null;\n }\n\n /** Delete a snapshot from the given slot. */\n deleteSnapshot(slot: string): void {\n this.storage.delete(this.key(\"snapshot\", slot));\n }\n\n // ---- Data management ----\n\n /** Check if user data exists in the given slot. */\n hasData<K extends keyof TSlots & string>(slot: K): boolean {\n return this.storage.load(this.key(\"data\", slot)) !== null;\n }\n\n /** Delete user data from the given slot. */\n deleteData<K extends keyof TSlots & string>(slot: K): void {\n this.storage.delete(this.key(\"data\", slot));\n }\n\n // ---- Private helpers ----\n\n private key(prefix: string, slot: string): string {\n return `${this.namespace}:${prefix}:${slot}`;\n }\n\n private readSnapshot(slot: string): GameSnapshot | null {\n const raw = this.storage.load(this.key(\"snapshot\", slot));\n if (raw === null) return null;\n try {\n return JSON.parse(raw) as GameSnapshot;\n } catch {\n return null;\n }\n }\n\n private buildSnapshot(): GameSnapshot {\n const sceneManager = this.context.resolve(SceneManagerKey);\n const scenes: SceneSnapshotEntry[] = [];\n\n for (const scene of sceneManager.all) {\n if (!isSerializable(scene)) continue;\n const type = getSerializableType(scene);\n if (!type) continue;\n\n const entities: EntitySnapshotEntry[] = [];\n for (const entity of scene.getEntities()) {\n if (!isSerializable(entity)) continue;\n entities.push(this.serializeEntity(entity));\n }\n\n const userData = scene.serialize?.();\n\n scenes.push({\n type,\n paused: scene.paused,\n entities,\n userData,\n });\n }\n\n const extras: Record<string, unknown> = {};\n let hasExtras = false;\n for (const [key, contributor] of this.contributors) {\n // Isolate each contributor — a single failing one (e.g. a third-party\n // plugin throwing during serialize) shouldn't poison the rest of the\n // snapshot. Log and continue.\n try {\n const data = contributor.serialize();\n if (data !== undefined) {\n extras[key] = data;\n hasExtras = true;\n }\n } catch (err) {\n console.error(\n `SaveService: contributor \"${key}\" serialize failed; ` +\n `omitting its extras from this snapshot.`,\n err,\n );\n }\n }\n\n return {\n version: SNAPSHOT_VERSION,\n timestamp: Date.now(),\n scenes,\n ...(hasExtras ? { extras } : {}),\n };\n }\n\n private async hydrateSnapshot(snapshot: GameSnapshot): Promise<void> {\n if (this._loading) {\n throw new Error(\"loadSnapshot already in progress.\");\n }\n this._loading = true;\n try {\n if (snapshot.version !== SNAPSHOT_VERSION) {\n throw new Error(\n `Save version mismatch: expected ${SNAPSHOT_VERSION}, got ${snapshot.version}.`,\n );\n }\n\n const sceneManager = this.context.resolve(SceneManagerKey);\n await sceneManager.popAll();\n\n for (const entry of snapshot.scenes) {\n const SceneClass = SerializableRegistry.get(entry.type) as\n | (new () => Scene)\n | undefined;\n if (!SceneClass) {\n throw new Error(\n `Cannot load scene type \"${entry.type}\". ` +\n `Ensure the scene class is decorated with @serializable.`,\n );\n }\n\n const scene = new SceneClass();\n\n // Instance-patch onEnter: restore entities + call afterRestore instead\n scene.onEnter = () => {\n this.restoreSceneEntities(scene, entry);\n };\n\n await sceneManager.push(scene);\n\n if (entry.paused) {\n scene.paused = true;\n }\n }\n\n // Run snapshot contributors after every scene is pushed and entities\n // restored. By this point any layer/scene render trees the renderer\n // contributor needs to find by name are live, and component-scope\n // afterRestore hooks have run, so contributors see consistent state.\n //\n // Iterate every REGISTERED contributor — not just keys present in\n // `extras` — so contributors get a chance to clear stale baseline\n // state (e.g. a screen-scope vignette enabled after the save) when\n // the snapshot has no entry for them. Contributors must treat\n // `data === undefined` as \"reset to empty\".\n const extras = snapshot.extras ?? {};\n for (const [key, contributor] of this.contributors) {\n // Isolate each contributor — one bad restore (e.g. a third-party\n // plugin choking on a partially-compatible payload) shouldn't abort\n // the whole load and leave the engine in a half-restored state.\n try {\n await contributor.restore(extras[key]);\n } catch (err) {\n console.error(\n `SaveService: contributor \"${key}\" restore failed; continuing.`,\n err,\n );\n }\n }\n for (const key of Object.keys(extras)) {\n if (!this.contributors.has(key)) {\n console.warn(\n `SaveService: snapshot contains extras for \"${key}\" but no ` +\n `contributor is registered — skipping.`,\n );\n }\n }\n } finally {\n this._loading = false;\n }\n }\n\n private restoreSceneEntities(\n scene: Scene,\n entry: SceneSnapshotEntry,\n ): void {\n // Phase 1: Create all entities, add to scene, add components\n const idMap = new Map<number, Entity>();\n const entityEntries: Array<{\n entity: Entity;\n entry: EntitySnapshotEntry;\n restoredComponents: Array<{ component: Component; data: unknown }>;\n }> = [];\n\n for (const entityEntry of entry.entities) {\n const EntityClass = SerializableRegistry.get(entityEntry.type) as\n | (new () => Entity)\n | undefined;\n if (!EntityClass) {\n console.warn(\n `Entity type \"${entityEntry.type}\" not found in registry — skipping.`,\n );\n continue;\n }\n\n const entity = new EntityClass();\n scene._addExistingEntity(entity);\n const restoredComponents = this.restoreEntityComponents(\n entity,\n entityEntry.components,\n );\n\n idMap.set(entityEntry.id, entity);\n entityEntries.push({ entity, entry: entityEntry, restoredComponents });\n }\n\n // Phase 2: Rewire parent/child relationships\n for (const { entity, entry: entityEntry } of entityEntries) {\n if (entityEntry.parentId != null && entityEntry.childName != null) {\n const parent = idMap.get(entityEntry.parentId);\n if (parent) {\n parent.addChild(entityEntry.childName, entity);\n } else {\n console.warn(\n `Parent entity (saved id ${entityEntry.parentId}) not found for child \"${entity.name}\" — restoring as root entity.`,\n );\n }\n }\n }\n\n // Build resolver for afterRestore hooks\n const resolver: SnapshotResolver = {\n entity(savedId: number) {\n return idMap.get(savedId) ?? null;\n },\n };\n\n // Phase 3: afterRestore hooks (components, then entities, then scene)\n for (const { entity, entry: entityEntry, restoredComponents } of entityEntries) {\n for (const { component, data } of restoredComponents) {\n component.afterRestore?.(data, resolver);\n }\n\n entity.afterRestore?.(entityEntry.userData, resolver);\n }\n\n scene.afterRestore?.(entry.userData, resolver);\n }\n\n private serializeEntity(entity: Entity): EntitySnapshotEntry {\n const type = getSerializableType(entity);\n if (!type) throw new Error(\"Entity is not serializable\");\n\n const components: ComponentSnapshot[] = [];\n for (const component of entity.getAll()) {\n if (typeof component.serialize !== \"function\") continue;\n const data = component.serialize();\n if (data == null) continue;\n const compType =\n getSerializableType(component) ?? component.constructor.name;\n components.push({ type: compType, data });\n }\n\n const userData = entity.serialize?.();\n\n const result: EntitySnapshotEntry = {\n id: entity.id,\n type,\n components,\n userData,\n };\n\n // Capture parent/child relationship\n if (entity.parent && isSerializable(entity.parent)) {\n result.parentId = entity.parent.id;\n // Find the name this entity is registered under\n for (const [name, child] of entity.parent.children) {\n if (child === entity) {\n result.childName = name;\n break;\n }\n }\n } else if (entity.parent) {\n console.warn(\n `Entity \"${entity.name}\" has non-serializable parent \"${entity.parent.name}\" — parent/child relationship will not be saved.`,\n );\n }\n\n return result;\n }\n\n private restoreEntityComponents(\n entity: Entity,\n snapshots: ComponentSnapshot[],\n ): Array<{ component: Component; data: unknown }> {\n const sorted = [...snapshots].sort((a, b) => {\n const ai = COMPONENT_ORDER.indexOf(a.type);\n const bi = COMPONENT_ORDER.indexOf(b.type);\n return (ai >= 0 ? ai : 999) - (bi >= 0 ? bi : 999);\n });\n\n const restored: Array<{ component: Component; data: unknown }> = [];\n\n for (const snap of sorted) {\n const CompClass = SerializableRegistry.get(snap.type) as\n | ({ fromSnapshot?(data: unknown): Component } & (new (\n ...args: unknown[]\n ) => Component))\n | undefined;\n\n if (!CompClass || typeof CompClass.fromSnapshot !== \"function\") {\n // Not in registry or no fromSnapshot — entity handles in afterRestore\n continue;\n }\n\n const component = CompClass.fromSnapshot(snap.data);\n entity.add(component);\n restored.push({ component, data: snap.data });\n }\n\n return restored;\n }\n}\n","import { ServiceKey } from \"@yagejs/core\";\nimport type { SaveService } from \"./SaveService.js\";\n\n/** Service key for the SaveService. */\nexport const SaveServiceKey = new ServiceKey<SaveService>(\"saveService\");\n","import type { EngineContext, Plugin } from \"@yagejs/core\";\nimport type { SaveStorage } from \"./types.js\";\nimport { LocalStorageSaveStorage } from \"./LocalStorageAdapter.js\";\nimport { SaveService } from \"./SaveService.js\";\nimport { SaveServiceKey } from \"./keys.js\";\n\n/** Options for the SavePlugin. */\nexport interface SavePluginOptions {\n /** Custom storage backend. Defaults to LocalStorageSaveStorage. */\n storage?: SaveStorage;\n /** Namespace for stored keys. Defaults to \"yage\". */\n namespace?: string;\n}\n\n/** Plugin that registers SaveService into the engine context. */\nexport class SavePlugin implements Plugin {\n readonly name = \"save\";\n readonly version = \"1.0.0\";\n\n private readonly options: SavePluginOptions;\n\n constructor(options?: SavePluginOptions) {\n this.options = options ?? {};\n }\n\n install(context: EngineContext): void {\n const storage = this.options.storage ?? new LocalStorageSaveStorage();\n const service = new SaveService(storage, context, this.options.namespace);\n\n context.register(SaveServiceKey, service);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/Save.ts","../src/keys.ts","../src/SavePlugin.ts","../src/adapters/memory.ts","../src/adapters/localStorage.ts","../src/snapshot/LocalStorageSnapshotStorage.ts","../src/snapshot/SnapshotService.ts","../src/snapshot/keys.ts","../src/snapshot/SnapshotPlugin.ts"],"sourcesContent":["export { VERSION } from \"@yagejs/core\";\n\n// ---------------------------------------------------------------------------\n// Stores + save IO (primary persistence path — typed reactive stores)\n// ---------------------------------------------------------------------------\n\n// Re-exported store primitives so users can `import { defineStore } from \"@yagejs/save\"`\n// when they prefer to read it as a save concern. Originals live in @yagejs/core.\nexport {\n defineStore,\n defineSet,\n defineMap,\n defineCounter,\n jsonCodec,\n setCodec,\n mapCodec,\n dateCodec,\n StoreVersionTooNewError,\n StoreMigrationMissingError,\n} from \"@yagejs/core\";\nexport type {\n PersistentLike,\n PersistentStore,\n PersistentSet,\n PersistentMap,\n PersistentCounter,\n DefineStoreOptions,\n DefineSetOptions,\n DefineMapOptions,\n DefineCounterOptions,\n Codec,\n} from \"@yagejs/core\";\n\nexport {\n Save,\n createSave,\n SlotNotFoundError,\n InvalidKeyError,\n} from \"./Save.js\";\nexport type { SaveAdapter, SlotInfo, CreateSaveOptions } from \"./Save.js\";\n\nexport { SavePlugin } from \"./SavePlugin.js\";\nexport type { SavePluginOptions } from \"./SavePlugin.js\";\n\nexport { SaveServiceKey } from \"./keys.js\";\n\nexport { memoryAdapter, localStorageAdapter } from \"./adapters/index.js\";\nexport type { LocalStorageAdapterOptions } from \"./adapters/index.js\";\n\n// ---------------------------------------------------------------------------\n// Snapshot system (full-scene quicksave via @serializable)\n// ---------------------------------------------------------------------------\n\nexport type {\n SnapshotStorage,\n UntypedSlots,\n GameSnapshot,\n SceneSnapshotEntry,\n EntitySnapshotEntry,\n ComponentSnapshot,\n SnapshotContributor,\n} from \"./snapshot/types.js\";\nexport type { SnapshotResolver } from \"@yagejs/core\";\n\nexport { LocalStorageSnapshotStorage } from \"./snapshot/LocalStorageSnapshotStorage.js\";\n\nexport { SnapshotService } from \"./snapshot/SnapshotService.js\";\nexport { SnapshotPlugin } from \"./snapshot/SnapshotPlugin.js\";\nexport type { SnapshotPluginOptions } from \"./snapshot/SnapshotPlugin.js\";\nexport { SnapshotServiceKey } from \"./snapshot/keys.js\";\n","import type { PersistentLike } from \"@yagejs/core\";\n\n/**\n * Pluggable storage backend used by the Save instance. Adapters speak strings\n * — codecs and serialization happen above this layer.\n */\nexport interface SaveAdapter {\n /** Read raw bytes for a key. Returns null when the key is absent. */\n read(key: string): Promise<string | null>;\n /** Write raw bytes for a key. Overwrites existing value. */\n write(key: string, value: string): Promise<void>;\n /** Delete the value at a key. No-op when absent. */\n delete(key: string): Promise<void>;\n /** List all keys starting with `prefix`. */\n list(prefix: string): Promise<string[]>;\n}\n\n/** Public metadata about a single saved slot, returned by `listSlots`. */\nexport interface SlotInfo<M = unknown> {\n name: string;\n savedAt: number;\n metadata?: M;\n}\n\n/** Internal manifest entry stored alongside each store's slots. */\ninterface ManifestEntry {\n name: string;\n savedAt: number;\n metadata?: unknown;\n}\n\n/** Internal manifest format. */\ninterface SlotManifest {\n version: 1;\n slots: Record<string, ManifestEntry>;\n}\n\n/** Thrown by `loadSlot`/`deleteSlot` when the named slot doesn't exist. */\nexport class SlotNotFoundError extends Error {\n readonly storeId: string;\n readonly slot: string;\n constructor(storeId: string, slot: string) {\n super(`No save found for store \"${storeId}\" in slot \"${slot}\".`);\n this.name = \"SlotNotFoundError\";\n this.storeId = storeId;\n this.slot = slot;\n }\n}\n\n/** Thrown when a store id or slot name contains a reserved character. */\nexport class InvalidKeyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidKeyError\";\n }\n}\n\nexport interface CreateSaveOptions {\n adapter: SaveAdapter;\n}\n\n// Each adapter key is built from URI-encoded segments joined by `/`. Encoding\n// every user-supplied segment is what guarantees that legal-but-pathological\n// store ids or slot names (e.g. id=\"a/b\" or slot=\"m\") can't construct keys\n// that overlap with documents, slots, or manifests for other stores. The\n// suffix tags (`d`/`s`/`m`) are fixed and never come from user input.\nconst SEP = \"/\";\nconst DOC_TAG = \"d\";\nconst SLOT_TAG = \"s\";\nconst MANIFEST_TAG = \"m\";\n\nfunction validateStoreId(id: string): void {\n if (id.length === 0) {\n throw new InvalidKeyError(\"Save: store id must be non-empty.\");\n }\n}\n\nfunction validateSlotName(slot: string): void {\n if (slot.length === 0) {\n throw new InvalidKeyError(\"Save: slot name must be non-empty.\");\n }\n}\n\nfunction docKey(id: string): string {\n validateStoreId(id);\n return `${encodeURIComponent(id)}${SEP}${DOC_TAG}`;\n}\n\nfunction slotKey(id: string, slot: string): string {\n validateStoreId(id);\n validateSlotName(slot);\n return `${encodeURIComponent(id)}${SEP}${SLOT_TAG}${SEP}${encodeURIComponent(slot)}`;\n}\n\nfunction manifestKey(id: string): string {\n validateStoreId(id);\n return `${encodeURIComponent(id)}${SEP}${MANIFEST_TAG}`;\n}\n\nasync function readManifest(\n adapter: SaveAdapter,\n id: string,\n): Promise<SlotManifest> {\n const raw = await adapter.read(manifestKey(id));\n if (raw == null) return { version: 1, slots: {} };\n try {\n const parsed = JSON.parse(raw) as Partial<SlotManifest>;\n if (parsed && typeof parsed === \"object\" && parsed.slots) {\n return { version: 1, slots: parsed.slots };\n }\n } catch {\n // Fall through to empty manifest — the next save rewrites it.\n }\n return { version: 1, slots: {} };\n}\n\nasync function writeManifest(\n adapter: SaveAdapter,\n id: string,\n manifest: SlotManifest,\n): Promise<void> {\n await adapter.write(manifestKey(id), JSON.stringify(manifest));\n}\n\n/**\n * Save instance — IO over typed stores. Created with `createSave({ adapter })`\n * and registered in the engine via `SavePlugin` so components can resolve it\n * through `SaveServiceKey`.\n */\nexport class Save {\n readonly adapter: SaveAdapter;\n\n /**\n * Per-store manifest update queue. Two concurrent saveSlot/deleteSlot calls\n * against the same store would otherwise read-modify-write the manifest\n * blindly — the later writer wins and the earlier change is silently lost.\n * Funnelling manifest mutations through a per-store promise chain serializes\n * them while leaving slot data writes (which target distinct keys)\n * unaffected. Per-store, not global, because manifests for different stores\n * never collide.\n */\n private readonly manifestQueues = new Map<string, Promise<void>>();\n\n constructor(opts: CreateSaveOptions) {\n this.adapter = opts.adapter;\n }\n\n /** Persist the store as an unslotted document. */\n async persist(store: PersistentLike): Promise<void> {\n const payload = store.serialize();\n await this.adapter.write(docKey(store.id), JSON.stringify(payload));\n }\n\n /**\n * Restore an unslotted document into the store. No-op when the document\n * doesn't exist — the store keeps its current (default) value.\n */\n async restore(store: PersistentLike): Promise<void> {\n const raw = await this.adapter.read(docKey(store.id));\n if (raw == null) return;\n store.hydrate(JSON.parse(raw));\n }\n\n /** Restore many stores in parallel. */\n async restoreAll(stores: PersistentLike[]): Promise<void> {\n await Promise.all(stores.map((s) => this.restore(s)));\n }\n\n /**\n * Save the store into a named slot. The slot manifest is updated with the\n * timestamp and optional metadata.\n */\n async saveSlot<M = unknown>(\n store: PersistentLike,\n slot: string,\n opts?: { metadata?: M },\n ): Promise<void> {\n const payload = store.serialize();\n await this.adapter.write(slotKey(store.id, slot), JSON.stringify(payload));\n\n // Slot data is written before the manifest. If the manifest write fails,\n // the slot data exists at its slot key but `listSlots` won't see it\n // (loadSlot can still find it by name). Acceptable for localStorage-class\n // adapters where writes are effectively atomic; adapters with unreliable\n // writes should retry the manifest update or wrap both writes in a\n // transaction.\n const entry: ManifestEntry = {\n name: slot,\n savedAt: Date.now(),\n };\n if (opts?.metadata !== undefined) entry.metadata = opts.metadata;\n await this.updateManifest(store.id, (manifest) => {\n manifest.slots[slot] = entry;\n });\n }\n\n /** Load a slot into the store. Throws `SlotNotFoundError` when missing. */\n async loadSlot(store: PersistentLike, slot: string): Promise<void> {\n const raw = await this.adapter.read(slotKey(store.id, slot));\n if (raw == null) throw new SlotNotFoundError(store.id, slot);\n store.hydrate(JSON.parse(raw));\n }\n\n /** List slots for a store, optionally filtered by prefix. */\n async listSlots<M = unknown>(\n store: PersistentLike,\n opts?: { prefix?: string },\n ): Promise<SlotInfo<M>[]> {\n const manifest = await readManifest(this.adapter, store.id);\n const entries = Object.values(manifest.slots);\n const filtered =\n opts?.prefix !== undefined\n ? entries.filter((e) => e.name.startsWith(opts.prefix as string))\n : entries;\n return filtered.map((e) => {\n const info: SlotInfo<M> = { name: e.name, savedAt: e.savedAt };\n if (e.metadata !== undefined) info.metadata = e.metadata as M;\n return info;\n });\n }\n\n /** Delete a slot. No-op when the slot doesn't exist. */\n async deleteSlot(store: PersistentLike, slot: string): Promise<void> {\n // Slot data is deleted before the manifest is updated. If the manifest\n // write fails, the manifest still references a slot whose data is gone\n // and a subsequent `loadSlot` will throw `SlotNotFoundError`. Acceptable\n // for localStorage-class adapters; adapters with unreliable writes\n // should retry the manifest update.\n await this.adapter.delete(slotKey(store.id, slot));\n await this.updateManifest(store.id, (manifest) => {\n if (!(slot in manifest.slots)) return;\n // Build a fresh slots map without the deleted entry — avoids dynamic\n // delete on a record (lint: no-dynamic-delete).\n const next: Record<string, ManifestEntry> = {};\n for (const [name, entry] of Object.entries(manifest.slots)) {\n if (name !== slot) next[name] = entry;\n }\n manifest.slots = next;\n });\n }\n\n /**\n * Read-modify-write the manifest for a store, serialized through the\n * per-store queue. The mutator runs on the freshly-read manifest; if it's\n * a no-op the write is skipped (the mutator can opt out by leaving the\n * passed manifest unchanged — but we always write back to keep the contract\n * predictable; a no-op write is cheap).\n */\n private updateManifest(\n storeId: string,\n mutate: (manifest: SlotManifest) => void,\n ): Promise<void> {\n const prev = this.manifestQueues.get(storeId) ?? Promise.resolve();\n const next = prev.then(async () => {\n const manifest = await readManifest(this.adapter, storeId);\n mutate(manifest);\n await writeManifest(this.adapter, storeId, manifest);\n });\n // Swallow rejections in the chain so a single failure doesn't poison the\n // queue for subsequent updates; callers still see the original rejection\n // because we return `next`, not the swallowed copy.\n this.manifestQueues.set(\n storeId,\n next.catch(() => undefined),\n );\n return next;\n }\n\n /**\n * Subscribe to the store and persist on every change, coalesced to a single\n * in-flight write per store. Returns a stop function — call it to\n * unsubscribe.\n *\n * Writes are serialized: while a `persist()` is in flight, further changes\n * mark the store dirty and trigger one more write *after* the current one\n * resolves. The last-set state always wins, even on a slow async adapter,\n * because each flush re-reads `store.serialize()` rather than capturing the\n * value at scheduling time. Multiple synchronous `set` calls collapse into\n * one write because the dirty flag is consumed atomically.\n *\n * `setTimeout` is intentionally not used here: `Save` runs alongside the\n * page lifecycle, not the engine loop, and may be active before the engine\n * starts or after it stops (e.g. settings menus on a paused game). Using\n * engine-time processes here would tie persistence to a running scheduler.\n */\n autoPersist(store: PersistentLike): () => void {\n let inFlight = false;\n let dirty = false;\n let stopped = false;\n\n const flush = async (): Promise<void> => {\n // Drain the dirty flag in a loop so any change that arrives while a\n // write is in progress is captured by the next iteration. Snapshot the\n // value at write time (via store.serialize, called inside persist) so\n // we always commit the latest state — never an older one captured at\n // schedule time.\n while (dirty && !stopped) {\n dirty = false;\n try {\n await this.persist(store);\n } catch (err) {\n console.error(\n `autoPersist: failed to persist store \"${store.id}\":`,\n err,\n );\n }\n }\n inFlight = false;\n };\n\n const off = store.subscribe(() => {\n if (stopped) return;\n dirty = true;\n if (inFlight) return;\n inFlight = true;\n // Defer the first iteration to a microtask so multiple synchronous\n // `set` calls collapse into one flush iteration.\n queueMicrotask(() => {\n if (stopped) {\n inFlight = false;\n return;\n }\n void flush();\n });\n });\n\n return () => {\n stopped = true;\n off();\n };\n }\n}\n\n/** Construct a Save instance bound to the given adapter. */\nexport function createSave(opts: CreateSaveOptions): Save {\n return new Save(opts);\n}\n","import { ServiceKey } from \"@yagejs/core\";\nimport type { Save } from \"./Save.js\";\n\n/** Service key for the Save instance registered by SavePlugin. */\nexport const SaveServiceKey = new ServiceKey<Save>(\"save\");\n","import type { EngineContext, Plugin } from \"@yagejs/core\";\nimport type { Save } from \"./Save.js\";\nimport { SaveServiceKey } from \"./keys.js\";\n\nexport interface SavePluginOptions {\n /**\n * Save instance to register. Constructed in user code (typically in `main.ts`)\n * so it's also available before the engine starts — for restoring settings,\n * loading a save slot, or other boot-time work.\n */\n save: Save;\n}\n\n/**\n * Registers a Save instance under `SaveServiceKey` so components can resolve it\n * via `this.use(SaveServiceKey)` for in-game persistence.\n */\nexport class SavePlugin implements Plugin {\n readonly name = \"save\";\n readonly version = \"1.0.0\";\n\n private readonly options: SavePluginOptions;\n\n constructor(options: SavePluginOptions) {\n this.options = options;\n }\n\n install(context: EngineContext): void {\n context.register(SaveServiceKey, this.options.save);\n }\n}\n","import type { SaveAdapter } from \"../Save.js\";\n\n/**\n * In-memory adapter — backed by a `Map<string, string>`. Useful for tests and\n * Node tooling. State lives on the instance; create a fresh adapter to reset.\n */\nexport function memoryAdapter(): SaveAdapter {\n const data = new Map<string, string>();\n return {\n async read(key) {\n return data.get(key) ?? null;\n },\n async write(key, value) {\n data.set(key, value);\n },\n async delete(key) {\n data.delete(key);\n },\n async list(prefix) {\n const out: string[] = [];\n for (const k of data.keys()) {\n if (k.startsWith(prefix)) out.push(k);\n }\n return out;\n },\n };\n}\n","import type { SaveAdapter } from \"../Save.js\";\n\nexport interface LocalStorageAdapterOptions {\n /** Key namespace. Every key is prefixed with `${namespace}:`. Defaults to \"yage\". */\n namespace?: string;\n}\n\n/**\n * Browser localStorage-backed adapter. Throws helpfully on quota and\n * unsupported environments.\n */\nexport function localStorageAdapter(\n opts: LocalStorageAdapterOptions = {},\n): SaveAdapter {\n const namespace = opts.namespace ?? \"yage\";\n const prefix = `${namespace}:`;\n\n const ls = (): Storage => {\n if (typeof window === \"undefined\") {\n throw new Error(\n \"localStorageAdapter: window is not available in this environment.\",\n );\n }\n // Safari with cookies blocked, private-mode iframes, and some embedded\n // contexts throw `SecurityError` on the property access itself — not\n // just on get/set. Catch eagerly and rethrow with a normalized message\n // so callers don't have to handle raw DOMExceptions.\n try {\n const storage = window.localStorage;\n if (!storage) {\n throw new Error(\n \"localStorageAdapter: window.localStorage is not available in this environment.\",\n );\n }\n return storage;\n } catch (err) {\n throw new Error(\n \"localStorageAdapter: window.localStorage is not available in this environment.\",\n { cause: err },\n );\n }\n };\n\n return {\n async read(key) {\n return ls().getItem(prefix + key);\n },\n async write(key, value) {\n try {\n ls().setItem(prefix + key, value);\n } catch (err) {\n if (\n err instanceof DOMException &&\n (err.name === \"QuotaExceededError\" ||\n err.name === \"NS_ERROR_DOM_QUOTA_REACHED\")\n ) {\n throw new Error(\n `localStorageAdapter: quota exceeded while writing \"${prefix + key}\". ` +\n `Consider deleting old slots or using a different adapter.`,\n { cause: err },\n );\n }\n throw err;\n }\n },\n async delete(key) {\n ls().removeItem(prefix + key);\n },\n async list(keyPrefix) {\n const storage = ls();\n const full = prefix + keyPrefix;\n const out: string[] = [];\n for (let i = 0; i < storage.length; i += 1) {\n const k = storage.key(i);\n if (k != null && k.startsWith(full)) {\n out.push(k.slice(prefix.length));\n }\n }\n return out;\n },\n };\n}\n","import type { SnapshotStorage } from \"./types.js\";\n\n/** SnapshotStorage backed by browser localStorage. */\nexport class LocalStorageSnapshotStorage implements SnapshotStorage {\n load(key: string): string | null {\n return localStorage.getItem(key);\n }\n\n save(key: string, data: string): void {\n localStorage.setItem(key, data);\n }\n\n delete(key: string): void {\n localStorage.removeItem(key);\n }\n\n list(prefix?: string): string[] {\n const result: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key !== null && (!prefix || key.startsWith(prefix))) {\n result.push(key);\n }\n }\n return result;\n }\n}\n","import type { Scene, Entity, Component, SnapshotResolver } from \"@yagejs/core\";\nimport {\n SceneManagerKey,\n SerializableRegistry,\n isSerializable,\n getSerializableType,\n} from \"@yagejs/core\";\nimport type { EngineContext } from \"@yagejs/core\";\nimport type {\n SnapshotStorage,\n UntypedSlots,\n GameSnapshot,\n SceneSnapshotEntry,\n EntitySnapshotEntry,\n ComponentSnapshot,\n SnapshotContributor,\n} from \"./types.js\";\n\n/** Current snapshot format version. */\nconst SNAPSHOT_VERSION = 4;\n\n/**\n * Component restoration priority. Components listed here are added first\n * (in order) to satisfy onAdd() dependencies.\n */\nconst COMPONENT_ORDER = [\n \"Transform\",\n \"RigidBodyComponent\",\n \"ColliderComponent\",\n \"SpriteComponent\",\n \"GraphicsComponent\",\n \"AnimatedSpriteComponent\",\n \"AnimationController\",\n \"SoundComponent\",\n \"ParticleEmitterComponent\",\n \"TilemapComponent\",\n];\n\n/** Orchestrates full game-state serialization and hydration. */\nexport class SnapshotService<TSlots extends UntypedSlots = UntypedSlots> {\n private readonly storage: SnapshotStorage;\n private readonly context: EngineContext;\n private readonly namespace: string;\n private readonly contributors = new Map<string, SnapshotContributor>();\n private _loading = false;\n\n constructor(\n storage: SnapshotStorage,\n context: EngineContext,\n namespace = \"yage\",\n ) {\n this.storage = storage;\n this.context = context;\n this.namespace = namespace;\n }\n\n // ---- Extension API ----\n\n /**\n * Register a plugin to contribute extra data to every snapshot under\n * `key`. The contributor's `serialize()` is invoked during `saveSnapshot`,\n * and its `restore(data)` runs after every scene + entity in the snapshot\n * has been hydrated. Re-registering an existing key replaces the previous\n * contributor.\n */\n registerSnapshotExtra(key: string, contributor: SnapshotContributor): void {\n this.contributors.set(key, contributor);\n }\n\n /** Remove a previously registered contributor. */\n unregisterSnapshotExtra(key: string): void {\n this.contributors.delete(key);\n }\n\n // ---- Snapshot API ----\n\n /** Save a snapshot of the current scene stack to the given slot. */\n saveSnapshot(slot: string): void {\n const snapshot = this.buildSnapshot();\n this.storage.save(\n this.key(\"snapshot\", slot),\n JSON.stringify(snapshot),\n );\n }\n\n /** Load a snapshot from the given slot, rebuilding the scene stack. */\n async loadSnapshot(slot: string): Promise<void> {\n const snapshot = this.readSnapshot(slot);\n if (!snapshot) {\n throw new Error(`No save found in slot \"${slot}\".`);\n }\n await this.hydrateSnapshot(snapshot);\n }\n\n /** Export a previously saved snapshot from the given slot. */\n exportSnapshot(slot: string): GameSnapshot | null {\n return this.readSnapshot(slot);\n }\n\n /** Import a snapshot into the given slot and hydrate the scene stack. */\n async importSnapshot(slot: string, snapshot: GameSnapshot): Promise<void> {\n if (this._loading) {\n throw new Error(\"loadSnapshot already in progress.\");\n }\n if (snapshot.version !== SNAPSHOT_VERSION) {\n throw new Error(\n `Save version mismatch: expected ${SNAPSHOT_VERSION}, got ${snapshot.version}.`,\n );\n }\n this.storage.save(\n this.key(\"snapshot\", slot),\n JSON.stringify(snapshot),\n );\n await this.hydrateSnapshot(snapshot);\n }\n\n // ---- User Data API ----\n\n /** Save arbitrary structured data to a named slot. */\n saveData<K extends keyof TSlots & string>(slot: K, data: TSlots[K]): void {\n this.storage.save(this.key(\"data\", slot), JSON.stringify(data));\n }\n\n /** Load structured data from a named slot. Returns null if not found. */\n loadData<K extends keyof TSlots & string>(slot: K): TSlots[K] | null {\n const raw = this.storage.load(this.key(\"data\", slot));\n if (raw === null) return null;\n try {\n return JSON.parse(raw) as TSlots[K];\n } catch {\n return null;\n }\n }\n\n /** Read data from a slot for external use (cloud upload, file export). Alias for `loadData`. */\n exportData<K extends keyof TSlots & string>(slot: K): TSlots[K] | null {\n return this.loadData(slot);\n }\n\n /** Write externally-sourced data into a slot. Alias for `saveData` — no version check or hydration. */\n importData<K extends keyof TSlots & string>(slot: K, data: TSlots[K]): void {\n this.saveData(slot, data);\n }\n\n // ---- Snapshot management ----\n\n /** Check if a snapshot exists in the given slot. */\n hasSnapshot(slot: string): boolean {\n return this.storage.load(this.key(\"snapshot\", slot)) !== null;\n }\n\n /** Delete a snapshot from the given slot. */\n deleteSnapshot(slot: string): void {\n this.storage.delete(this.key(\"snapshot\", slot));\n }\n\n // ---- Data management ----\n\n /** Check if user data exists in the given slot. */\n hasData<K extends keyof TSlots & string>(slot: K): boolean {\n return this.storage.load(this.key(\"data\", slot)) !== null;\n }\n\n /** Delete user data from the given slot. */\n deleteData<K extends keyof TSlots & string>(slot: K): void {\n this.storage.delete(this.key(\"data\", slot));\n }\n\n // ---- Private helpers ----\n\n private key(prefix: string, slot: string): string {\n return `${this.namespace}:${prefix}:${slot}`;\n }\n\n private readSnapshot(slot: string): GameSnapshot | null {\n const raw = this.storage.load(this.key(\"snapshot\", slot));\n if (raw === null) return null;\n try {\n return JSON.parse(raw) as GameSnapshot;\n } catch {\n return null;\n }\n }\n\n private buildSnapshot(): GameSnapshot {\n const sceneManager = this.context.resolve(SceneManagerKey);\n const scenes: SceneSnapshotEntry[] = [];\n\n for (const scene of sceneManager.all) {\n if (!isSerializable(scene)) continue;\n const type = getSerializableType(scene);\n if (!type) continue;\n\n const entities: EntitySnapshotEntry[] = [];\n for (const entity of scene.getEntities()) {\n if (!isSerializable(entity)) continue;\n entities.push(this.serializeEntity(entity));\n }\n\n const userData = scene.serialize?.();\n\n scenes.push({\n type,\n paused: scene.paused,\n entities,\n userData,\n });\n }\n\n const extras: Record<string, unknown> = {};\n let hasExtras = false;\n for (const [key, contributor] of this.contributors) {\n // Isolate each contributor — a single failing one (e.g. a third-party\n // plugin throwing during serialize) shouldn't poison the rest of the\n // snapshot. Log and continue.\n try {\n const data = contributor.serialize();\n if (data !== undefined) {\n extras[key] = data;\n hasExtras = true;\n }\n } catch (err) {\n console.error(\n `SnapshotService: contributor \"${key}\" serialize failed; ` +\n `omitting its extras from this snapshot.`,\n err,\n );\n }\n }\n\n return {\n version: SNAPSHOT_VERSION,\n timestamp: Date.now(),\n scenes,\n ...(hasExtras ? { extras } : {}),\n };\n }\n\n private async hydrateSnapshot(snapshot: GameSnapshot): Promise<void> {\n if (this._loading) {\n throw new Error(\"loadSnapshot already in progress.\");\n }\n this._loading = true;\n try {\n if (snapshot.version !== SNAPSHOT_VERSION) {\n throw new Error(\n `Save version mismatch: expected ${SNAPSHOT_VERSION}, got ${snapshot.version}.`,\n );\n }\n\n const sceneManager = this.context.resolve(SceneManagerKey);\n await sceneManager.popAll();\n\n for (const entry of snapshot.scenes) {\n const SceneClass = SerializableRegistry.get(entry.type) as\n | (new () => Scene)\n | undefined;\n if (!SceneClass) {\n throw new Error(\n `Cannot load scene type \"${entry.type}\". ` +\n `Ensure the scene class is decorated with @serializable.`,\n );\n }\n\n const scene = new SceneClass();\n\n // Instance-patch onEnter: restore entities + call afterRestore instead\n scene.onEnter = () => {\n this.restoreSceneEntities(scene, entry);\n };\n\n await sceneManager.push(scene);\n\n if (entry.paused) {\n scene.paused = true;\n }\n }\n\n // Run snapshot contributors after every scene is pushed and entities\n // restored. By this point any layer/scene render trees the renderer\n // contributor needs to find by name are live, and component-scope\n // afterRestore hooks have run, so contributors see consistent state.\n //\n // Iterate every REGISTERED contributor — not just keys present in\n // `extras` — so contributors get a chance to clear stale baseline\n // state (e.g. a screen-scope vignette enabled after the save) when\n // the snapshot has no entry for them. Contributors must treat\n // `data === undefined` as \"reset to empty\".\n const extras = snapshot.extras ?? {};\n for (const [key, contributor] of this.contributors) {\n // Isolate each contributor — one bad restore (e.g. a third-party\n // plugin choking on a partially-compatible payload) shouldn't abort\n // the whole load and leave the engine in a half-restored state.\n try {\n await contributor.restore(extras[key]);\n } catch (err) {\n console.error(\n `SnapshotService: contributor \"${key}\" restore failed; continuing.`,\n err,\n );\n }\n }\n for (const key of Object.keys(extras)) {\n if (!this.contributors.has(key)) {\n console.warn(\n `SnapshotService: snapshot contains extras for \"${key}\" but no ` +\n `contributor is registered — skipping.`,\n );\n }\n }\n } finally {\n this._loading = false;\n }\n }\n\n private restoreSceneEntities(\n scene: Scene,\n entry: SceneSnapshotEntry,\n ): void {\n // Phase 1: Create all entities, add to scene, add components\n const idMap = new Map<number, Entity>();\n const entityEntries: Array<{\n entity: Entity;\n entry: EntitySnapshotEntry;\n restoredComponents: Array<{ component: Component; data: unknown }>;\n }> = [];\n\n for (const entityEntry of entry.entities) {\n const EntityClass = SerializableRegistry.get(entityEntry.type) as\n | (new () => Entity)\n | undefined;\n if (!EntityClass) {\n console.warn(\n `Entity type \"${entityEntry.type}\" not found in registry — skipping.`,\n );\n continue;\n }\n\n const entity = new EntityClass();\n scene._addExistingEntity(entity);\n const restoredComponents = this.restoreEntityComponents(\n entity,\n entityEntry.components,\n );\n\n idMap.set(entityEntry.id, entity);\n entityEntries.push({ entity, entry: entityEntry, restoredComponents });\n }\n\n // Phase 2: Rewire parent/child relationships\n for (const { entity, entry: entityEntry } of entityEntries) {\n if (entityEntry.parentId != null && entityEntry.childName != null) {\n const parent = idMap.get(entityEntry.parentId);\n if (parent) {\n parent.addChild(entityEntry.childName, entity);\n } else {\n console.warn(\n `Parent entity (saved id ${entityEntry.parentId}) not found for child \"${entity.name}\" — restoring as root entity.`,\n );\n }\n }\n }\n\n // Build resolver for afterRestore hooks\n const resolver: SnapshotResolver = {\n entity(savedId: number) {\n return idMap.get(savedId) ?? null;\n },\n };\n\n // Phase 3: afterRestore hooks (components, then entities, then scene)\n for (const { entity, entry: entityEntry, restoredComponents } of entityEntries) {\n for (const { component, data } of restoredComponents) {\n component.afterRestore?.(data, resolver);\n }\n\n entity.afterRestore?.(entityEntry.userData, resolver);\n }\n\n scene.afterRestore?.(entry.userData, resolver);\n }\n\n private serializeEntity(entity: Entity): EntitySnapshotEntry {\n const type = getSerializableType(entity);\n if (!type) throw new Error(\"Entity is not serializable\");\n\n const components: ComponentSnapshot[] = [];\n for (const component of entity.getAll()) {\n if (typeof component.serialize !== \"function\") continue;\n const data = component.serialize();\n if (data == null) continue;\n const compType =\n getSerializableType(component) ?? component.constructor.name;\n components.push({ type: compType, data });\n }\n\n const userData = entity.serialize?.();\n\n const result: EntitySnapshotEntry = {\n id: entity.id,\n type,\n components,\n userData,\n };\n\n // Capture parent/child relationship\n if (entity.parent && isSerializable(entity.parent)) {\n result.parentId = entity.parent.id;\n // Find the name this entity is registered under\n for (const [name, child] of entity.parent.children) {\n if (child === entity) {\n result.childName = name;\n break;\n }\n }\n } else if (entity.parent) {\n console.warn(\n `Entity \"${entity.name}\" has non-serializable parent \"${entity.parent.name}\" — parent/child relationship will not be saved.`,\n );\n }\n\n return result;\n }\n\n private restoreEntityComponents(\n entity: Entity,\n snapshots: ComponentSnapshot[],\n ): Array<{ component: Component; data: unknown }> {\n const sorted = [...snapshots].sort((a, b) => {\n const ai = COMPONENT_ORDER.indexOf(a.type);\n const bi = COMPONENT_ORDER.indexOf(b.type);\n return (ai >= 0 ? ai : 999) - (bi >= 0 ? bi : 999);\n });\n\n const restored: Array<{ component: Component; data: unknown }> = [];\n\n for (const snap of sorted) {\n const CompClass = SerializableRegistry.get(snap.type) as\n | ({ fromSnapshot?(data: unknown): Component } & (new (\n ...args: unknown[]\n ) => Component))\n | undefined;\n\n if (!CompClass || typeof CompClass.fromSnapshot !== \"function\") {\n // Not in registry or no fromSnapshot — entity handles in afterRestore\n continue;\n }\n\n const component = CompClass.fromSnapshot(snap.data);\n entity.add(component);\n restored.push({ component, data: snap.data });\n }\n\n return restored;\n }\n}\n","import { ServiceKey } from \"@yagejs/core\";\nimport type { SnapshotService } from \"./SnapshotService.js\";\n\n/** Service key for the SnapshotService. */\nexport const SnapshotServiceKey = new ServiceKey<SnapshotService>(\n \"snapshotService\",\n);\n","import type { EngineContext, Plugin } from \"@yagejs/core\";\nimport type { SnapshotStorage } from \"./types.js\";\nimport { LocalStorageSnapshotStorage } from \"./LocalStorageSnapshotStorage.js\";\nimport { SnapshotService } from \"./SnapshotService.js\";\nimport { SnapshotServiceKey } from \"./keys.js\";\n\n/** Options for the SnapshotPlugin. */\nexport interface SnapshotPluginOptions {\n /** Custom storage backend. Defaults to LocalStorageSnapshotStorage. */\n storage?: SnapshotStorage;\n /** Namespace for stored keys. Defaults to \"yage\". */\n namespace?: string;\n}\n\n/** Plugin that registers SnapshotService into the engine context. */\nexport class SnapshotPlugin implements Plugin {\n readonly name = \"snapshot\";\n readonly version = \"1.0.0\";\n\n private readonly options: SnapshotPluginOptions;\n\n constructor(options?: SnapshotPluginOptions) {\n this.options = options ?? {};\n }\n\n install(context: EngineContext): void {\n const storage = this.options.storage ?? new LocalStorageSnapshotStorage();\n const service = new SnapshotService(storage, context, this.options.namespace);\n\n context.register(SnapshotServiceKey, service);\n }\n}\n"],"mappings":"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","names":["ServiceKey"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagejs/save",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Save and load game state for YAGE",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"yage",
|
|
@@ -48,6 +48,6 @@
|
|
|
48
48
|
"clean": "rm -rf dist"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@yagejs/core": "^0.
|
|
51
|
+
"@yagejs/core": "^0.6.0"
|
|
52
52
|
}
|
|
53
53
|
}
|