@asaidimu/utils-artifacts 8.2.6 → 8.2.8

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.
Files changed (2) hide show
  1. package/README.md +156 -0
  2. 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/utils-artifacts",
3
- "version": "8.2.6",
3
+ "version": "8.2.8",
4
4
  "description": "Reactive artifact container.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",