@asaidimu/utils-artifacts 8.2.6 → 8.2.7
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 +156 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,6 +224,162 @@ observer.subscribe((resolved) => {
|
|
|
224
224
|
});
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
### Persistence: Export and Import
|
|
228
|
+
|
|
229
|
+
The container can export its current set of built singleton artifacts into a serializable bundle and later restore them in a new container. This is useful for **saving/reloading application state**, or **transferring** a snapshot across environments.
|
|
230
|
+
|
|
231
|
+
#### Exporting Artifacts
|
|
232
|
+
|
|
233
|
+
Call `container.export()` to generate a JSON‑compatible bundle.
|
|
234
|
+
|
|
235
|
+
- The method **waits** for all currently resolving artifacts to finish.
|
|
236
|
+
- Only **singleton** artifacts that have been fully built (i.e., `ready === true`) are included.
|
|
237
|
+
- Each exported artifact stores:
|
|
238
|
+
- Its resolved `instance` (must be JSON‑serializable).
|
|
239
|
+
- The **state groups** (store selectors) it depends on, with their paths and options.
|
|
240
|
+
- Its list of **artifact dependencies** (other artifact keys).
|
|
241
|
+
- The bundle contains a **checksum** to detect corruption.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const bundle = await container.export();
|
|
245
|
+
// {
|
|
246
|
+
// version: "1.0",
|
|
247
|
+
// timestamp: 1700000000000,
|
|
248
|
+
// checksum: "abc123...",
|
|
249
|
+
// artifacts: [
|
|
250
|
+
// {
|
|
251
|
+
// key: "config",
|
|
252
|
+
// instance: { theme: "dark" },
|
|
253
|
+
// state: { groups: [{ paths: ["theme"], options: undefined }] },
|
|
254
|
+
// dependencies: []
|
|
255
|
+
// },
|
|
256
|
+
// // ...
|
|
257
|
+
// ]
|
|
258
|
+
// }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
> **Note**: Transient artifacts are **never** exported. If an artifact instance contains non‑serializable values (e.g., functions, symbols), `export()` will throw an error.
|
|
262
|
+
|
|
263
|
+
#### Importing Artifacts
|
|
264
|
+
|
|
265
|
+
Use the static `ArtifactContainer.from()` method to create a new container pre‑filled with exported data.
|
|
266
|
+
|
|
267
|
+
- You must provide:
|
|
268
|
+
- A `store` (the same or a different `ReactiveDataStore`).
|
|
269
|
+
- The previously exported `bundle`.
|
|
270
|
+
- The **original artifact templates** (the container does not store factory functions).
|
|
271
|
+
- The method validates the bundle’s checksum. On mismatch, it throws `"Bundle checksum mismatch"`.
|
|
272
|
+
- For each exported artifact, the container **restores the instance only if** all its state groups still match the current store’s data. If any selected slice has changed, the artifact is marked **stale** (not restored) and will be rebuilt on the next `resolve()`.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Create a new container from a bundle
|
|
276
|
+
const restoredContainer = await ArtifactContainer.from({
|
|
277
|
+
store: new ReactiveDataStore(initialState),
|
|
278
|
+
bundle: savedBundle,
|
|
279
|
+
templates: [
|
|
280
|
+
{
|
|
281
|
+
key: "config",
|
|
282
|
+
factory: async ({ use }) => ({
|
|
283
|
+
theme: await use(({ select }) => select((s: any) => s.theme)),
|
|
284
|
+
}),
|
|
285
|
+
scope: "singleton",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
key: "userName",
|
|
289
|
+
factory: async ({ use }) =>
|
|
290
|
+
`User-${await use(({ select }) => select((s: any) => s.userId))}`,
|
|
291
|
+
scope: "singleton",
|
|
292
|
+
},
|
|
293
|
+
// ... other templates
|
|
294
|
+
],
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Staleness and Reactivity
|
|
299
|
+
|
|
300
|
+
When importing, the container compares the exported **state groups** (store selectors) with the **current store**:
|
|
301
|
+
|
|
302
|
+
- If **all** state group values are identical → the artifact instance is restored directly (no rebuild).
|
|
303
|
+
- If **any** value differs → the artifact is **skipped** (not restored) and its cache entry remains empty. The next `resolve()` will run the factory again, using the latest store data.
|
|
304
|
+
|
|
305
|
+
Staleness **propagates** through the dependency graph: if artifact `A` depends on `B` and `B` is stale, then `A` is also considered stale and will be rebuilt.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Example: store changed from { theme: "dark", userId: 42 } to { theme: "light", userId: 42 }
|
|
309
|
+
// - config (depends on theme) → stale, not restored
|
|
310
|
+
// - userName (depends on userId) → fresh, restored
|
|
311
|
+
// - greeting (depends on config) → stale (transitive), not restored
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Parameterized Artifacts
|
|
315
|
+
|
|
316
|
+
Parameterized singletons are exported and imported **per parameter key**. The same staleness checks apply independently for each parameter combination.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
container.register({
|
|
320
|
+
key: "user",
|
|
321
|
+
paramKey: (params) => `user:${params.userId}`,
|
|
322
|
+
factory: async (ctx) => ({ id: ctx.params.userId, name: "..." }),
|
|
323
|
+
scope: "singleton",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Export includes both user:alice and user:bob
|
|
327
|
+
const bundle = await container.export();
|
|
328
|
+
|
|
329
|
+
// Later, restore both instances
|
|
330
|
+
const newContainer = await ArtifactContainer.from({ store, bundle, templates });
|
|
331
|
+
newContainer.peek("user", { userId: "alice" }); // restored instance
|
|
332
|
+
newContainer.peek("user", { userId: "bob" }); // restored instance
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Complete Example
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { ReactiveDataStore } from "@asaidimu/utils-store";
|
|
339
|
+
import { ArtifactContainer } from "@asaidimu/utils-artifacts";
|
|
340
|
+
|
|
341
|
+
// 1. Initial setup
|
|
342
|
+
const store = new ReactiveDataStore({ theme: "dark", userId: 42 });
|
|
343
|
+
const container = new ArtifactContainer(store);
|
|
344
|
+
|
|
345
|
+
container.register({
|
|
346
|
+
key: "greeting",
|
|
347
|
+
factory: async ({ use }) => {
|
|
348
|
+
const theme = await use(({ select }) => select((s: any) => s.theme));
|
|
349
|
+
const userId = await use(({ select }) => select((s: any) => s.userId));
|
|
350
|
+
return `${theme} greeting for user ${userId}`;
|
|
351
|
+
},
|
|
352
|
+
scope: "singleton",
|
|
353
|
+
lazy: false,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
await container.resolve("greeting"); // builds "dark greeting for user 42"
|
|
357
|
+
|
|
358
|
+
// 2. Export the bundle
|
|
359
|
+
const bundle = await container.export();
|
|
360
|
+
|
|
361
|
+
// 3. Later, in a different environment (e.g., another request)
|
|
362
|
+
const newStore = new ReactiveDataStore({ theme: "light", userId: 42 });
|
|
363
|
+
const restored = await ArtifactContainer.from({
|
|
364
|
+
store: newStore,
|
|
365
|
+
bundle,
|
|
366
|
+
templates: [
|
|
367
|
+
/* same greeting template */
|
|
368
|
+
],
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// theme changed → greeting is stale, will be rebuilt on demand
|
|
372
|
+
const greeting = await restored.require("greeting");
|
|
373
|
+
console.log(greeting); // "light greeting for user 42"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### Important Notes
|
|
377
|
+
|
|
378
|
+
- The exported bundle is **read‑only** and should be treated as an immutable snapshot.
|
|
379
|
+
- Always provide **all necessary templates** when calling `from()`. Missing templates cause resolution errors.
|
|
380
|
+
- For high‑cardinality parameterized artifacts, consider cleaning up unused keys before exporting to keep the bundle size reasonable.
|
|
381
|
+
- The checksum prevents accidental corruption but does **not** provide cryptographic security.
|
|
382
|
+
|
|
227
383
|
## 5. Architecture
|
|
228
384
|
|
|
229
385
|
`@asaidimu/utils-artifacts` implements a **Directed Acyclic Graph (DAG)** for dependency management using a **Pull-based Invalidation** model.
|