@ontrails/core 1.0.0-beta.13 → 1.0.0-beta.15
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +19 -14
- package/dist/branded.d.ts +1 -1
- package/dist/branded.d.ts.map +1 -1
- package/dist/branded.js +1 -1
- package/dist/branded.js.map +1 -1
- package/dist/context.d.ts +13 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +19 -3
- package/dist/context.js.map +1 -1
- package/dist/contour.d.ts +81 -0
- package/dist/contour.d.ts.map +1 -0
- package/dist/contour.js +176 -0
- package/dist/contour.js.map +1 -0
- package/dist/cross-schema.d.ts +18 -0
- package/dist/cross-schema.d.ts.map +1 -0
- package/dist/cross-schema.js +29 -0
- package/dist/cross-schema.js.map +1 -0
- package/dist/derive.d.ts +6 -0
- package/dist/derive.d.ts.map +1 -1
- package/dist/derive.js +29 -5
- package/dist/derive.js.map +1 -1
- package/dist/draft.d.ts +28 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +157 -0
- package/dist/draft.js.map +1 -0
- package/dist/errors.d.ts +62 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +26 -9
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +441 -20
- package/dist/execute.js.map +1 -1
- package/dist/fire.d.ts +43 -0
- package/dist/fire.d.ts.map +1 -0
- package/dist/fire.js +185 -0
- package/dist/fire.js.map +1 -0
- package/dist/index.d.ts +33 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cross-batch.d.ts +44 -0
- package/dist/internal/cross-batch.d.ts.map +1 -0
- package/dist/internal/cross-batch.js +58 -0
- package/dist/internal/cross-batch.js.map +1 -0
- package/dist/internal/fork-ctx.d.ts +41 -0
- package/dist/internal/fork-ctx.d.ts.map +1 -0
- package/dist/internal/fork-ctx.js +47 -0
- package/dist/internal/fork-ctx.js.map +1 -0
- package/dist/internal/signal-ref.d.ts +15 -0
- package/dist/internal/signal-ref.d.ts.map +1 -0
- package/dist/internal/signal-ref.js +41 -0
- package/dist/internal/signal-ref.js.map +1 -0
- package/dist/internal/topo-saves.d.ts +53 -0
- package/dist/internal/topo-saves.d.ts.map +1 -0
- package/dist/internal/topo-saves.js +449 -0
- package/dist/internal/topo-saves.js.map +1 -0
- package/dist/internal/topo-snapshots.d.ts +57 -0
- package/dist/internal/topo-snapshots.d.ts.map +1 -0
- package/dist/internal/topo-snapshots.js +372 -0
- package/dist/internal/topo-snapshots.js.map +1 -0
- package/dist/internal/topo-store-read.d.ts +70 -0
- package/dist/internal/topo-store-read.d.ts.map +1 -0
- package/dist/internal/topo-store-read.js +221 -0
- package/dist/internal/topo-store-read.js.map +1 -0
- package/dist/internal/topo-store.d.ts +26 -0
- package/dist/internal/topo-store.d.ts.map +1 -0
- package/dist/internal/topo-store.js +621 -0
- package/dist/internal/topo-store.js.map +1 -0
- package/dist/internal/tracing.d.ts +98 -0
- package/dist/internal/tracing.d.ts.map +1 -0
- package/dist/internal/tracing.js +102 -0
- package/dist/internal/tracing.js.map +1 -0
- package/dist/internal/trails-db.d.ts +16 -0
- package/dist/internal/trails-db.d.ts.map +1 -0
- package/dist/internal/trails-db.js +118 -0
- package/dist/internal/trails-db.js.map +1 -0
- package/dist/internal/zod-wrappers.d.ts +40 -0
- package/dist/internal/zod-wrappers.d.ts.map +1 -0
- package/dist/internal/zod-wrappers.js +59 -0
- package/dist/internal/zod-wrappers.js.map +1 -0
- package/dist/layer.d.ts +22 -5
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +20 -3
- package/dist/layer.js.map +1 -1
- package/dist/path-security.d.ts +1 -1
- package/dist/path-security.d.ts.map +1 -1
- package/dist/path-security.js +1 -1
- package/dist/path-security.js.map +1 -1
- package/dist/resilience.d.ts +1 -1
- package/dist/resilience.d.ts.map +1 -1
- package/dist/resilience.js +3 -3
- package/dist/resilience.js.map +1 -1
- package/dist/resource-config.d.ts +22 -0
- package/dist/resource-config.d.ts.map +1 -0
- package/dist/resource-config.js +210 -0
- package/dist/resource-config.js.map +1 -0
- package/dist/resource.d.ts +74 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +61 -0
- package/dist/resource.js.map +1 -0
- package/dist/run.d.ts +4 -3
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +5 -4
- package/dist/run.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +2 -1
- package/dist/serialization.js.map +1 -1
- package/dist/store/accessor-protocol.d.ts +51 -0
- package/dist/store/accessor-protocol.d.ts.map +1 -0
- package/dist/store/accessor-protocol.js +2 -0
- package/dist/store/accessor-protocol.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/surface-filter.d.ts +13 -0
- package/dist/surface-filter.d.ts.map +1 -0
- package/dist/surface-filter.js +76 -0
- package/dist/surface-filter.js.map +1 -0
- package/dist/topo-store.d.ts +62 -0
- package/dist/topo-store.d.ts.map +1 -0
- package/dist/topo-store.js +319 -0
- package/dist/topo-store.js.map +1 -0
- package/dist/topo.d.ts +23 -9
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +175 -46
- package/dist/topo.js.map +1 -1
- package/dist/trail.d.ts +86 -19
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +59 -4
- package/dist/trail.js.map +1 -1
- package/dist/trails/derive-trail.d.ts +66 -0
- package/dist/trails/derive-trail.d.ts.map +1 -0
- package/dist/trails/derive-trail.js +378 -0
- package/dist/trails/derive-trail.js.map +1 -0
- package/dist/trails/index.d.ts +5 -0
- package/dist/trails/index.d.ts.map +1 -0
- package/dist/trails/index.js +3 -0
- package/dist/trails/index.js.map +1 -0
- package/dist/trails/ingest.d.ts +24 -0
- package/dist/trails/ingest.d.ts.map +1 -0
- package/dist/trails/ingest.js +63 -0
- package/dist/trails/ingest.js.map +1 -0
- package/dist/transport-error-map.d.ts +106 -0
- package/dist/transport-error-map.d.ts.map +1 -0
- package/dist/transport-error-map.js +24 -0
- package/dist/transport-error-map.js.map +1 -0
- package/dist/type-checks.test-d.d.ts +71 -0
- package/dist/type-checks.test-d.d.ts.map +1 -0
- package/dist/type-checks.test-d.js +12 -0
- package/dist/type-checks.test-d.js.map +1 -0
- package/dist/type-utils.d.ts +11 -2
- package/dist/type-utils.d.ts.map +1 -1
- package/dist/type-utils.js.map +1 -1
- package/dist/types.d.ts +113 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-established-topo.d.ts +76 -0
- package/dist/validate-established-topo.d.ts.map +1 -0
- package/dist/validate-established-topo.js +43 -0
- package/dist/validate-established-topo.js.map +1 -0
- package/dist/validate-topo.d.ts.map +1 -1
- package/dist/validate-topo.js +27 -8
- package/dist/validate-topo.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +1 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +10 -1
- package/src/__tests__/branded.test.ts +5 -5
- package/src/__tests__/context.test.ts +3 -3
- package/src/__tests__/contour.test.ts +263 -0
- package/src/__tests__/derive-trail.test.ts +668 -0
- package/src/__tests__/derive.test.ts +58 -1
- package/src/__tests__/errors.test.ts +118 -0
- package/src/__tests__/execute.test.ts +1179 -141
- package/src/__tests__/fire.test.ts +1056 -0
- package/src/__tests__/fork-ctx.test.ts +83 -0
- package/src/__tests__/ingest.test.ts +191 -0
- package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
- package/src/__tests__/path-security.test.ts +7 -7
- package/src/__tests__/resilience.test.ts +8 -8
- package/src/__tests__/resource.test.ts +204 -0
- package/src/__tests__/run.test.ts +13 -13
- package/src/__tests__/service-config.test.ts +28 -32
- package/src/__tests__/surface-filter.test.ts +196 -0
- package/src/__tests__/topo-store-read.test.ts +256 -0
- package/src/__tests__/topo-store.test.ts +869 -0
- package/src/__tests__/topo.test.ts +350 -54
- package/src/__tests__/trail.test.ts +189 -24
- package/src/__tests__/trails-db.test.ts +198 -0
- package/src/__tests__/transport-error-map.test.ts +82 -0
- package/src/__tests__/type-utils.test.ts +29 -1
- package/src/__tests__/validate-topo.test.ts +305 -16
- package/src/__tests__/workspace.test.ts +6 -6
- package/src/__tests__/zod-wrappers.test.ts +62 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +24 -4
- package/src/contour.ts +344 -0
- package/src/cross-schema.ts +36 -0
- package/src/derive.ts +39 -8
- package/src/draft.ts +338 -0
- package/src/errors.ts +62 -18
- package/src/execute.ts +840 -29
- package/src/fire.ts +295 -0
- package/src/index.ts +142 -26
- package/src/internal/cross-batch.ts +77 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/internal/signal-ref.ts +87 -0
- package/src/internal/topo-snapshots.ts +517 -0
- package/src/internal/topo-store-read.ts +481 -0
- package/src/internal/topo-store.ts +1192 -0
- package/src/internal/tracing.ts +187 -0
- package/src/internal/trails-db.ts +187 -0
- package/src/internal/zod-wrappers.ts +78 -0
- package/src/layer.ts +50 -0
- package/src/path-security.ts +1 -1
- package/src/resilience.ts +3 -3
- package/src/{provision-config.ts → resource-config.ts} +109 -123
- package/src/resource.ts +155 -0
- package/src/run.ts +5 -4
- package/src/serialization.ts +2 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/surface-filter.ts +182 -0
- package/src/topo-store.ts +510 -0
- package/src/topo.ts +328 -57
- package/src/trail.ts +199 -31
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +146 -0
- package/src/transport-error-map.ts +56 -0
- package/src/type-checks.test-d.ts +106 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +148 -11
- package/src/validate-established-topo.ts +63 -0
- package/src/validate-topo.ts +37 -9
- package/src/workspace.ts +1 -1
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/dispatch.d.ts +0 -27
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -34
- package/dist/dispatch.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/gate.d.ts +0 -17
- package/dist/gate.d.ts.map +0 -1
- package/dist/gate.js +0 -21
- package/dist/gate.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/provision-config.d.ts +0 -22
- package/dist/provision-config.d.ts.map +0 -1
- package/dist/provision-config.js +0 -210
- package/dist/provision-config.js.map +0 -1
- package/dist/provision.d.ts +0 -71
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -56
- package/dist/provision.js.map +0 -1
- package/dist/service-config.d.ts +0 -22
- package/dist/service-config.d.ts.map +0 -1
- package/dist/service-config.js +0 -210
- package/dist/service-config.js.map +0 -1
- package/dist/service.d.ts +0 -71
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -56
- package/dist/service.js.map +0 -1
- package/src/__tests__/service.test.ts +0 -197
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/provision.ts +0 -148
package/dist/service.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { Result } from './result.js';
|
|
2
|
-
import type { ProvisionLookup, TrailContext } from './types.js';
|
|
3
|
-
import type { z } from 'zod';
|
|
4
|
-
/**
|
|
5
|
-
* Stable process-scoped fields available when constructing a provision.
|
|
6
|
-
*
|
|
7
|
-
* Provisions are app-level singletons, so they intentionally do not receive
|
|
8
|
-
* the full per-request TrailContext. When a provision declares a `config` schema,
|
|
9
|
-
* the validated config is passed as `svc.config`.
|
|
10
|
-
*/
|
|
11
|
-
export type ProvisionContext<C = unknown> = Pick<TrailContext, 'cwd' | 'env' | 'workspaceRoot'> & {
|
|
12
|
-
readonly config: C;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Everything needed to describe a provision before a factory is introduced.
|
|
16
|
-
*
|
|
17
|
-
* When `config` is a Zod schema, the `create` callback receives
|
|
18
|
-
* `ProvisionContext<C>` with the validated config value.
|
|
19
|
-
*/
|
|
20
|
-
export interface ProvisionSpec<T, C = unknown> {
|
|
21
|
-
/** Create the provision instance from stable process-scoped context. */
|
|
22
|
-
readonly create: (svc: ProvisionContext<C>) => Result<T, Error> | Promise<Result<T, Error>>;
|
|
23
|
-
/** Config schema — when present, config is validated and passed to `create`. */
|
|
24
|
-
readonly config?: z.ZodType<C> | undefined;
|
|
25
|
-
/** Optional cleanup performed when the hosting surface shuts down. */
|
|
26
|
-
readonly dispose?: ((provision: T) => void | Promise<void>) | undefined;
|
|
27
|
-
/** Optional operational readiness probe for introspection tooling. */
|
|
28
|
-
readonly health?: ((provision: T) => Result<unknown, Error> | Promise<Result<unknown, Error>>) | undefined;
|
|
29
|
-
/** Optional test factory used by higher-level helpers. */
|
|
30
|
-
readonly mock?: (() => T | Promise<T>) | undefined;
|
|
31
|
-
/** Human-readable description. */
|
|
32
|
-
readonly description?: string | undefined;
|
|
33
|
-
/** Arbitrary metadata for tooling and filtering. */
|
|
34
|
-
readonly metadata?: Readonly<Record<string, unknown>> | undefined;
|
|
35
|
-
}
|
|
36
|
-
/** A typed provision definition. */
|
|
37
|
-
export interface Provision<T> extends ProvisionSpec<T> {
|
|
38
|
-
readonly kind: 'provision';
|
|
39
|
-
readonly id: string;
|
|
40
|
-
/** Read the resolved provision instance from a trail context. */
|
|
41
|
-
from(ctx: TrailContext): T;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Existential type for heterogeneous provision collections.
|
|
45
|
-
*
|
|
46
|
-
* `Provision<T>` includes function parameters in `dispose`/`health`, so
|
|
47
|
-
* `unknown` is too narrow for mixed provision arrays. `any` is the correct
|
|
48
|
-
* existential here.
|
|
49
|
-
*/
|
|
50
|
-
export type AnyProvision = Provision<any>;
|
|
51
|
-
/** Explicit runtime overrides keyed by provision ID. */
|
|
52
|
-
export type ProvisionOverrideMap = Readonly<Record<string, unknown>>;
|
|
53
|
-
/** Create a `ctx.provision(...)` accessor bound to a concrete context snapshot. */
|
|
54
|
-
export declare const createProvisionLookup: (getContext: () => Pick<TrailContext, "extensions">) => ProvisionLookup;
|
|
55
|
-
/**
|
|
56
|
-
* Create a typed provision definition.
|
|
57
|
-
*
|
|
58
|
-
* The provision object is inert until a later execution branch resolves concrete
|
|
59
|
-
* instances into TrailContext extensions.
|
|
60
|
-
*/
|
|
61
|
-
export declare const provision: <T>(id: string, spec: ProvisionSpec<T>) => Provision<T>;
|
|
62
|
-
/** Narrow unknown values to provision definitions during topo discovery. */
|
|
63
|
-
export declare const isProvision: (value: unknown) => value is AnyProvision;
|
|
64
|
-
/**
|
|
65
|
-
* Return the first duplicate provision ID in a collection, if any.
|
|
66
|
-
*
|
|
67
|
-
* This supports later topo registration without each caller duplicating the
|
|
68
|
-
* same scan logic.
|
|
69
|
-
*/
|
|
70
|
-
export declare const findDuplicateProvisionId: (provisions: readonly Pick<AnyProvision, "id">[]) => string | undefined;
|
|
71
|
-
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,CAC9C,YAAY,EACZ,KAAK,GAAG,KAAK,GAAG,eAAe,CAChC,GAAG;IACF,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,CACf,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,KACrB,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAClD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC3C,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EACZ,CAAC,CACC,SAAS,EAAE,CAAC,KACT,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,GAC9D,SAAS,CAAC;IACd,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IACnD,kCAAkC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CACnE;AAED,oCAAoC;AACpC,MAAM,WAAW,SAAS,CAAC,CAAC,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,CAAC,CAAC;CAC5B;AAED;;;;;;GAMG;AAEH,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;AAE1C,wDAAwD;AACxD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAqBrE,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,GAChC,YAAY,MAAM,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,KACjD,eAQoB,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EACzB,IAAI,MAAM,EACV,MAAM,aAAa,CAAC,CAAC,CAAC,KACrB,SAAS,CAAC,CAAC,CASV,CAAC;AAEL,4EAA4E;AAC5E,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,YAMrD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,EAAE,KAC9C,MAAM,GAAG,SASX,CAAC"}
|
package/dist/service.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { NotFoundError } from './errors.js';
|
|
2
|
-
const getProvisionId = (provisionOrId) => typeof provisionOrId === 'string' ? provisionOrId : provisionOrId.id;
|
|
3
|
-
const getProvisionInstance = (ctx, provisionOrId) => {
|
|
4
|
-
const id = getProvisionId(provisionOrId);
|
|
5
|
-
return ctx.extensions?.[id];
|
|
6
|
-
};
|
|
7
|
-
const hasProvisionInstance = (ctx, provisionOrId) => Object.hasOwn(ctx.extensions ?? {}, getProvisionId(provisionOrId));
|
|
8
|
-
/** Create a `ctx.provision(...)` accessor bound to a concrete context snapshot. */
|
|
9
|
-
export const createProvisionLookup = (getContext) => ((provisionOrId) => {
|
|
10
|
-
const id = getProvisionId(provisionOrId);
|
|
11
|
-
const ctx = getContext();
|
|
12
|
-
if (!hasProvisionInstance(ctx, id)) {
|
|
13
|
-
throw new NotFoundError(`Provision "${id}" not found in trail context`);
|
|
14
|
-
}
|
|
15
|
-
return getProvisionInstance(ctx, id);
|
|
16
|
-
});
|
|
17
|
-
/**
|
|
18
|
-
* Create a typed provision definition.
|
|
19
|
-
*
|
|
20
|
-
* The provision object is inert until a later execution branch resolves concrete
|
|
21
|
-
* instances into TrailContext extensions.
|
|
22
|
-
*/
|
|
23
|
-
export const provision = (id, spec) => Object.freeze({
|
|
24
|
-
...spec,
|
|
25
|
-
from(ctx) {
|
|
26
|
-
const lookup = ctx.provision ?? createProvisionLookup(() => ctx);
|
|
27
|
-
return lookup(this);
|
|
28
|
-
},
|
|
29
|
-
id,
|
|
30
|
-
kind: 'provision',
|
|
31
|
-
});
|
|
32
|
-
/** Narrow unknown values to provision definitions during topo discovery. */
|
|
33
|
-
export const isProvision = (value) => {
|
|
34
|
-
if (typeof value !== 'object' || value === null) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
const v = value;
|
|
38
|
-
return v.kind === 'provision' && typeof v.id === 'string';
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Return the first duplicate provision ID in a collection, if any.
|
|
42
|
-
*
|
|
43
|
-
* This supports later topo registration without each caller duplicating the
|
|
44
|
-
* same scan logic.
|
|
45
|
-
*/
|
|
46
|
-
export const findDuplicateProvisionId = (provisions) => {
|
|
47
|
-
const seen = new Set();
|
|
48
|
-
for (const candidate of provisions) {
|
|
49
|
-
if (seen.has(candidate.id)) {
|
|
50
|
-
return candidate.id;
|
|
51
|
-
}
|
|
52
|
-
seen.add(candidate.id);
|
|
53
|
-
}
|
|
54
|
-
return undefined;
|
|
55
|
-
};
|
|
56
|
-
//# sourceMappingURL=service.js.map
|
package/dist/service.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqE5C,MAAM,cAAc,GAAG,CACrB,aAAgD,EACxC,EAAE,CACV,OAAO,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC;AAEvE,MAAM,oBAAoB,GAAG,CAC3B,GAAqC,EACrC,aAAgD,EAC7C,EAAE;IACL,MAAM,EAAE,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IACzC,OAAO,GAAG,CAAC,UAAU,EAAE,CAAC,EAAE,CAAM,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC3B,GAAqC,EACrC,aAAgD,EACvC,EAAE,CACX,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAErE,mFAAmF;AACnF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,UAAkD,EACjC,EAAE,CACnB,CAAC,CAAC,aAAgD,EAAE,EAAE;IACpD,MAAM,EAAE,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,aAAa,CAAC,cAAc,EAAE,8BAA8B,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC,CAAoB,CAAC;AAExB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,EAAU,EACV,IAAsB,EACR,EAAE,CAChB,MAAM,CAAC,MAAM,CAAC;IACZ,GAAG,IAAI;IACP,IAAI,CAAC,GAAiB;QACpB,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,IAAI,qBAAqB,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IACD,EAAE;IACF,IAAI,EAAE,WAAoB;CAC3B,CAAC,CAAC;AAEL,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAc,EAAyB,EAAE;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,GAAG,KAAyC,CAAC;IACpD,OAAO,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC;AAC5D,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,UAA+C,EAC3B,EAAE;IACtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC"}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
Result,
|
|
6
|
-
createTrailContext,
|
|
7
|
-
findDuplicateProvisionId,
|
|
8
|
-
isProvision,
|
|
9
|
-
provision as defineProvision,
|
|
10
|
-
} from '../index.js';
|
|
11
|
-
import type {
|
|
12
|
-
Provision,
|
|
13
|
-
ProvisionContext,
|
|
14
|
-
ProvisionSpec,
|
|
15
|
-
TrailContext,
|
|
16
|
-
} from '../index.js';
|
|
17
|
-
|
|
18
|
-
const provisionCtx: ProvisionContext = {
|
|
19
|
-
cwd: '/tmp/trails',
|
|
20
|
-
env: { DATABASE_URL: 'file::memory:' },
|
|
21
|
-
workspaceRoot: '/tmp',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
let disposedValue: number | undefined;
|
|
25
|
-
|
|
26
|
-
const counterProvisionSpec: ProvisionSpec<number> = {
|
|
27
|
-
create: () => Result.ok(3),
|
|
28
|
-
description: 'Counter provision',
|
|
29
|
-
dispose: (provision) => {
|
|
30
|
-
disposedValue = provision;
|
|
31
|
-
},
|
|
32
|
-
health: (provision) => Result.ok({ healthy: provision > 0 }),
|
|
33
|
-
meta: { domain: 'data' },
|
|
34
|
-
mock: () => 1,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const resolvedServiceCtx = (id: string, instance: unknown): TrailContext =>
|
|
38
|
-
createTrailContext({
|
|
39
|
-
abortSignal: new AbortController().signal,
|
|
40
|
-
extensions: { [id]: instance },
|
|
41
|
-
requestId: `${id}-request`,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('provision types', () => {
|
|
45
|
-
test('ProvisionContext exposes the stable process-scoped fields', () => {
|
|
46
|
-
expect(provisionCtx.cwd).toBe('/tmp/trails');
|
|
47
|
-
expect(provisionCtx.env?.DATABASE_URL).toBe('file::memory:');
|
|
48
|
-
expect(provisionCtx.workspaceRoot).toBe('/tmp');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('ProvisionSpec stores description and meta', () => {
|
|
52
|
-
expect(counterProvisionSpec.description).toBe('Counter provision');
|
|
53
|
-
expect(counterProvisionSpec.meta).toEqual({ domain: 'data' });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('ProvisionSpec can reserve a config schema for future composition', () => {
|
|
57
|
-
const config = z.object({ url: z.string().url() });
|
|
58
|
-
const spec: ProvisionSpec<number> = {
|
|
59
|
-
config,
|
|
60
|
-
create: () => Result.ok(1),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
expect(spec.config).toBe(config);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('ProvisionSpec create and health callbacks are callable', async () => {
|
|
67
|
-
const result = await counterProvisionSpec.create(provisionCtx);
|
|
68
|
-
expect(result.isOk()).toBe(true);
|
|
69
|
-
expect(result.unwrap()).toBe(3);
|
|
70
|
-
|
|
71
|
-
const health = await counterProvisionSpec.health?.(result.unwrap());
|
|
72
|
-
expect(health?.isOk()).toBe(true);
|
|
73
|
-
expect(health?.unwrap()).toEqual({ healthy: true });
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('ProvisionSpec mock and dispose callbacks are callable', async () => {
|
|
77
|
-
disposedValue = undefined;
|
|
78
|
-
const result = await counterProvisionSpec.create(provisionCtx);
|
|
79
|
-
expect(result.isOk()).toBe(true);
|
|
80
|
-
|
|
81
|
-
const provision = result.unwrap();
|
|
82
|
-
expect(await counterProvisionSpec.mock?.()).toBe(1);
|
|
83
|
-
|
|
84
|
-
await counterProvisionSpec.dispose?.(provision);
|
|
85
|
-
expect(disposedValue).toBe(3);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('ProvisionSpec create can be async', async () => {
|
|
89
|
-
const spec: ProvisionSpec<number> = {
|
|
90
|
-
create: async () => {
|
|
91
|
-
await Bun.sleep(0);
|
|
92
|
-
return Result.ok(7);
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const result = await spec.create(provisionCtx);
|
|
97
|
-
expect(result.isOk()).toBe(true);
|
|
98
|
-
expect(result.unwrap()).toBe(7);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('Provision carries identity alongside the shared spec fields', async () => {
|
|
102
|
-
const counterProvision: Provision<number> = {
|
|
103
|
-
from(ctx) {
|
|
104
|
-
return ctx.provision(this);
|
|
105
|
-
},
|
|
106
|
-
id: 'counter.main',
|
|
107
|
-
kind: 'provision',
|
|
108
|
-
...counterProvisionSpec,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
expect(counterProvision.kind).toBe('provision');
|
|
112
|
-
expect(counterProvision.id).toBe('counter.main');
|
|
113
|
-
|
|
114
|
-
const created = await counterProvision.create(provisionCtx);
|
|
115
|
-
expect(created.isOk()).toBe(true);
|
|
116
|
-
expect(created.unwrap()).toBe(3);
|
|
117
|
-
expect(await counterProvision.mock?.()).toBe(1);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('provision()', () => {
|
|
122
|
-
test('returns a frozen provision object with kind and id', () => {
|
|
123
|
-
const counter = defineProvision('counter.main', counterProvisionSpec);
|
|
124
|
-
|
|
125
|
-
expect(counter.kind).toBe('provision');
|
|
126
|
-
expect(counter.id).toBe('counter.main');
|
|
127
|
-
expect(Object.isFrozen(counter)).toBe(true);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('infers the provision type through from(ctx)', () => {
|
|
131
|
-
const db = defineProvision('db.main', {
|
|
132
|
-
create: () =>
|
|
133
|
-
Result.ok({
|
|
134
|
-
query(sql: string) {
|
|
135
|
-
return sql.length;
|
|
136
|
-
},
|
|
137
|
-
}),
|
|
138
|
-
description: 'Typed database service',
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const store = {
|
|
142
|
-
query(sql: string) {
|
|
143
|
-
return sql.length;
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
const ctx = resolvedServiceCtx('db.main', store);
|
|
147
|
-
|
|
148
|
-
const resolved = db.from(ctx);
|
|
149
|
-
expect(resolved.query('select 1')).toBe(8);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('from(ctx) throws when the provision is missing', () => {
|
|
153
|
-
const counter = defineProvision('counter.main', counterProvisionSpec);
|
|
154
|
-
const ctx = createTrailContext({
|
|
155
|
-
abortSignal: new AbortController().signal,
|
|
156
|
-
requestId: 'missing-provision',
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
expect(() => counter.from(ctx)).toThrow(
|
|
160
|
-
'Provision "counter.main" not found in trail context'
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('from(ctx) returns undefined when the provision key exists with an undefined value', () => {
|
|
165
|
-
const optional = defineProvision<undefined>('optional.main', {
|
|
166
|
-
create: () => Result.ok<undefined>(),
|
|
167
|
-
});
|
|
168
|
-
const ctx = createTrailContext({
|
|
169
|
-
abortSignal: new AbortController().signal,
|
|
170
|
-
extensions: { [optional.id]: undefined },
|
|
171
|
-
requestId: 'undefined-provision',
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
expect(optional.from(ctx)).toBeUndefined();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe('provision helpers', () => {
|
|
179
|
-
test('isProvision identifies provision definitions', () => {
|
|
180
|
-
const counter = defineProvision('counter.main', counterProvisionSpec);
|
|
181
|
-
|
|
182
|
-
expect(isProvision(counter)).toBe(true);
|
|
183
|
-
expect(isProvision({ id: 'counter.main', kind: 'trail' })).toBe(false);
|
|
184
|
-
expect(isProvision(null)).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('findDuplicateProvisionId returns the first repeated ID', () => {
|
|
188
|
-
const first = defineProvision('counter.main', counterProvisionSpec);
|
|
189
|
-
const duplicate = defineProvision('counter.main', counterProvisionSpec);
|
|
190
|
-
const other = defineProvision('counter.secondary', counterProvisionSpec);
|
|
191
|
-
|
|
192
|
-
expect(findDuplicateProvisionId([first, other])).toBeUndefined();
|
|
193
|
-
expect(findDuplicateProvisionId([first, other, duplicate])).toBe(
|
|
194
|
-
'counter.main'
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
});
|
package/src/event.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Legacy event aliases for the signal model.
|
|
3
|
-
*
|
|
4
|
-
* Keep this file as a compatibility seam while the repo-wide cutover lands.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { signal as event } from './signal.js';
|
|
8
|
-
export type {
|
|
9
|
-
AnySignal,
|
|
10
|
-
AnySignal as AnyEvent,
|
|
11
|
-
Signal,
|
|
12
|
-
Signal as Event,
|
|
13
|
-
SignalSpec,
|
|
14
|
-
SignalSpec as EventSpec,
|
|
15
|
-
} from './signal.js';
|
package/src/gate.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { Trail } from './trail.js';
|
|
2
|
-
import type { Implementation } from './types.js';
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Gate interface
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
/** A composable gate that wraps trail implementations. */
|
|
9
|
-
export interface Gate {
|
|
10
|
-
readonly name: string;
|
|
11
|
-
readonly description?: string | undefined;
|
|
12
|
-
|
|
13
|
-
/** Wrap a trail's implementation, returning a new implementation. */
|
|
14
|
-
wrap<I, O>(
|
|
15
|
-
trail: Trail<I, O>,
|
|
16
|
-
implementation: Implementation<I, O>
|
|
17
|
-
): Implementation<I, O>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Composition
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Apply gates outermost-first: gates[0] wraps gates[1] wraps ... wraps
|
|
26
|
-
* the base implementation.
|
|
27
|
-
*
|
|
28
|
-
* An empty gates array returns the implementation unchanged.
|
|
29
|
-
*/
|
|
30
|
-
export const composeGates = <I, O>(
|
|
31
|
-
gates: readonly Gate[],
|
|
32
|
-
trail: Trail<I, O>,
|
|
33
|
-
implementation: Implementation<I, O>
|
|
34
|
-
): Implementation<I, O> => {
|
|
35
|
-
// Fold right so gates[0] is the outermost wrapper.
|
|
36
|
-
let result = implementation;
|
|
37
|
-
for (let i = gates.length - 1; i >= 0; i -= 1) {
|
|
38
|
-
const gate = gates[i];
|
|
39
|
-
if (gate) {
|
|
40
|
-
result = gate.wrap(trail, result);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
44
|
-
};
|
package/src/provision.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { NotFoundError } from './errors.js';
|
|
2
|
-
import type { Result } from './result.js';
|
|
3
|
-
import type { ProvisionLookup, TrailContext } from './types.js';
|
|
4
|
-
import type { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Stable process-scoped fields available when constructing a provision.
|
|
8
|
-
*
|
|
9
|
-
* Provisions are app-level singletons, so they intentionally do not receive
|
|
10
|
-
* the full per-request TrailContext. When a provision declares a `config` schema,
|
|
11
|
-
* the validated config is passed as `svc.config`.
|
|
12
|
-
*/
|
|
13
|
-
export type ProvisionContext<C = unknown> = Pick<
|
|
14
|
-
TrailContext,
|
|
15
|
-
'cwd' | 'env' | 'workspaceRoot'
|
|
16
|
-
> & {
|
|
17
|
-
readonly config: C;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Everything needed to describe a provision before a factory is introduced.
|
|
22
|
-
*
|
|
23
|
-
* When `config` is a Zod schema, the `create` callback receives
|
|
24
|
-
* `ProvisionContext<C>` with the validated config value.
|
|
25
|
-
*/
|
|
26
|
-
export interface ProvisionSpec<T, C = unknown> {
|
|
27
|
-
/** Create the provision instance from stable process-scoped context. */
|
|
28
|
-
readonly create: (
|
|
29
|
-
svc: ProvisionContext<C>
|
|
30
|
-
) => Result<T, Error> | Promise<Result<T, Error>>;
|
|
31
|
-
/** Config schema — when present, config is validated and passed to `create`. */
|
|
32
|
-
readonly config?: z.ZodType<C> | undefined;
|
|
33
|
-
/** Optional cleanup performed when the hosting trailhead shuts down. */
|
|
34
|
-
readonly dispose?: ((provision: T) => void | Promise<void>) | undefined;
|
|
35
|
-
/** Optional operational readiness probe for introspection tooling. */
|
|
36
|
-
readonly health?:
|
|
37
|
-
| ((
|
|
38
|
-
provision: T
|
|
39
|
-
) => Result<unknown, Error> | Promise<Result<unknown, Error>>)
|
|
40
|
-
| undefined;
|
|
41
|
-
/** Optional test factory used by higher-level helpers. */
|
|
42
|
-
readonly mock?: (() => T | Promise<T>) | undefined;
|
|
43
|
-
/** Human-readable description. */
|
|
44
|
-
readonly description?: string | undefined;
|
|
45
|
-
/** Arbitrary meta for tooling and filtering. */
|
|
46
|
-
readonly meta?: Readonly<Record<string, unknown>> | undefined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** A typed provision definition. */
|
|
50
|
-
export interface Provision<T> extends ProvisionSpec<T> {
|
|
51
|
-
readonly kind: 'provision';
|
|
52
|
-
readonly id: string;
|
|
53
|
-
/** Read the resolved provision instance from a trail context. */
|
|
54
|
-
from(ctx: TrailContext): T;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Existential type for heterogeneous provision collections.
|
|
59
|
-
*
|
|
60
|
-
* `Provision<T>` includes function parameters in `dispose`/`health`, so
|
|
61
|
-
* `unknown` is too narrow for mixed provision arrays. `any` is the correct
|
|
62
|
-
* existential here.
|
|
63
|
-
*/
|
|
64
|
-
// oxlint-disable-next-line no-explicit-any -- existential type for heterogeneous provision collections
|
|
65
|
-
export type AnyProvision = Provision<any>;
|
|
66
|
-
|
|
67
|
-
/** Explicit runtime overrides keyed by provision ID. */
|
|
68
|
-
export type ProvisionOverrideMap = Readonly<Record<string, unknown>>;
|
|
69
|
-
|
|
70
|
-
const getProvisionId = <T>(
|
|
71
|
-
provisionOrId: string | Pick<Provision<T>, 'id'>
|
|
72
|
-
): string =>
|
|
73
|
-
typeof provisionOrId === 'string' ? provisionOrId : provisionOrId.id;
|
|
74
|
-
|
|
75
|
-
const getProvisionInstance = <T>(
|
|
76
|
-
ctx: Pick<TrailContext, 'extensions'>,
|
|
77
|
-
provisionOrId: string | Pick<Provision<T>, 'id'>
|
|
78
|
-
): T => {
|
|
79
|
-
const id = getProvisionId(provisionOrId);
|
|
80
|
-
return ctx.extensions?.[id] as T;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const hasProvisionInstance = (
|
|
84
|
-
ctx: Pick<TrailContext, 'extensions'>,
|
|
85
|
-
provisionOrId: string | Pick<AnyProvision, 'id'>
|
|
86
|
-
): boolean =>
|
|
87
|
-
Object.hasOwn(ctx.extensions ?? {}, getProvisionId(provisionOrId));
|
|
88
|
-
|
|
89
|
-
/** Create a `ctx.provision(...)` accessor bound to a concrete context snapshot. */
|
|
90
|
-
export const createProvisionLookup = (
|
|
91
|
-
getContext: () => Pick<TrailContext, 'extensions'>
|
|
92
|
-
): ProvisionLookup =>
|
|
93
|
-
((provisionOrId: string | Pick<AnyProvision, 'id'>) => {
|
|
94
|
-
const id = getProvisionId(provisionOrId);
|
|
95
|
-
const ctx = getContext();
|
|
96
|
-
if (!hasProvisionInstance(ctx, id)) {
|
|
97
|
-
throw new NotFoundError(`Provision "${id}" not found in trail context`);
|
|
98
|
-
}
|
|
99
|
-
return getProvisionInstance(ctx, id);
|
|
100
|
-
}) as ProvisionLookup;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Create a typed provision definition.
|
|
104
|
-
*
|
|
105
|
-
* The provision object is inert until a later execution branch resolves concrete
|
|
106
|
-
* instances into TrailContext extensions.
|
|
107
|
-
*/
|
|
108
|
-
export const provision = <T>(
|
|
109
|
-
id: string,
|
|
110
|
-
spec: ProvisionSpec<T>
|
|
111
|
-
): Provision<T> =>
|
|
112
|
-
Object.freeze({
|
|
113
|
-
...spec,
|
|
114
|
-
from(ctx: TrailContext): T {
|
|
115
|
-
const lookup = ctx.provision ?? createProvisionLookup(() => ctx);
|
|
116
|
-
return lookup(this);
|
|
117
|
-
},
|
|
118
|
-
id,
|
|
119
|
-
kind: 'provision' as const,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
/** Narrow unknown values to provision definitions during topo discovery. */
|
|
123
|
-
export const isProvision = (value: unknown): value is AnyProvision => {
|
|
124
|
-
if (typeof value !== 'object' || value === null) {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
const v = value as { kind?: unknown; id?: unknown };
|
|
128
|
-
return v.kind === 'provision' && typeof v.id === 'string';
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Return the first duplicate provision ID in a collection, if any.
|
|
133
|
-
*
|
|
134
|
-
* This supports later topo registration without each caller duplicating the
|
|
135
|
-
* same scan logic.
|
|
136
|
-
*/
|
|
137
|
-
export const findDuplicateProvisionId = (
|
|
138
|
-
provisions: readonly Pick<AnyProvision, 'id'>[]
|
|
139
|
-
): string | undefined => {
|
|
140
|
-
const seen = new Set<string>();
|
|
141
|
-
for (const candidate of provisions) {
|
|
142
|
-
if (seen.has(candidate.id)) {
|
|
143
|
-
return candidate.id;
|
|
144
|
-
}
|
|
145
|
-
seen.add(candidate.id);
|
|
146
|
-
}
|
|
147
|
-
return undefined;
|
|
148
|
-
};
|