@pumped-fn/lite 1.11.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/MIGRATION.md +2 -2
- package/PATTERNS.md +58 -39
- package/README.md +145 -187
- package/dist/cli.cjs +335 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +337 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +169 -22
- package/dist/index.d.cts +126 -18
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +126 -18
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +167 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -4
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["categories: Record<string, { title: string; content: string | (() => string) }>"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nconst pkgDir = join(dirname(fileURLToPath(import.meta.url)), \"..\")\nconst readme = readFileSync(join(pkgDir, \"README.md\"), \"utf-8\")\n\nfunction extractOverview(): string {\n const idx = readme.indexOf(\"## How It Works\")\n const raw = idx === -1 ? readme : readme.slice(0, idx)\n return raw.replace(/^#[^\\n]*\\n+/, \"\").trim()\n}\n\nfunction extractDiagram(): string {\n const match = readme.match(/```mermaid\\n([\\s\\S]*?)```/)\n return match ? `Full system sequence (unified):\\n\\n\\`\\`\\`mermaid\\n${match[1]!.trim()}\\n\\`\\`\\`` : \"No diagram found in README.md\"\n}\n\nconst categories: Record<string, { title: string; content: string | (() => string) }> = {\n overview: {\n title: \"What is @pumped-fn/lite\",\n content: extractOverview,\n },\n\n primitives: {\n title: \"Primitives API\",\n content: `atom({ factory, deps?, tags?, keepAlive? })\n Creates a managed effect. Factory receives (ctx, resolvedDeps) and returns a value.\n Cached per scope. Supports cleanup via ctx.onClose().\n\n import { atom } from \"@pumped-fn/lite\"\n const dbAtom = atom({ factory: () => createDbPool() })\n const userAtom = atom({\n deps: { db: dbAtom },\n factory: (ctx, { db }) => db.query(\"SELECT ...\"),\n })\n\nflow({ factory, parse?, deps?, tags? })\n Operation template executed per call. parse validates input, factory runs logic.\n\n import { flow, typed } from \"@pumped-fn/lite\"\n const getUser = flow({\n parse: typed<{ id: string }>(),\n deps: { db: dbAtom },\n factory: (ctx, { db }) => db.findUser(ctx.input.id),\n })\n\ntag({ label, default?, parse? })\n Ambient context value. Attach to atoms/flows/contexts. Retrieve via tag.get/find/collect.\n\n import { tag } from \"@pumped-fn/lite\"\n const tenantTag = tag<string>({ label: \"tenant\" })\n\npreset(target, value)\n Override an atom's resolved value. Used for testing and multi-tenant isolation.\n\n import { preset } from \"@pumped-fn/lite\"\n const mockDb = preset(dbAtom, fakeDatabaseInstance)\n\nservice({ factory, deps? })\n Convenience wrapper for atom whose value is an object of methods.\n Each method receives (ctx, ...args) for tracing/auth integration.`,\n },\n\n scope: {\n title: \"Scope Management\",\n content: `createScope({ extensions?, presets?, tags?, gc? })\n Creates a scope that manages atom resolution, caching, extensions, and GC.\n\n import { createScope } from \"@pumped-fn/lite\"\n const scope = createScope({\n extensions: [loggingExt],\n presets: [preset(dbAtom, mockDb)],\n tags: [tenantTag(\"acme\")],\n gc: { enabled: true, graceMs: 3000 },\n })\n await scope.ready\n\nscope.resolve(atom) → Promise<value> resolve and cache an atom\nscope.controller(atom) → Controller get reactive handle\nscope.select(atom, fn, opts?) → SelectHandle derived slice with equality check\nscope.on(event, atom, fn) → unsubscribe listen to atom events\nscope.release(atom) → void release atom, run cleanups\nscope.createContext(opts?) → ExecutionContext create execution boundary\nscope.flush() → Promise<void> wait all pending operations\nscope.dispose() → void release everything, run all cleanups`,\n },\n\n context: {\n title: \"ExecutionContext\",\n content: `ctx = scope.createContext({ tags? })\n Execution boundary. Tags merge with scope tags. Cleanup runs LIFO on close.\n\nctx.exec({ flow, input?, tags? }) → Promise<output>\n Execute a flow within this context. Creates a child context with merged tags.\n Child context closes automatically after execution.\n\nctx.exec({ fn, params?, tags? }) → Promise<result>\n Execute an inline function: fn(childCtx, ...params).\n Same child-context lifecycle as flow execution.\n\nctx.onClose(cleanup) → void register cleanup (runs LIFO on ctx.close)\nctx.close() → void run all registered cleanups in LIFO order\n\nctx.data\n Key-value store scoped to the context:\n Raw: get(key) / set(key, val) / has(key) / delete(key) / clear() / seek(key)\n Typed: getTag(tag) / setTag(tag, val) / hasTag(tag) / deleteTag(tag) / seekTag(tag) / getOrSetTag(tag, factory)\n\n seek/seekTag walks up the context chain to find values in parent contexts.`,\n },\n\n reactivity: {\n title: \"Reactivity (opt-in)\",\n content: `controller(atom) → Controller\n Opt-in reactive handle for an atom.\n\n ctrl.get() → current value (must be resolved first)\n ctrl.resolve() → Promise<value> (resolve if not yet)\n ctrl.set(value) → replace value, notify listeners\n ctrl.update(fn) → update value via function, notify listeners\n ctrl.invalidate() → re-run factory, notify listeners\n ctrl.release() → release atom, run cleanups\n ctrl.on(event, listener) → unsubscribe\n events: 'resolving' | 'resolved' | '*'\n\nselect(atom, selector, { eq? }) → SelectHandle\n Derived state slice. Only notifies when selected value changes per eq function.\n\n handle.get() → current selected value\n handle.subscribe(fn) → unsubscribe\n\nscope.on('resolved', atom, listener) → unsubscribe\n Listen to atom resolution events at scope level.\n\nController as dependency:\n import { controller } from \"@pumped-fn/lite\"\n const serverAtom = atom({\n deps: { cfg: controller(configAtom, { resolve: true }) },\n factory: (ctx, { cfg }) => {\n cfg.on('resolved', () => ctx.invalidate())\n return createServer(cfg.get())\n },\n })`,\n },\n\n tags: {\n title: \"Tag System\",\n content: `tag<T>({ label, default?, parse? }) → Tag<T>\n Define an ambient context value type.\n\ntag(value) → Tagged<T>\n Create a tagged value to attach to atoms, flows, or contexts.\n\nAttaching tags:\n atom({ tags: [tenantTag(\"acme\")] })\n flow({ tags: [roleTag(\"admin\")] })\n scope.createContext({ tags: [userTag(currentUser)] })\n ctx.exec({ flow, tags: [localeTag(\"en\")] })\n\nReading tags:\n tag.get(source) → T first match or throw\n tag.find(source) → T | undefined first match or undefined\n tag.collect(source) → T[] all matches\n\nContext data integration:\n ctx.data.setTag(tag, value)\n ctx.data.getTag(tag) → T\n ctx.data.seekTag(tag) → T (walks parent chain)\n ctx.data.hasTag(tag) → boolean\n\nTag executor (dependency wiring):\n tags.required(tag) → resolves tag or throws\n tags.optional(tag) → resolves tag or undefined\n tags.all(tag) → resolves all values for tag\n\nIntrospection:\n tag.atoms() → Atom[] with this tag attached\n getAllTags() → Tag[] all registered tags`,\n },\n\n extensions: {\n title: \"Extensions Pipeline\",\n content: `Extensions wrap atom resolution and flow execution (middleware pattern).\n\ninterface Extension {\n init?(scope): void | Promise<void>\n dispose?(scope): void\n wrapResolve?(next, event: ResolveEvent): Promise<value>\n wrapExec?(next, flow, ctx): Promise<output>\n}\n\ncreateScope({ extensions: [ext1, ext2] })\n\nLifecycle:\n 1. scope creation → ext.init(scope) called for each extension\n 2. await scope.ready → all init() resolved\n 3. resolve(atom) → ext.wrapResolve(next, { kind: \"atom\", target, scope })\n resolve(resource) → ext.wrapResolve(next, { kind: \"resource\", target, ctx })\n - call next() to proceed to actual resolution\n - dispatch on event.kind for atom vs resource\n 4. ctx.exec(flow) → ext.wrapExec(next, flow, ctx)\n - call next() to proceed to actual execution\n 5. scope.dispose() → ext.dispose(scope) called for each extension\n\nExample:\n const timingExt: Extension = {\n wrapResolve: async (next, event) => {\n const start = Date.now()\n const value = await next()\n console.log(event.target, Date.now() - start, \"ms\")\n return value\n },\n }`,\n },\n\n testing: {\n title: \"Testing & Isolation\",\n content: `Use presets to swap implementations without changing production code.\n\nimport { createScope, preset } from \"@pumped-fn/lite\"\n\nconst scope = createScope({\n presets: [\n preset(dbAtom, mockDatabase),\n preset(cacheAtom, inMemoryCache),\n ],\n tags: [tenantTag(\"test-tenant\")],\n})\n\nconst db = await scope.resolve(dbAtom) // → mockDatabase (not real db)\n\nMulti-tenant isolation:\n Each scope is fully isolated. Create one scope per tenant/test.\n\n const tenantScope = createScope({\n tags: [tenantTag(tenantId)],\n presets: tenantOverrides,\n })\n\nCleanup:\n scope.dispose() releases all atoms and runs all cleanup functions.\n In tests: call scope.dispose() in afterEach.`,\n },\n\n patterns: {\n title: \"Common Patterns\",\n content: `Request lifecycle:\n const scope = createScope()\n const ctx = scope.createContext({ tags: [requestTag(req)] })\n const result = await ctx.exec({ flow: handleRequest, input: req.body })\n ctx.close() // cleanup LIFO\n\nService pattern:\n const userService = service({\n deps: { db: dbAtom },\n factory: (ctx, { db }) => ({\n getUser: (ctx, id) => db.findUser(id),\n updateUser: (ctx, id, data) => db.updateUser(id, data),\n }),\n })\n\nTyped flow input:\n const getUser = flow({\n parse: typed<{ id: string }>(),\n factory: (ctx) => findUser(ctx.input.id),\n })\n\nInline execution:\n const result = await ctx.exec({\n fn: (ctx, a, b) => a + b,\n params: [1, 2],\n })\n\nAtom with cleanup:\n const serverAtom = atom({\n factory: (ctx) => {\n const server = createServer()\n ctx.onClose(() => server.close())\n return server\n },\n })\n\nAtom retention / GC:\n createScope({ gc: { enabled: true, graceMs: 3000 } })\n atom({ keepAlive: true }) // never GC'd`,\n },\n\n diagrams: {\n title: \"Visual Diagrams (mermaid)\",\n content: extractDiagram,\n },\n\n types: {\n title: \"Type Utilities & Guards\",\n content: `Type extractors (Lite.Utils namespace):\n AtomValue<A> extract resolved type from atom\n FlowOutput<F> extract output type from flow\n FlowInput<F> extract input type from flow\n TagValue<T> extract value type from tag\n DepsOf<A | F> extract deps record type\n ControllerValue<C> extract value from controller\n Simplify<T> flatten intersection types\n AtomType<T, D> construct atom type\n FlowType<O, I, D> construct flow type\n\nType guards:\n isAtom(v) → v is Atom\n isFlow(v) → v is Flow\n isTag(v) → v is Tag\n isTagged(v) → v is Tagged\n isPreset(v) → v is Preset\n isControllerDep(v) → v is ControllerDep\n isTagExecutor(v) → v is TagExecutor\n\nConvenience types:\n AnyAtom any atom regardless of value/deps\n AnyFlow any flow regardless of output/input/deps\n AnyController any controller regardless of value\n\nSymbols (advanced, for library authors):\n atomSymbol, flowSymbol, tagSymbol, taggedSymbol,\n presetSymbol, controllerSymbol, controllerDepSymbol,\n tagExecutorSymbol, typedSymbol`,\n },\n}\n\nconst args = process.argv.slice(2)\nconst category = args[0]\n\nif (!category || category === \"help\" || category === \"--help\") {\n console.log(\"@pumped-fn/lite — Scoped Ambient State for TypeScript\\n\")\n console.log(\"Usage: pumped-lite <category>\\n\")\n console.log(\"Categories:\")\n for (const [key, { title }] of Object.entries(categories)) {\n console.log(` ${key.padEnd(14)} ${title}`)\n }\n console.log(\"\\nExamples:\")\n console.log(\" npx @pumped-fn/lite primitives # API reference\")\n console.log(\" npx @pumped-fn/lite diagrams # mermaid diagrams\")\n process.exit(0)\n}\n\nif (!(category in categories)) {\n console.error(`Unknown category: \"${category}\"\\n`)\n console.error(\"Available categories: \" + Object.keys(categories).join(\", \"))\n process.exit(1)\n}\n\nconst entry = categories[category]!\nconst output = typeof entry.content === \"function\" ? entry.content() : entry.content\nconsole.log(`# ${entry.title}\\n`)\nconsole.log(output)\n"],"mappings":";;;;;;AAOA,MAAM,SAAS,aAAa,KADb,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,KAAK,EACzB,YAAY,EAAE,QAAQ;AAE/D,SAAS,kBAA0B;CACjC,MAAM,MAAM,OAAO,QAAQ,kBAAkB;AAE7C,SADY,QAAQ,KAAK,SAAS,OAAO,MAAM,GAAG,IAAI,EAC3C,QAAQ,eAAe,GAAG,CAAC,MAAM;;AAG9C,SAAS,iBAAyB;CAChC,MAAM,QAAQ,OAAO,MAAM,4BAA4B;AACvD,QAAO,QAAQ,qDAAqD,MAAM,GAAI,MAAM,CAAC,YAAY;;AAGnG,MAAMA,aAAkF;CACtF,UAAU;EACR,OAAO;EACP,SAAS;EACV;CAED,YAAY;EACV,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCV;CAED,OAAO;EACL,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;EAoBV;CAED,SAAS;EACP,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;EAoBV;CAED,YAAY;EACV,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BV;CAED,MAAM;EACJ,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BV;CAED,YAAY;EACV,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BV;CAED,SAAS;EACP,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;EAyBV;CAED,UAAU;EACR,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuCV;CAED,UAAU;EACR,OAAO;EACP,SAAS;EACV;CAED,OAAO;EACL,OAAO;EACP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BV;CACF;AAGD,MAAM,WADO,QAAQ,KAAK,MAAM,EAAE,CACZ;AAEtB,IAAI,CAAC,YAAY,aAAa,UAAU,aAAa,UAAU;AAC7D,SAAQ,IAAI,0DAA0D;AACtE,SAAQ,IAAI,kCAAkC;AAC9C,SAAQ,IAAI,cAAc;AAC1B,MAAK,MAAM,CAAC,KAAK,EAAE,YAAY,OAAO,QAAQ,WAAW,CACvD,SAAQ,IAAI,KAAK,IAAI,OAAO,GAAG,CAAC,GAAG,QAAQ;AAE7C,SAAQ,IAAI,cAAc;AAC1B,SAAQ,IAAI,qDAAqD;AACjE,SAAQ,IAAI,wDAAwD;AACpE,SAAQ,KAAK,EAAE;;AAGjB,IAAI,EAAE,YAAY,aAAa;AAC7B,SAAQ,MAAM,sBAAsB,SAAS,KAAK;AAClD,SAAQ,MAAM,2BAA2B,OAAO,KAAK,WAAW,CAAC,KAAK,KAAK,CAAC;AAC5E,SAAQ,KAAK,EAAE;;AAGjB,MAAM,QAAQ,WAAW;AACzB,MAAM,SAAS,OAAO,MAAM,YAAY,aAAa,MAAM,SAAS,GAAG,MAAM;AAC7E,QAAQ,IAAI,KAAK,MAAM,MAAM,IAAI;AACjC,QAAQ,IAAI,OAAO"}
|
package/dist/index.cjs
CHANGED
|
@@ -9,6 +9,7 @@ const presetSymbol = Symbol.for("@pumped-fn/lite/preset");
|
|
|
9
9
|
const controllerSymbol = Symbol.for("@pumped-fn/lite/controller");
|
|
10
10
|
const tagExecutorSymbol = Symbol.for("@pumped-fn/lite/tag-executor");
|
|
11
11
|
const typedSymbol = Symbol.for("@pumped-fn/lite/typed");
|
|
12
|
+
const resourceSymbol = Symbol.for("@pumped-fn/lite/resource");
|
|
12
13
|
|
|
13
14
|
//#endregion
|
|
14
15
|
//#region src/errors.ts
|
|
@@ -262,20 +263,33 @@ function isAtom(value) {
|
|
|
262
263
|
* The Controller provides full lifecycle control: get, resolve, release, invalidate, and subscribe.
|
|
263
264
|
*
|
|
264
265
|
* @param atom - The Atom to wrap
|
|
265
|
-
* @param options - Optional configuration
|
|
266
|
+
* @param options - Optional configuration:
|
|
267
|
+
* - `resolve: true` — auto-resolves the dep before the parent factory runs; `config.get()` is safe.
|
|
268
|
+
* - `watch: true` — atom deps only; requires `resolve: true`; automatically re-runs the parent factory
|
|
269
|
+
* when the dep resolves to a new value (value-equality gated via `Object.is` by default). Replaces
|
|
270
|
+
* manual `ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))` wiring. Watch
|
|
271
|
+
* listeners are auto-cleaned on re-resolve, release, and dispose.
|
|
272
|
+
* - `eq` — custom equality function `(a: T, b: T) => boolean`; only used with `watch: true`.
|
|
266
273
|
* @returns A ControllerDep that resolves to a Controller for the Atom
|
|
267
274
|
*
|
|
268
275
|
* @example
|
|
269
276
|
* ```typescript
|
|
270
|
-
*
|
|
277
|
+
* // resolve only
|
|
271
278
|
* const serverAtom = atom({
|
|
272
279
|
* deps: { config: controller(configAtom, { resolve: true }) },
|
|
273
|
-
* factory: (
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* }
|
|
280
|
+
* factory: (_, { config }) => createServer(config.get().port),
|
|
281
|
+
* })
|
|
282
|
+
*
|
|
283
|
+
* // watch: re-runs parent when dep value changes
|
|
284
|
+
* const profileAtom = atom({
|
|
285
|
+
* deps: { token: controller(tokenAtom, { resolve: true, watch: true }) },
|
|
286
|
+
* factory: (_, { token }) => ({ id: `user-${token.get().jwt}` }),
|
|
287
|
+
* })
|
|
288
|
+
*
|
|
289
|
+
* // watch with custom equality
|
|
290
|
+
* const derivedAtom = atom({
|
|
291
|
+
* deps: { src: controller(srcAtom, { resolve: true, watch: true, eq: (a, b) => a.id === b.id }) },
|
|
292
|
+
* factory: (_, { src }) => src.get().name,
|
|
279
293
|
* })
|
|
280
294
|
* ```
|
|
281
295
|
*/
|
|
@@ -283,7 +297,9 @@ function controller(atom$1, options) {
|
|
|
283
297
|
return {
|
|
284
298
|
[controllerDepSymbol]: true,
|
|
285
299
|
atom: atom$1,
|
|
286
|
-
resolve: options?.resolve
|
|
300
|
+
resolve: options?.resolve,
|
|
301
|
+
watch: options?.watch,
|
|
302
|
+
eq: options?.eq
|
|
287
303
|
};
|
|
288
304
|
}
|
|
289
305
|
/**
|
|
@@ -379,6 +395,33 @@ function isPreset(value) {
|
|
|
379
395
|
return typeof value === "object" && value !== null && value[presetSymbol] === true;
|
|
380
396
|
}
|
|
381
397
|
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/resource.ts
|
|
400
|
+
function resource(config) {
|
|
401
|
+
return Object.freeze({
|
|
402
|
+
[resourceSymbol]: true,
|
|
403
|
+
name: config.name,
|
|
404
|
+
deps: config.deps,
|
|
405
|
+
factory: config.factory
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Type guard to check if a value is a Resource.
|
|
410
|
+
*
|
|
411
|
+
* @param value - The value to check
|
|
412
|
+
* @returns True if the value is a Resource, false otherwise
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* if (isResource(value)) {
|
|
417
|
+
* // value is Lite.Resource<unknown>
|
|
418
|
+
* }
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
function isResource(value) {
|
|
422
|
+
return typeof value === "object" && value !== null && value[resourceSymbol] === true;
|
|
423
|
+
}
|
|
424
|
+
|
|
382
425
|
//#endregion
|
|
383
426
|
//#region src/service.ts
|
|
384
427
|
function service(config) {
|
|
@@ -392,6 +435,18 @@ function service(config) {
|
|
|
392
435
|
|
|
393
436
|
//#endregion
|
|
394
437
|
//#region src/scope.ts
|
|
438
|
+
const resourceKeys = /* @__PURE__ */ new WeakMap();
|
|
439
|
+
let resourceKeyCounter = 0;
|
|
440
|
+
function getResourceKey(resource$1) {
|
|
441
|
+
let key = resourceKeys.get(resource$1);
|
|
442
|
+
if (!key) {
|
|
443
|
+
key = Symbol(`resource:${resource$1.name ?? resourceKeyCounter++}`);
|
|
444
|
+
resourceKeys.set(resource$1, key);
|
|
445
|
+
}
|
|
446
|
+
return key;
|
|
447
|
+
}
|
|
448
|
+
const inflightResources = /* @__PURE__ */ new WeakMap();
|
|
449
|
+
const resolvingResources = /* @__PURE__ */ new Set();
|
|
395
450
|
var ContextDataImpl = class {
|
|
396
451
|
map = /* @__PURE__ */ new Map();
|
|
397
452
|
constructor(parentData) {
|
|
@@ -731,6 +786,8 @@ var ScopeImpl = class {
|
|
|
731
786
|
async doResolve(atom$1) {
|
|
732
787
|
const entry = this.getOrCreateEntry(atom$1);
|
|
733
788
|
if (!(entry.state === "resolving")) {
|
|
789
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) await entry.cleanups[i]?.();
|
|
790
|
+
entry.cleanups = [];
|
|
734
791
|
entry.state = "resolving";
|
|
735
792
|
this.emitStateChange("resolving", atom$1);
|
|
736
793
|
this.notifyListeners(atom$1, "resolving");
|
|
@@ -753,7 +810,12 @@ var ScopeImpl = class {
|
|
|
753
810
|
else return factory(ctx);
|
|
754
811
|
};
|
|
755
812
|
try {
|
|
756
|
-
const
|
|
813
|
+
const event = {
|
|
814
|
+
kind: "atom",
|
|
815
|
+
target: atom$1,
|
|
816
|
+
scope: this
|
|
817
|
+
};
|
|
818
|
+
const value = await this.applyResolveExtensions(event, doResolve);
|
|
757
819
|
entry.state = "resolved";
|
|
758
820
|
entry.value = value;
|
|
759
821
|
entry.hasValue = true;
|
|
@@ -784,13 +846,13 @@ var ScopeImpl = class {
|
|
|
784
846
|
throw entry.error;
|
|
785
847
|
}
|
|
786
848
|
}
|
|
787
|
-
async applyResolveExtensions(
|
|
849
|
+
async applyResolveExtensions(event, doResolve) {
|
|
788
850
|
let next = doResolve;
|
|
789
851
|
for (let i = this.extensions.length - 1; i >= 0; i--) {
|
|
790
852
|
const ext = this.extensions[i];
|
|
791
853
|
if (ext?.wrapResolve) {
|
|
792
854
|
const currentNext = next;
|
|
793
|
-
next = ext.wrapResolve.bind(ext, currentNext,
|
|
855
|
+
next = ext.wrapResolve.bind(ext, currentNext, event);
|
|
794
856
|
}
|
|
795
857
|
}
|
|
796
858
|
return next();
|
|
@@ -805,13 +867,31 @@ var ScopeImpl = class {
|
|
|
805
867
|
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
806
868
|
}
|
|
807
869
|
} else if (isControllerDep(dep)) {
|
|
808
|
-
|
|
870
|
+
if (dep.watch) {
|
|
871
|
+
if (!dependentAtom) throw new Error("controller({ watch: true }) is only supported in atom dependencies");
|
|
872
|
+
if (!dep.resolve) throw new Error("controller({ watch: true }) requires resolve: true");
|
|
873
|
+
}
|
|
874
|
+
const ctrl = this.controller(dep.atom);
|
|
809
875
|
if (dep.resolve) await ctrl.resolve();
|
|
810
876
|
result[key] = ctrl;
|
|
811
877
|
if (dependentAtom) {
|
|
812
878
|
const depEntry = this.getEntry(dep.atom);
|
|
813
879
|
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
814
880
|
}
|
|
881
|
+
if (dep.watch) {
|
|
882
|
+
const eq = dep.eq ?? Object.is;
|
|
883
|
+
let prev = ctrl.get();
|
|
884
|
+
const unsub = this.on("resolved", dep.atom, () => {
|
|
885
|
+
const next = ctrl.get();
|
|
886
|
+
if (!eq(prev, next)) {
|
|
887
|
+
prev = next;
|
|
888
|
+
this.scheduleInvalidation(dependentAtom);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
const depEntry = this.getEntry(dependentAtom);
|
|
892
|
+
if (depEntry) depEntry.cleanups.push(unsub);
|
|
893
|
+
else unsub();
|
|
894
|
+
}
|
|
815
895
|
} else if (tagExecutorSymbol in dep) {
|
|
816
896
|
const tagExecutor = dep;
|
|
817
897
|
switch (tagExecutor.mode) {
|
|
@@ -829,6 +909,59 @@ var ScopeImpl = class {
|
|
|
829
909
|
result[key] = ctx ? this.collectFromHierarchy(ctx, tagExecutor.tag) : tagExecutor.tag.collect(this.tags);
|
|
830
910
|
break;
|
|
831
911
|
}
|
|
912
|
+
} else if (isResource(dep)) {
|
|
913
|
+
if (!ctx) throw new Error("Resource deps require an ExecutionContext");
|
|
914
|
+
const resource$1 = dep;
|
|
915
|
+
const resourceKey = getResourceKey(resource$1);
|
|
916
|
+
const storeCtx = ctx.parent ?? ctx;
|
|
917
|
+
if (storeCtx.data.has(resourceKey)) {
|
|
918
|
+
result[key] = storeCtx.data.get(resourceKey);
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
const existingSeek = ctx.data.seek(resourceKey);
|
|
922
|
+
if (existingSeek !== void 0 || ctx.data.has(resourceKey)) {
|
|
923
|
+
result[key] = existingSeek;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (resolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
|
|
927
|
+
let flights = inflightResources.get(storeCtx.data);
|
|
928
|
+
if (!flights) {
|
|
929
|
+
flights = /* @__PURE__ */ new Map();
|
|
930
|
+
inflightResources.set(storeCtx.data, flights);
|
|
931
|
+
}
|
|
932
|
+
const inflight = flights.get(resourceKey);
|
|
933
|
+
if (inflight) {
|
|
934
|
+
result[key] = await inflight;
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
const resolve = async () => {
|
|
938
|
+
resolvingResources.add(resourceKey);
|
|
939
|
+
try {
|
|
940
|
+
const resourceDeps = await this.resolveDeps(resource$1.deps, ctx);
|
|
941
|
+
const event = {
|
|
942
|
+
kind: "resource",
|
|
943
|
+
target: resource$1,
|
|
944
|
+
ctx: storeCtx
|
|
945
|
+
};
|
|
946
|
+
const doResolve = async () => {
|
|
947
|
+
const factory = resource$1.factory;
|
|
948
|
+
if (resource$1.deps && Object.keys(resource$1.deps).length > 0) return factory(storeCtx, resourceDeps);
|
|
949
|
+
return factory(storeCtx);
|
|
950
|
+
};
|
|
951
|
+
const value = await this.applyResolveExtensions(event, doResolve);
|
|
952
|
+
storeCtx.data.set(resourceKey, value);
|
|
953
|
+
return value;
|
|
954
|
+
} finally {
|
|
955
|
+
resolvingResources.delete(resourceKey);
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
const promise = resolve();
|
|
959
|
+
flights.set(resourceKey, promise);
|
|
960
|
+
try {
|
|
961
|
+
result[key] = await promise;
|
|
962
|
+
} finally {
|
|
963
|
+
flights.delete(resourceKey);
|
|
964
|
+
}
|
|
832
965
|
}
|
|
833
966
|
return result;
|
|
834
967
|
}
|
|
@@ -1006,10 +1139,15 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1006
1139
|
for (const tagged of execTags ?? []) childCtx.data.set(tagged.key, tagged.value);
|
|
1007
1140
|
for (const tagged of flow$1.tags ?? []) if (!childCtx.data.has(tagged.key)) childCtx.data.set(tagged.key, tagged.value);
|
|
1008
1141
|
try {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1142
|
+
const result = presetValue !== void 0 && typeof presetValue === "function" ? await childCtx.execPresetFn(flow$1, presetValue) : await childCtx.execFlowInternal(flow$1);
|
|
1143
|
+
await childCtx.close({ ok: true });
|
|
1144
|
+
return result;
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
await childCtx.close({
|
|
1147
|
+
ok: false,
|
|
1148
|
+
error
|
|
1149
|
+
});
|
|
1150
|
+
throw error;
|
|
1013
1151
|
}
|
|
1014
1152
|
} else {
|
|
1015
1153
|
const childCtx = new ExecutionContextImpl(this.scope, {
|
|
@@ -1019,9 +1157,15 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1019
1157
|
input: options.params
|
|
1020
1158
|
});
|
|
1021
1159
|
try {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1160
|
+
const result = await childCtx.execFnInternal(options);
|
|
1161
|
+
await childCtx.close({ ok: true });
|
|
1162
|
+
return result;
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
await childCtx.close({
|
|
1165
|
+
ok: false,
|
|
1166
|
+
error
|
|
1167
|
+
});
|
|
1168
|
+
throw error;
|
|
1025
1169
|
}
|
|
1026
1170
|
}
|
|
1027
1171
|
}
|
|
@@ -1057,12 +1201,12 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1057
1201
|
onClose(fn) {
|
|
1058
1202
|
this.cleanups.push(fn);
|
|
1059
1203
|
}
|
|
1060
|
-
async close() {
|
|
1204
|
+
async close(result = { ok: true }) {
|
|
1061
1205
|
if (this.closed) return;
|
|
1062
1206
|
this.closed = true;
|
|
1063
1207
|
for (let i = this.cleanups.length - 1; i >= 0; i--) {
|
|
1064
1208
|
const cleanup = this.cleanups[i];
|
|
1065
|
-
if (cleanup) await cleanup();
|
|
1209
|
+
if (cleanup) await cleanup(result);
|
|
1066
1210
|
}
|
|
1067
1211
|
}
|
|
1068
1212
|
};
|
|
@@ -1115,11 +1259,14 @@ exports.isAtom = isAtom;
|
|
|
1115
1259
|
exports.isControllerDep = isControllerDep;
|
|
1116
1260
|
exports.isFlow = isFlow;
|
|
1117
1261
|
exports.isPreset = isPreset;
|
|
1262
|
+
exports.isResource = isResource;
|
|
1118
1263
|
exports.isTag = isTag;
|
|
1119
1264
|
exports.isTagExecutor = isTagExecutor;
|
|
1120
1265
|
exports.isTagged = isTagged;
|
|
1121
1266
|
exports.preset = preset;
|
|
1122
1267
|
exports.presetSymbol = presetSymbol;
|
|
1268
|
+
exports.resource = resource;
|
|
1269
|
+
exports.resourceSymbol = resourceSymbol;
|
|
1123
1270
|
exports.service = service;
|
|
1124
1271
|
exports.tag = tag;
|
|
1125
1272
|
exports.tagExecutorSymbol = tagExecutorSymbol;
|
package/dist/index.d.cts
CHANGED
|
@@ -8,6 +8,7 @@ declare const presetSymbol: unique symbol;
|
|
|
8
8
|
declare const controllerSymbol: unique symbol;
|
|
9
9
|
declare const tagExecutorSymbol: unique symbol;
|
|
10
10
|
declare const typedSymbol: unique symbol;
|
|
11
|
+
declare const resourceSymbol: unique symbol;
|
|
11
12
|
//#endregion
|
|
12
13
|
//#region src/types.d.ts
|
|
13
14
|
type MaybePromise<T> = T | Promise<T>;
|
|
@@ -58,6 +59,12 @@ declare namespace Lite {
|
|
|
58
59
|
readonly deps?: Record<string, Dependency>;
|
|
59
60
|
readonly tags?: Tagged<any>[];
|
|
60
61
|
}
|
|
62
|
+
interface Resource<T, D extends Record<string, Dependency> = Record<string, Dependency>> {
|
|
63
|
+
readonly [resourceSymbol]: true;
|
|
64
|
+
readonly name?: string;
|
|
65
|
+
readonly deps?: D;
|
|
66
|
+
readonly factory: ResourceFactory<T, D>;
|
|
67
|
+
}
|
|
61
68
|
/**
|
|
62
69
|
* Unified context data storage with both raw Map operations and Tag-based DX.
|
|
63
70
|
*/
|
|
@@ -112,6 +119,12 @@ declare namespace Lite {
|
|
|
112
119
|
readonly scope: Scope;
|
|
113
120
|
readonly data: ContextData;
|
|
114
121
|
}
|
|
122
|
+
type CloseResult = {
|
|
123
|
+
ok: true;
|
|
124
|
+
} | {
|
|
125
|
+
ok: false;
|
|
126
|
+
error: unknown;
|
|
127
|
+
};
|
|
115
128
|
interface ExecutionContext {
|
|
116
129
|
readonly input: unknown;
|
|
117
130
|
readonly name: string | undefined;
|
|
@@ -120,8 +133,8 @@ declare namespace Lite {
|
|
|
120
133
|
readonly data: ContextData;
|
|
121
134
|
exec<Output, Input>(options: ExecFlowOptions<Output, Input>): Promise<Output>;
|
|
122
135
|
exec<Output, Args extends unknown[]>(options: ExecFnOptions<Output, Args>): Promise<Output>;
|
|
123
|
-
onClose(fn: () => MaybePromise<void>): void;
|
|
124
|
-
close(): Promise<void>;
|
|
136
|
+
onClose(fn: (result: CloseResult) => MaybePromise<void>): void;
|
|
137
|
+
close(result?: CloseResult): Promise<void>;
|
|
125
138
|
}
|
|
126
139
|
type ExecFlowOptions<Output, Input> = {
|
|
127
140
|
flow: Flow<Output, Input>;
|
|
@@ -217,10 +230,17 @@ declare namespace Lite {
|
|
|
217
230
|
readonly [controllerDepSymbol]: true;
|
|
218
231
|
readonly atom: Atom<T>;
|
|
219
232
|
readonly resolve?: boolean;
|
|
233
|
+
readonly watch?: boolean;
|
|
234
|
+
readonly eq?: (a: any, b: any) => boolean;
|
|
220
235
|
}
|
|
221
236
|
interface ControllerOptions {
|
|
222
237
|
resolve?: boolean;
|
|
223
238
|
}
|
|
239
|
+
interface ControllerDepOptions<T> {
|
|
240
|
+
resolve?: boolean;
|
|
241
|
+
watch?: boolean;
|
|
242
|
+
eq?: (a: T, b: T) => boolean;
|
|
243
|
+
}
|
|
224
244
|
interface Typed<T> {
|
|
225
245
|
readonly [typedSymbol]: true;
|
|
226
246
|
}
|
|
@@ -233,15 +253,38 @@ declare namespace Lite {
|
|
|
233
253
|
readonly target: PresetTarget<T, I>;
|
|
234
254
|
readonly value: PresetValue<T, I>;
|
|
235
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Discriminated context for `wrapResolve`.
|
|
258
|
+
*
|
|
259
|
+
* - `"atom"` — scope-level singleton. Cached after first resolve.
|
|
260
|
+
* - `"resource"` — execution-level. Fresh factory per first encounter,
|
|
261
|
+
* seek-up on nested execs within the same chain.
|
|
262
|
+
*/
|
|
263
|
+
type ResolveEvent = {
|
|
264
|
+
readonly kind: "atom";
|
|
265
|
+
readonly target: Atom<unknown>;
|
|
266
|
+
readonly scope: Scope;
|
|
267
|
+
} | {
|
|
268
|
+
readonly kind: "resource";
|
|
269
|
+
readonly target: Resource<unknown>;
|
|
270
|
+
readonly ctx: ExecutionContext;
|
|
271
|
+
};
|
|
236
272
|
interface Extension {
|
|
237
273
|
readonly name: string;
|
|
238
274
|
init?(scope: Scope): MaybePromise<void>;
|
|
239
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Wraps dependency resolution. Dispatch by `event.kind`:
|
|
277
|
+
*
|
|
278
|
+
* - `"atom"` — `event.scope`, `event.target: Atom`. Cached in scope.
|
|
279
|
+
* - `"resource"` — `event.ctx`, `event.target: Resource`. Seek-up in
|
|
280
|
+
* execution hierarchy, factory(ctx, deps) on miss.
|
|
281
|
+
*/
|
|
282
|
+
wrapResolve?(next: () => Promise<unknown>, event: ResolveEvent): Promise<unknown>;
|
|
240
283
|
wrapExec?(next: () => Promise<unknown>, target: ExecTarget, ctx: ExecutionContext): Promise<unknown>;
|
|
241
284
|
dispose?(scope: Scope): MaybePromise<void>;
|
|
242
285
|
}
|
|
243
|
-
type Dependency = Atom<unknown> | ControllerDep<unknown> | TagExecutor<any>;
|
|
244
|
-
type InferDep<D> = D extends Atom<infer T> ? T : D extends ControllerDep<infer T> ? Controller<T> : D extends TagExecutor<infer TOutput, infer _TTag> ? TOutput : never;
|
|
286
|
+
type Dependency = Atom<unknown> | ControllerDep<unknown> | TagExecutor<any> | Resource<unknown>;
|
|
287
|
+
type InferDep<D> = D extends Atom<infer T> ? T : D extends ControllerDep<infer T> ? Controller<T> : D extends TagExecutor<infer TOutput, infer _TTag> ? TOutput : D extends Resource<infer T> ? T : never;
|
|
245
288
|
type InferDeps<D> = { [K in keyof D]: InferDep<D[K]> };
|
|
246
289
|
type AtomFactory<T, D extends Record<string, Dependency>> = keyof D extends never ? (ctx: ResolveContext) => MaybePromise<T> : (ctx: ResolveContext, deps: InferDeps<D>) => MaybePromise<T>;
|
|
247
290
|
type FlowFactory<Output, Input, D extends Record<string, Dependency>> = keyof D extends never ? (ctx: ExecutionContext & {
|
|
@@ -249,6 +292,7 @@ declare namespace Lite {
|
|
|
249
292
|
}) => MaybePromise<Output> : (ctx: ExecutionContext & {
|
|
250
293
|
readonly input: Input;
|
|
251
294
|
}, deps: InferDeps<D>) => MaybePromise<Output>;
|
|
295
|
+
type ResourceFactory<T, D extends Record<string, Dependency>> = keyof D extends never ? (ctx: ExecutionContext) => MaybePromise<T> : (ctx: ExecutionContext, deps: InferDeps<D>) => MaybePromise<T>;
|
|
252
296
|
type ServiceMethod = (ctx: ExecutionContext, ...args: any[]) => unknown;
|
|
253
297
|
type ServiceMethods = Record<string, ServiceMethod>;
|
|
254
298
|
/**
|
|
@@ -265,6 +309,10 @@ declare namespace Lite {
|
|
|
265
309
|
* Any controller regardless of value type.
|
|
266
310
|
*/
|
|
267
311
|
type AnyController = Controller<any>;
|
|
312
|
+
/**
|
|
313
|
+
* Any resource regardless of value type.
|
|
314
|
+
*/
|
|
315
|
+
type AnyResource = Resource<any>;
|
|
268
316
|
/**
|
|
269
317
|
* Target type for wrapExec extension hook.
|
|
270
318
|
* Either a Flow or an inline function.
|
|
@@ -531,24 +579,37 @@ declare function isAtom(value: unknown): value is Lite.Atom<unknown>;
|
|
|
531
579
|
* The Controller provides full lifecycle control: get, resolve, release, invalidate, and subscribe.
|
|
532
580
|
*
|
|
533
581
|
* @param atom - The Atom to wrap
|
|
534
|
-
* @param options - Optional configuration
|
|
582
|
+
* @param options - Optional configuration:
|
|
583
|
+
* - `resolve: true` — auto-resolves the dep before the parent factory runs; `config.get()` is safe.
|
|
584
|
+
* - `watch: true` — atom deps only; requires `resolve: true`; automatically re-runs the parent factory
|
|
585
|
+
* when the dep resolves to a new value (value-equality gated via `Object.is` by default). Replaces
|
|
586
|
+
* manual `ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))` wiring. Watch
|
|
587
|
+
* listeners are auto-cleaned on re-resolve, release, and dispose.
|
|
588
|
+
* - `eq` — custom equality function `(a: T, b: T) => boolean`; only used with `watch: true`.
|
|
535
589
|
* @returns A ControllerDep that resolves to a Controller for the Atom
|
|
536
590
|
*
|
|
537
591
|
* @example
|
|
538
592
|
* ```typescript
|
|
539
|
-
*
|
|
593
|
+
* // resolve only
|
|
540
594
|
* const serverAtom = atom({
|
|
541
595
|
* deps: { config: controller(configAtom, { resolve: true }) },
|
|
542
|
-
* factory: (
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
* }
|
|
596
|
+
* factory: (_, { config }) => createServer(config.get().port),
|
|
597
|
+
* })
|
|
598
|
+
*
|
|
599
|
+
* // watch: re-runs parent when dep value changes
|
|
600
|
+
* const profileAtom = atom({
|
|
601
|
+
* deps: { token: controller(tokenAtom, { resolve: true, watch: true }) },
|
|
602
|
+
* factory: (_, { token }) => ({ id: `user-${token.get().jwt}` }),
|
|
603
|
+
* })
|
|
604
|
+
*
|
|
605
|
+
* // watch with custom equality
|
|
606
|
+
* const derivedAtom = atom({
|
|
607
|
+
* deps: { src: controller(srcAtom, { resolve: true, watch: true, eq: (a, b) => a.id === b.id }) },
|
|
608
|
+
* factory: (_, { src }) => src.get().name,
|
|
548
609
|
* })
|
|
549
610
|
* ```
|
|
550
611
|
*/
|
|
551
|
-
declare function controller<T>(atom: Lite.Atom<T>, options?: Lite.
|
|
612
|
+
declare function controller<T>(atom: Lite.Atom<T>, options?: Lite.ControllerDepOptions<T>): Lite.ControllerDep<T>;
|
|
552
613
|
/**
|
|
553
614
|
* Type guard to check if a value is a ControllerDep wrapper.
|
|
554
615
|
*
|
|
@@ -621,7 +682,7 @@ declare function flow<TOutput, TInput>(config: {
|
|
|
621
682
|
}) => MaybePromise<TOutput>;
|
|
622
683
|
tags?: Lite.Tagged<any>[];
|
|
623
684
|
}): Lite.Flow<TOutput, TInput>;
|
|
624
|
-
declare function flow<TOutput, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | {
|
|
685
|
+
declare function flow<TOutput, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | Lite.Resource<unknown, Record<string, Lite.Dependency>> | {
|
|
625
686
|
mode: string;
|
|
626
687
|
}>>(config: {
|
|
627
688
|
name?: string;
|
|
@@ -630,7 +691,7 @@ declare function flow<TOutput, const D extends Record<string, Lite.Atom<unknown>
|
|
|
630
691
|
factory: (ctx: Lite.ExecutionContext, deps: Lite.InferDeps<D>) => MaybePromise<TOutput>;
|
|
631
692
|
tags?: Lite.Tagged<any>[];
|
|
632
693
|
}): Lite.Flow<TOutput, void>;
|
|
633
|
-
declare function flow<TOutput, TInput, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | {
|
|
694
|
+
declare function flow<TOutput, TInput, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | Lite.Resource<unknown, Record<string, Lite.Dependency>> | {
|
|
634
695
|
mode: string;
|
|
635
696
|
}>>(config: {
|
|
636
697
|
name?: string;
|
|
@@ -641,7 +702,7 @@ declare function flow<TOutput, TInput, const D extends Record<string, Lite.Atom<
|
|
|
641
702
|
}, deps: Lite.InferDeps<D>) => MaybePromise<TOutput>;
|
|
642
703
|
tags?: Lite.Tagged<any>[];
|
|
643
704
|
}): Lite.Flow<TOutput, TInput>;
|
|
644
|
-
declare function flow<TOutput, TInput, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | {
|
|
705
|
+
declare function flow<TOutput, TInput, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | Lite.Resource<unknown, Record<string, Lite.Dependency>> | {
|
|
645
706
|
mode: string;
|
|
646
707
|
}>>(config: {
|
|
647
708
|
name?: string;
|
|
@@ -721,6 +782,53 @@ declare function preset<TOutput, TInput>(target: Lite.Flow<TOutput, TInput>, val
|
|
|
721
782
|
*/
|
|
722
783
|
declare function isPreset(value: unknown): value is Lite.Preset<unknown>;
|
|
723
784
|
//#endregion
|
|
785
|
+
//#region src/resource.d.ts
|
|
786
|
+
/**
|
|
787
|
+
* Creates an execution-scoped dependency that is resolved per execution chain.
|
|
788
|
+
* Fresh instance on first encounter, seek-up on nested execs within the same chain.
|
|
789
|
+
*
|
|
790
|
+
* @param config - Configuration object containing factory function and optional dependencies
|
|
791
|
+
* @returns A Resource instance that can be declared as a dependency in flows and other resources
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```typescript
|
|
795
|
+
* const requestLogger = resource({
|
|
796
|
+
* deps: { logService: logServiceAtom },
|
|
797
|
+
* factory: (ctx, { logService }) => {
|
|
798
|
+
* const logger = logService.child({ requestId: ctx.data.get("requestId") })
|
|
799
|
+
* ctx.onClose(() => logger.flush())
|
|
800
|
+
* return logger
|
|
801
|
+
* }
|
|
802
|
+
* })
|
|
803
|
+
* ```
|
|
804
|
+
*/
|
|
805
|
+
declare function resource<T>(config: {
|
|
806
|
+
name?: string;
|
|
807
|
+
deps?: undefined;
|
|
808
|
+
factory: (ctx: Lite.ExecutionContext) => MaybePromise<T>;
|
|
809
|
+
}): Lite.Resource<T>;
|
|
810
|
+
declare function resource<T, const D extends Record<string, Lite.Atom<unknown> | Lite.ControllerDep<unknown> | Lite.Resource<unknown, Record<string, Lite.Dependency>> | {
|
|
811
|
+
mode: string;
|
|
812
|
+
}>>(config: {
|
|
813
|
+
name?: string;
|
|
814
|
+
deps: D;
|
|
815
|
+
factory: (ctx: Lite.ExecutionContext, deps: Lite.InferDeps<D>) => MaybePromise<T>;
|
|
816
|
+
}): Lite.Resource<T>;
|
|
817
|
+
/**
|
|
818
|
+
* Type guard to check if a value is a Resource.
|
|
819
|
+
*
|
|
820
|
+
* @param value - The value to check
|
|
821
|
+
* @returns True if the value is a Resource, false otherwise
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```typescript
|
|
825
|
+
* if (isResource(value)) {
|
|
826
|
+
* // value is Lite.Resource<unknown>
|
|
827
|
+
* }
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
declare function isResource(value: unknown): value is Lite.Resource<unknown>;
|
|
831
|
+
//#endregion
|
|
724
832
|
//#region src/service.d.ts
|
|
725
833
|
/** Creates an atom with methods constrained to (ctx: ExecutionContext, ...args) => result. */
|
|
726
834
|
declare function service<T extends Lite.ServiceMethods>(config: {
|
|
@@ -774,5 +882,5 @@ declare class ParseError extends Error {
|
|
|
774
882
|
//#region src/index.d.ts
|
|
775
883
|
declare const VERSION = "0.0.1";
|
|
776
884
|
//#endregion
|
|
777
|
-
export { type AtomState, type Lite, ParseError, VERSION, atom, atomSymbol, controller, controllerDepSymbol, controllerSymbol, createScope, flow, flowSymbol, getAllTags, isAtom, isControllerDep, isFlow, isPreset, isTag, isTagExecutor, isTagged, preset, presetSymbol, service, tag, tagExecutorSymbol, tagSymbol, taggedSymbol, tags, typed, typedSymbol };
|
|
885
|
+
export { type AtomState, type Lite, ParseError, VERSION, atom, atomSymbol, controller, controllerDepSymbol, controllerSymbol, createScope, flow, flowSymbol, getAllTags, isAtom, isControllerDep, isFlow, isPreset, isResource, isTag, isTagExecutor, isTagged, preset, presetSymbol, resource, resourceSymbol, service, tag, tagExecutorSymbol, tagSymbol, taggedSymbol, tags, typed, typedSymbol };
|
|
778
886
|
//# sourceMappingURL=index.d.cts.map
|