@smthrs/plugin 0.0.0-stage → 1.0.0-rc.12

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 (118) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/LICENSE +21 -0
  3. package/README.md +88 -2
  4. package/dist/cjs/Config.d.ts +129 -0
  5. package/dist/cjs/Config.d.ts.map +1 -0
  6. package/dist/cjs/Config.js +115 -0
  7. package/dist/cjs/Config.js.map +7 -0
  8. package/dist/cjs/Hooks.d.ts +176 -0
  9. package/dist/cjs/Hooks.d.ts.map +1 -0
  10. package/dist/cjs/Hooks.js +34 -0
  11. package/dist/cjs/Hooks.js.map +7 -0
  12. package/dist/cjs/Kernel.d.ts +81 -0
  13. package/dist/cjs/Kernel.d.ts.map +1 -0
  14. package/dist/cjs/Kernel.js +57 -0
  15. package/dist/cjs/Kernel.js.map +7 -0
  16. package/dist/cjs/Plugin.d.ts +71 -0
  17. package/dist/cjs/Plugin.d.ts.map +1 -0
  18. package/dist/cjs/Plugin.js +25 -0
  19. package/dist/cjs/Plugin.js.map +7 -0
  20. package/dist/cjs/PluginError.d.ts +50 -0
  21. package/dist/cjs/PluginError.d.ts.map +1 -0
  22. package/dist/cjs/PluginError.js +58 -0
  23. package/dist/cjs/PluginError.js.map +7 -0
  24. package/dist/cjs/Plugins.d.ts +97 -0
  25. package/dist/cjs/Plugins.d.ts.map +1 -0
  26. package/dist/cjs/Plugins.js +169 -0
  27. package/dist/cjs/Plugins.js.map +7 -0
  28. package/dist/cjs/Resolve.d.ts +128 -0
  29. package/dist/cjs/Resolve.d.ts.map +1 -0
  30. package/dist/cjs/Resolve.js +346 -0
  31. package/dist/cjs/Resolve.js.map +7 -0
  32. package/dist/cjs/index.d.ts +83 -0
  33. package/dist/cjs/index.d.ts.map +1 -0
  34. package/dist/cjs/index.js +45 -0
  35. package/dist/cjs/index.js.map +7 -0
  36. package/dist/cjs/internal/Boundary.d.ts +96 -0
  37. package/dist/cjs/internal/Boundary.d.ts.map +1 -0
  38. package/dist/cjs/internal/Boundary.js +129 -0
  39. package/dist/cjs/internal/Boundary.js.map +7 -0
  40. package/dist/cjs/internal/mergePluginLayers.d.ts +21 -0
  41. package/dist/cjs/internal/mergePluginLayers.d.ts.map +1 -0
  42. package/dist/cjs/internal/mergePluginLayers.js +57 -0
  43. package/dist/cjs/internal/mergePluginLayers.js.map +7 -0
  44. package/dist/cjs/internal/snapshotCacheEnvironment.d.ts +23 -0
  45. package/dist/cjs/internal/snapshotCacheEnvironment.d.ts.map +1 -0
  46. package/dist/cjs/internal/snapshotCacheEnvironment.js +92 -0
  47. package/dist/cjs/internal/snapshotCacheEnvironment.js.map +7 -0
  48. package/dist/cjs/package.json +1 -0
  49. package/dist/esm/Config.d.ts +129 -0
  50. package/dist/esm/Config.d.ts.map +1 -0
  51. package/dist/esm/Config.js +160 -0
  52. package/dist/esm/Config.js.map +1 -0
  53. package/dist/esm/Hooks.d.ts +176 -0
  54. package/dist/esm/Hooks.d.ts.map +1 -0
  55. package/dist/esm/Hooks.js +56 -0
  56. package/dist/esm/Hooks.js.map +1 -0
  57. package/dist/esm/Kernel.d.ts +81 -0
  58. package/dist/esm/Kernel.d.ts.map +1 -0
  59. package/dist/esm/Kernel.js +58 -0
  60. package/dist/esm/Kernel.js.map +1 -0
  61. package/dist/esm/Plugin.d.ts +71 -0
  62. package/dist/esm/Plugin.d.ts.map +1 -0
  63. package/dist/esm/Plugin.js +20 -0
  64. package/dist/esm/Plugin.js.map +1 -0
  65. package/dist/esm/PluginError.d.ts +50 -0
  66. package/dist/esm/PluginError.d.ts.map +1 -0
  67. package/dist/esm/PluginError.js +53 -0
  68. package/dist/esm/PluginError.js.map +1 -0
  69. package/dist/esm/Plugins.d.ts +97 -0
  70. package/dist/esm/Plugins.d.ts.map +1 -0
  71. package/dist/esm/Plugins.js +190 -0
  72. package/dist/esm/Plugins.js.map +1 -0
  73. package/dist/esm/Resolve.d.ts +128 -0
  74. package/dist/esm/Resolve.d.ts.map +1 -0
  75. package/dist/esm/Resolve.js +369 -0
  76. package/dist/esm/Resolve.js.map +1 -0
  77. package/dist/esm/index.d.ts +83 -0
  78. package/dist/esm/index.d.ts.map +1 -0
  79. package/dist/esm/index.js +48 -0
  80. package/dist/esm/index.js.map +1 -0
  81. package/dist/esm/internal/Boundary.d.ts +96 -0
  82. package/dist/esm/internal/Boundary.d.ts.map +1 -0
  83. package/dist/esm/internal/Boundary.js +162 -0
  84. package/dist/esm/internal/Boundary.js.map +1 -0
  85. package/dist/esm/internal/mergePluginLayers.d.ts +21 -0
  86. package/dist/esm/internal/mergePluginLayers.d.ts.map +1 -0
  87. package/dist/esm/internal/mergePluginLayers.js +41 -0
  88. package/dist/esm/internal/mergePluginLayers.js.map +1 -0
  89. package/dist/esm/internal/snapshotCacheEnvironment.d.ts +23 -0
  90. package/dist/esm/internal/snapshotCacheEnvironment.d.ts.map +1 -0
  91. package/dist/esm/internal/snapshotCacheEnvironment.js +64 -0
  92. package/dist/esm/internal/snapshotCacheEnvironment.js.map +1 -0
  93. package/docs/README.md +133 -0
  94. package/docs/api.md +524 -0
  95. package/docs/concepts/configuration.md +111 -0
  96. package/docs/concepts/hook-kinds.md +107 -0
  97. package/docs/concepts/resolution.md +132 -0
  98. package/docs/guides/cache-identity.md +106 -0
  99. package/docs/guides/contribute-services.md +84 -0
  100. package/docs/guides/host-your-own-hooks.md +129 -0
  101. package/docs/guides/order-handlers.md +76 -0
  102. package/docs/guides/select-plugins.md +72 -0
  103. package/docs/guides/testing.md +149 -0
  104. package/docs/installation.md +75 -0
  105. package/docs/quickstart.md +152 -0
  106. package/docs/troubleshooting.md +180 -0
  107. package/package.json +179 -3
  108. package/src/Config.ts +207 -0
  109. package/src/Hooks.ts +212 -0
  110. package/src/Kernel.ts +119 -0
  111. package/src/Plugin.ts +80 -0
  112. package/src/PluginError.ts +62 -0
  113. package/src/Plugins.ts +271 -0
  114. package/src/Resolve.ts +539 -0
  115. package/src/index.ts +91 -0
  116. package/src/internal/Boundary.ts +252 -0
  117. package/src/internal/mergePluginLayers.ts +57 -0
  118. package/src/internal/snapshotCacheEnvironment.ts +78 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The bounded cell-host plugin kernel: typed hooks, resolution and ordering,
3
+ * and the config pipeline.
4
+ *
5
+ * The public package contract is documented at
6
+ * {@link https://smithers.sh/docs/reference/api/plugin}, with the shipped host catalog in
7
+ * `packages/smithers/agent/src/CellPlugin.ts`. Durable
8
+ * engine extension remains dependency injection; this package does not expose
9
+ * an engine-wide lifecycle registry.
10
+ *
11
+ * @since 1.0.0-rc.0
12
+ */
13
+ import type * as Effect from "effect/Effect";
14
+ import type { FlowsConfig, ResolvedConfig } from "./Config.ts";
15
+ import type { ParallelHook, WaterfallHook } from "./Hooks.ts";
16
+ /**
17
+ * The shared kernel's base hook catalog.
18
+ *
19
+ * Declared here, in the package entry point, so that the documented
20
+ * augmentation specifier works: an interface can only be augmented in the
21
+ * module that declares it:
22
+ *
23
+ * ```ts
24
+ * declare module "@smthrs/plugin" {
25
+ * interface FlowsHooks {
26
+ * toolCall: SequentialHook<(ctx: ToolCallContext) => Effect.Effect<Option.Option<ToolOverride>>>
27
+ * }
28
+ * }
29
+ * ```
30
+ *
31
+ * Closed for dispatch, open for augmentation: the kernel dispatches only the
32
+ * config lifecycle below; a host supplies and dispatches its own catalog over
33
+ * the same augmented interface.
34
+ *
35
+ * Both startup hooks are context-free: `Kernel.make` runs them before any
36
+ * plugin layer exists and supplies no services of its own, so a handler that
37
+ * needs a service provides it inside the hook. A host whose startup hooks do
38
+ * require services declares a separate hook interface, and `Kernel.make` then
39
+ * surfaces those requirements as `Kernel.StartupContext<H>`.
40
+ *
41
+ * @category models
42
+ * @since 1.0.0-rc.0
43
+ */
44
+ export interface FlowsHooks {
45
+ readonly config: WaterfallHook<(config: FlowsConfig) => Effect.Effect<Partial<FlowsConfig> | void, any, never>>;
46
+ readonly configResolved: ParallelHook<(config: ResolvedConfig) => Effect.Effect<void, any, never>>;
47
+ }
48
+ /**
49
+ * @since 1.0.0-rc.0
50
+ * @category config
51
+ */
52
+ export * as Config from "./Config.ts";
53
+ /**
54
+ * @since 1.0.0-rc.0
55
+ * @category hooks
56
+ */
57
+ export * from "./Hooks.ts";
58
+ /**
59
+ * @since 1.0.0-rc.0
60
+ * @category startup
61
+ */
62
+ export * as Kernel from "./Kernel.ts";
63
+ /**
64
+ * @since 1.0.0-rc.0
65
+ * @category models
66
+ */
67
+ export * from "./Plugin.ts";
68
+ /**
69
+ * @since 1.0.0-rc.0
70
+ * @category errors
71
+ */
72
+ export * from "./PluginError.ts";
73
+ /**
74
+ * @since 1.0.0-rc.0
75
+ * @category dispatch
76
+ */
77
+ export * as Plugins from "./Plugins.ts";
78
+ /**
79
+ * @since 1.0.0-rc.0
80
+ * @category resolution
81
+ */
82
+ export * as Resolve from "./Resolve.ts";
83
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;IAC/G,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,MAAM,EAAE,cAAc,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;CACnG;AAED;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,cAAc,YAAY,CAAA;AAE1B;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,cAAc,aAAa,CAAA;AAE3B;;;GAGG;AACH,cAAc,kBAAkB,CAAA;AAEhC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The bounded cell-host plugin kernel: typed hooks, resolution and ordering,
3
+ * and the config pipeline.
4
+ *
5
+ * The public package contract is documented at
6
+ * {@link https://smithers.sh/docs/reference/api/plugin}, with the shipped host catalog in
7
+ * `packages/smithers/agent/src/CellPlugin.ts`. Durable
8
+ * engine extension remains dependency injection; this package does not expose
9
+ * an engine-wide lifecycle registry.
10
+ *
11
+ * @since 1.0.0-rc.0
12
+ */
13
+ /**
14
+ * @since 1.0.0-rc.0
15
+ * @category config
16
+ */
17
+ export * as Config from "./Config.js";
18
+ /**
19
+ * @since 1.0.0-rc.0
20
+ * @category hooks
21
+ */
22
+ export * from "./Hooks.js";
23
+ /**
24
+ * @since 1.0.0-rc.0
25
+ * @category startup
26
+ */
27
+ export * as Kernel from "./Kernel.js";
28
+ /**
29
+ * @since 1.0.0-rc.0
30
+ * @category models
31
+ */
32
+ export * from "./Plugin.js";
33
+ /**
34
+ * @since 1.0.0-rc.0
35
+ * @category errors
36
+ */
37
+ export * from "./PluginError.js";
38
+ /**
39
+ * @since 1.0.0-rc.0
40
+ * @category dispatch
41
+ */
42
+ export * as Plugins from "./Plugins.js";
43
+ /**
44
+ * @since 1.0.0-rc.0
45
+ * @category resolution
46
+ */
47
+ export * as Resolve from "./Resolve.js";
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuCH;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,cAAc,YAAY,CAAA;AAE1B;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,cAAc,aAAa,CAAA;AAE3B;;;GAGG;AACH,cAAc,kBAAkB,CAAA;AAEhC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA","sourcesContent":["/**\n * The bounded cell-host plugin kernel: typed hooks, resolution and ordering,\n * and the config pipeline.\n *\n * The public package contract is documented at\n * {@link https://smithers.sh/docs/reference/api/plugin}, with the shipped host catalog in\n * `packages/smithers/agent/src/CellPlugin.ts`. Durable\n * engine extension remains dependency injection; this package does not expose\n * an engine-wide lifecycle registry.\n *\n * @since 1.0.0-rc.0\n */\n\nimport type * as Effect from \"effect/Effect\"\nimport type { FlowsConfig, ResolvedConfig } from \"./Config.ts\"\nimport type { ParallelHook, WaterfallHook } from \"./Hooks.ts\"\n\n/**\n * The shared kernel's base hook catalog.\n *\n * Declared here, in the package entry point, so that the documented\n * augmentation specifier works: an interface can only be augmented in the\n * module that declares it:\n *\n * ```ts\n * declare module \"@smthrs/plugin\" {\n * interface FlowsHooks {\n * toolCall: SequentialHook<(ctx: ToolCallContext) => Effect.Effect<Option.Option<ToolOverride>>>\n * }\n * }\n * ```\n *\n * Closed for dispatch, open for augmentation: the kernel dispatches only the\n * config lifecycle below; a host supplies and dispatches its own catalog over\n * the same augmented interface.\n *\n * Both startup hooks are context-free: `Kernel.make` runs them before any\n * plugin layer exists and supplies no services of its own, so a handler that\n * needs a service provides it inside the hook. A host whose startup hooks do\n * require services declares a separate hook interface, and `Kernel.make` then\n * surfaces those requirements as `Kernel.StartupContext<H>`.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface FlowsHooks {\n readonly config: WaterfallHook<(config: FlowsConfig) => Effect.Effect<Partial<FlowsConfig> | void, any, never>>\n readonly configResolved: ParallelHook<(config: ResolvedConfig) => Effect.Effect<void, any, never>>\n}\n\n/**\n * @since 1.0.0-rc.0\n * @category config\n */\nexport * as Config from \"./Config.ts\"\n\n/**\n * @since 1.0.0-rc.0\n * @category hooks\n */\nexport * from \"./Hooks.ts\"\n\n/**\n * @since 1.0.0-rc.0\n * @category startup\n */\nexport * as Kernel from \"./Kernel.ts\"\n\n/**\n * @since 1.0.0-rc.0\n * @category models\n */\nexport * from \"./Plugin.ts\"\n\n/**\n * @since 1.0.0-rc.0\n * @category errors\n */\nexport * from \"./PluginError.ts\"\n\n/**\n * @since 1.0.0-rc.0\n * @category dispatch\n */\nexport * as Plugins from \"./Plugins.ts\"\n\n/**\n * @since 1.0.0-rc.0\n * @category resolution\n */\nexport * as Resolve from \"./Resolve.ts\"\n"]}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Inert JSON admission for plugin configuration and cache identity.
3
+ *
4
+ * Reflection is descriptor-only: accessors never execute, hostile proxies are
5
+ * refused, and accepted values are detached before they are frozen.
6
+ *
7
+ * @private
8
+ * @since 1.0.0-rc.0
9
+ */
10
+ import * as BoundedJson from "@smthrs/canonical/BoundedJson";
11
+ /**
12
+ * Strict JSON value accepted by the boundary.
13
+ *
14
+ * @private
15
+ * @since 1.0.0-rc.0
16
+ */
17
+ export type Json = BoundedJson.Json;
18
+ /**
19
+ * Resource bounds for one admitted tree.
20
+ *
21
+ * @private
22
+ * @since 1.0.0-rc.0
23
+ */
24
+ export type Limits = BoundedJson.StrictLimits;
25
+ /**
26
+ * Successful admission.
27
+ *
28
+ * @private
29
+ * @since 1.0.0-rc.0
30
+ */
31
+ export interface AdmissionSuccess {
32
+ readonly ok: true;
33
+ readonly value: Json;
34
+ }
35
+ /**
36
+ * Refusal with a stable value path.
37
+ *
38
+ * @private
39
+ * @since 1.0.0-rc.0
40
+ */
41
+ export interface AdmissionFailure {
42
+ readonly ok: false;
43
+ readonly path: string;
44
+ readonly complaint: string;
45
+ }
46
+ /**
47
+ * Result of inert JSON admission.
48
+ *
49
+ * @private
50
+ * @since 1.0.0-rc.0
51
+ */
52
+ export type Admission = AdmissionSuccess | AdmissionFailure;
53
+ /**
54
+ * Default configuration bounds.
55
+ *
56
+ * @private
57
+ * @since 1.0.0-rc.0
58
+ */
59
+ export declare const defaultLimits: Limits;
60
+ /**
61
+ * Tests whether a string contains paired UTF-16 surrogate units.
62
+ *
63
+ * @private
64
+ * @since 1.0.0-rc.0
65
+ */
66
+ export declare const isWellFormedText: (value: string) => boolean;
67
+ /**
68
+ * Copies one strict JSON tree under explicit resource bounds.
69
+ *
70
+ * @private
71
+ * @since 1.0.0-rc.0
72
+ */
73
+ export declare const admit: (input: unknown, limits?: Limits) => Admission;
74
+ /**
75
+ * Admits only a JSON record at the root.
76
+ *
77
+ * @private
78
+ * @since 1.0.0-rc.0
79
+ */
80
+ export declare const record: (input: unknown, limits?: Limits) => AdmissionSuccess | AdmissionFailure;
81
+ /**
82
+ * Merges two independently admitted records, retaining unchanged subtrees.
83
+ * Both operands must come from successful admission, and the patch must be a
84
+ * fresh copy: their trees must be disjoint. Each output branch then comes from
85
+ * at most one location in either tree, preserving the no-repeated-reference
86
+ * invariant without walking retained subtrees. Limits apply to the result.
87
+ *
88
+ * @private
89
+ * @since 1.0.0-rc.0
90
+ */
91
+ export declare const mergeRecords: (base: {
92
+ readonly [key: string]: Json;
93
+ }, patch: {
94
+ readonly [key: string]: Json;
95
+ }, limits?: Limits) => Admission;
96
+ //# sourceMappingURL=Boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Boundary.d.ts","sourceRoot":"","sources":["../../../src/internal/Boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAA;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAA;AAEnC;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,YAAY,CAAA;AAE7C;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;IACjB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAA;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,MAO1B,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,OAA+B,CAAA;AA4EhF;;;;;GAKG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,OAAO,EAAE,SAAQ,MAAsB,KAAG,SAatE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GACjB,OAAO,OAAO,EACd,SAAQ,MAAsB,KAC7B,gBAAgB,GAAG,gBAKrB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACtC,OAAO;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACvC,SAAQ,MAAsB,KAC7B,SA4CF,CAAA"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Inert JSON admission for plugin configuration and cache identity.
3
+ *
4
+ * Reflection is descriptor-only: accessors never execute, hostile proxies are
5
+ * refused, and accepted values are detached before they are frozen.
6
+ *
7
+ * @private
8
+ * @since 1.0.0-rc.0
9
+ */
10
+ import * as BoundedJson from "@smthrs/canonical/BoundedJson";
11
+ /**
12
+ * Default configuration bounds.
13
+ *
14
+ * @private
15
+ * @since 1.0.0-rc.0
16
+ */
17
+ export const defaultLimits = Object.freeze({
18
+ maxBytes: 1024 * 1024,
19
+ maxDepth: 64,
20
+ maxMembers: 4_096,
21
+ maxNodes: 8_192,
22
+ maxStringBytes: 64 * 1024,
23
+ maxKeyBytes: 1_024
24
+ });
25
+ /**
26
+ * Tests whether a string contains paired UTF-16 surrogate units.
27
+ *
28
+ * @private
29
+ * @since 1.0.0-rc.0
30
+ */
31
+ export const isWellFormedText = (value) => value.isWellFormed();
32
+ const childPath = (path, key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
33
+ const encodedStringBytes = (value) => BoundedJson.encodedStringBytes(value);
34
+ // Paths contain only well-formed text and JSON-escaped keys. Count their JSON
35
+ // bytes without encoding the whole path, reserving quotes and a truncation marker.
36
+ const boundedPath = (path) => {
37
+ let bytes = 2 + 3;
38
+ let prefix = "";
39
+ for (const character of path) {
40
+ const point = character.codePointAt(0);
41
+ bytes += character === "\"" || character === "\\"
42
+ ? 2
43
+ : point <= 0x7f
44
+ ? 1
45
+ : point <= 0x7ff
46
+ ? 2
47
+ : point <= 0xffff
48
+ ? 3
49
+ : 4;
50
+ if (bytes > 192)
51
+ return `${prefix}...`;
52
+ prefix += character;
53
+ }
54
+ return path;
55
+ };
56
+ // Only detached containers constructed here enter this map. Mutable caller
57
+ // objects and Object.freeze alone never establish admission.
58
+ const snapshots = new WeakMap();
59
+ const scalarTotals = (bytes) => ({ bytes, members: 0, nodes: 1, depth: 0 });
60
+ const total = (entries) => {
61
+ const totals = { bytes: 2 + Math.max(0, entries.size - 1), members: entries.size, nodes: 1, depth: 1 };
62
+ for (const entry of entries.values()) {
63
+ totals.bytes += entry.keyBytes + entry.totals.bytes;
64
+ totals.members += entry.totals.members;
65
+ totals.nodes += entry.totals.nodes;
66
+ totals.depth = Math.max(totals.depth, 1 + entry.totals.depth);
67
+ }
68
+ return totals;
69
+ };
70
+ const remember = (value) => {
71
+ const entries = new Map();
72
+ for (const [key, child] of Object.entries(value)) {
73
+ entries.set(key, {
74
+ keyBytes: Array.isArray(value) ? 0 : encodedStringBytes(key) + 1,
75
+ totals: typeof child === "object" && child !== null ?
76
+ snapshots.get(child).totals
77
+ : scalarTotals(typeof child === "string" ? encodedStringBytes(child) : String(child).length)
78
+ });
79
+ }
80
+ snapshots.set(value, { totals: total(entries), entries });
81
+ };
82
+ /**
83
+ * Copies one strict JSON tree under explicit resource bounds.
84
+ *
85
+ * @private
86
+ * @since 1.0.0-rc.0
87
+ */
88
+ export const admit = (input, limits = defaultLimits) => {
89
+ const result = BoundedJson.admitStrict(input, limits, {
90
+ ordinaryRecords: true,
91
+ boundedText: true,
92
+ onContainer: remember
93
+ });
94
+ return result.ok ? result : {
95
+ ...result,
96
+ path: boundedPath(result.path),
97
+ complaint: result.complaint === "must be dense and have no extra properties"
98
+ ? "must be a dense array with no extra properties" :
99
+ result.complaint
100
+ };
101
+ };
102
+ /**
103
+ * Admits only a JSON record at the root.
104
+ *
105
+ * @private
106
+ * @since 1.0.0-rc.0
107
+ */
108
+ export const record = (input, limits = defaultLimits) => {
109
+ const admitted = admit(input, limits);
110
+ return admitted.ok && (admitted.value === null || Array.isArray(admitted.value) || typeof admitted.value !== "object")
111
+ ? { ok: false, path: "$", complaint: "must be a JSON record" }
112
+ : admitted;
113
+ };
114
+ /**
115
+ * Merges two independently admitted records, retaining unchanged subtrees.
116
+ * Both operands must come from successful admission, and the patch must be a
117
+ * fresh copy: their trees must be disjoint. Each output branch then comes from
118
+ * at most one location in either tree, preserving the no-repeated-reference
119
+ * invariant without walking retained subtrees. Limits apply to the result.
120
+ *
121
+ * @private
122
+ * @since 1.0.0-rc.0
123
+ */
124
+ export const mergeRecords = (base, patch, limits = defaultLimits) => {
125
+ const merge = (left, right, path) => {
126
+ const entries = new Map(snapshots.get(left).entries);
127
+ const result = { ...left };
128
+ for (const [key, entry] of snapshots.get(right).entries) {
129
+ const previous = left[key];
130
+ const next = right[key];
131
+ if (typeof previous === "object" && previous !== null && !Array.isArray(previous) &&
132
+ typeof next === "object" && next !== null && !Array.isArray(next)) {
133
+ const merged = merge(previous, next, childPath(path, key));
134
+ if (!merged.ok)
135
+ return merged;
136
+ result[key] = merged.value;
137
+ entries.set(key, { keyBytes: entry.keyBytes, totals: snapshots.get(merged.value).totals });
138
+ }
139
+ else {
140
+ result[key] = next;
141
+ entries.set(key, entry);
142
+ }
143
+ }
144
+ const totals = total(entries);
145
+ const complaint = totals.members > limits.maxMembers ?
146
+ `exceeds the ${limits.maxMembers}-member limit`
147
+ : totals.nodes > limits.maxNodes ?
148
+ `exceeds the ${limits.maxNodes}-node limit`
149
+ : totals.bytes > limits.maxBytes ?
150
+ `exceeds the ${limits.maxBytes}-byte limit`
151
+ : totals.depth > limits.maxDepth ?
152
+ `exceeds the depth limit of ${limits.maxDepth}`
153
+ : undefined;
154
+ if (complaint !== undefined)
155
+ return { ok: false, path: boundedPath(path), complaint };
156
+ Object.freeze(result);
157
+ snapshots.set(result, { totals, entries });
158
+ return { ok: true, value: result };
159
+ };
160
+ return merge(base, patch, "$");
161
+ };
162
+ //# sourceMappingURL=Boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Boundary.js","sourceRoot":"","sources":["../../../src/internal/Boundary.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Inert JSON admission for plugin configuration and cache identity.\n *\n * Reflection is descriptor-only: accessors never execute, hostile proxies are\n * refused, and accepted values are detached before they are frozen.\n *\n * @private\n * @since 1.0.0-rc.0\n */\n\nimport * as BoundedJson from \"@smthrs/canonical/BoundedJson\"\n\n/**\n * Strict JSON value accepted by the boundary.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport type Json = BoundedJson.Json\n\n/**\n * Resource bounds for one admitted tree.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport type Limits = BoundedJson.StrictLimits\n\n/**\n * Successful admission.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface AdmissionSuccess {\n readonly ok: true\n readonly value: Json\n}\n\n/**\n * Refusal with a stable value path.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface AdmissionFailure {\n readonly ok: false\n readonly path: string\n readonly complaint: string\n}\n\n/**\n * Result of inert JSON admission.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport type Admission = AdmissionSuccess | AdmissionFailure\n\n/**\n * Default configuration bounds.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const defaultLimits: Limits = Object.freeze({\n maxBytes: 1024 * 1024,\n maxDepth: 64,\n maxMembers: 4_096,\n maxNodes: 8_192,\n maxStringBytes: 64 * 1024,\n maxKeyBytes: 1_024\n})\n\n/**\n * Tests whether a string contains paired UTF-16 surrogate units.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const isWellFormedText = (value: string): boolean => value.isWellFormed()\n\nconst childPath = (path: string, key: string): string =>\n /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`\n\nconst encodedStringBytes = (value: string): number => BoundedJson.encodedStringBytes(value)!\n\n// Paths contain only well-formed text and JSON-escaped keys. Count their JSON\n// bytes without encoding the whole path, reserving quotes and a truncation marker.\nconst boundedPath = (path: string): string => {\n let bytes = 2 + 3\n let prefix = \"\"\n for (const character of path) {\n const point = character.codePointAt(0)!\n bytes += character === \"\\\"\" || character === \"\\\\\"\n ? 2\n : point <= 0x7f\n ? 1\n : point <= 0x7ff\n ? 2\n : point <= 0xffff\n ? 3\n : 4\n if (bytes > 192) return `${prefix}...`\n prefix += character\n }\n return path\n}\n\ninterface Totals {\n bytes: number\n members: number\n nodes: number\n depth: number\n}\n\ninterface Entry {\n readonly keyBytes: number\n readonly totals: Totals\n}\n\ninterface Snapshot {\n readonly totals: Totals\n readonly entries: Map<string, Entry>\n}\n\n// Only detached containers constructed here enter this map. Mutable caller\n// objects and Object.freeze alone never establish admission.\nconst snapshots = new WeakMap<object, Snapshot>()\n\nconst scalarTotals = (bytes: number): Totals => ({ bytes, members: 0, nodes: 1, depth: 0 })\n\nconst total = (entries: ReadonlyMap<string, Entry>): Totals => {\n const totals: Totals = { bytes: 2 + Math.max(0, entries.size - 1), members: entries.size, nodes: 1, depth: 1 }\n for (const entry of entries.values()) {\n totals.bytes += entry.keyBytes + entry.totals.bytes\n totals.members += entry.totals.members\n totals.nodes += entry.totals.nodes\n totals.depth = Math.max(totals.depth, 1 + entry.totals.depth)\n }\n return totals\n}\n\nconst remember = (value: ReadonlyArray<Json> | { readonly [key: string]: Json }): void => {\n const entries = new Map<string, Entry>()\n for (const [key, child] of Object.entries(value)) {\n entries.set(key, {\n keyBytes: Array.isArray(value) ? 0 : encodedStringBytes(key) + 1,\n totals: typeof child === \"object\" && child !== null ?\n snapshots.get(child)!.totals\n : scalarTotals(typeof child === \"string\" ? encodedStringBytes(child) : String(child).length)\n })\n }\n snapshots.set(value, { totals: total(entries), entries })\n}\n\n/**\n * Copies one strict JSON tree under explicit resource bounds.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const admit = (input: unknown, limits: Limits = defaultLimits): Admission => {\n const result = BoundedJson.admitStrict(input, limits, {\n ordinaryRecords: true,\n boundedText: true,\n onContainer: remember\n })\n return result.ok ? result : {\n ...result,\n path: boundedPath(result.path),\n complaint: result.complaint === \"must be dense and have no extra properties\"\n ? \"must be a dense array with no extra properties\" :\n result.complaint\n }\n}\n\n/**\n * Admits only a JSON record at the root.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const record = (\n input: unknown,\n limits: Limits = defaultLimits\n): AdmissionSuccess | AdmissionFailure => {\n const admitted = admit(input, limits)\n return admitted.ok && (admitted.value === null || Array.isArray(admitted.value) || typeof admitted.value !== \"object\")\n ? { ok: false, path: \"$\", complaint: \"must be a JSON record\" }\n : admitted\n}\n\n/**\n * Merges two independently admitted records, retaining unchanged subtrees.\n * Both operands must come from successful admission, and the patch must be a\n * fresh copy: their trees must be disjoint. Each output branch then comes from\n * at most one location in either tree, preserving the no-repeated-reference\n * invariant without walking retained subtrees. Limits apply to the result.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const mergeRecords = (\n base: { readonly [key: string]: Json },\n patch: { readonly [key: string]: Json },\n limits: Limits = defaultLimits\n): Admission => {\n const merge = (\n left: { readonly [key: string]: Json },\n right: { readonly [key: string]: Json },\n path: string\n ): Admission => {\n const entries = new Map(snapshots.get(left)!.entries)\n const result = { ...left }\n for (const [key, entry] of snapshots.get(right)!.entries) {\n const previous = left[key]\n const next = right[key]!\n if (\n typeof previous === \"object\" && previous !== null && !Array.isArray(previous) &&\n typeof next === \"object\" && next !== null && !Array.isArray(next)\n ) {\n const merged = merge(\n previous as { readonly [key: string]: Json },\n next as { readonly [key: string]: Json },\n childPath(path, key)\n )\n if (!merged.ok) return merged\n result[key] = merged.value\n entries.set(key, { keyBytes: entry.keyBytes, totals: snapshots.get(merged.value as object)!.totals })\n } else {\n result[key] = next\n entries.set(key, entry)\n }\n }\n const totals = total(entries)\n const complaint = totals.members > limits.maxMembers ?\n `exceeds the ${limits.maxMembers}-member limit`\n : totals.nodes > limits.maxNodes ?\n `exceeds the ${limits.maxNodes}-node limit`\n : totals.bytes > limits.maxBytes ?\n `exceeds the ${limits.maxBytes}-byte limit`\n : totals.depth > limits.maxDepth ?\n `exceeds the depth limit of ${limits.maxDepth}`\n : undefined\n if (complaint !== undefined) return { ok: false, path: boundedPath(path), complaint }\n Object.freeze(result)\n snapshots.set(result, { totals, entries })\n return { ok: true, value: result }\n }\n return merge(base, patch, \"$\")\n}\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Effect layer composition for a resolved plugin list.
3
+ *
4
+ * @private
5
+ * @since 1.0.0-rc.0
6
+ */
7
+ import { Action } from "@smthrs/flow";
8
+ import * as Layer from "effect/Layer";
9
+ import type { FlowsPlugin } from "../Plugin.ts";
10
+ import { PluginError } from "../PluginError.ts";
11
+ /**
12
+ * Merges plugin layers left-to-right, wrapping each build failure as
13
+ * `layer_failed`, then seals the cache environment: it is supplied beneath
14
+ * the plugin layers and overrides anything they output, and an absent
15
+ * declaration stays absent.
16
+ *
17
+ * @private
18
+ * @since 1.0.0-rc.0
19
+ */
20
+ export declare const mergePluginLayers: <H>(plugins: ReadonlyArray<FlowsPlugin<H>>, cacheEnvironment: Action.CacheEnvironment | undefined) => Layer.Layer<any, PluginError, any>;
21
+ //# sourceMappingURL=mergePluginLayers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergePluginLayers.d.ts","sourceRoot":"","sources":["../../../src/internal/mergePluginLayers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,SAAS,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EACtC,kBAAkB,MAAM,CAAC,gBAAgB,GAAG,SAAS,KACpD,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,CA+BnC,CAAA"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Effect layer composition for a resolved plugin list.
3
+ *
4
+ * @private
5
+ * @since 1.0.0-rc.0
6
+ */
7
+ import { Action } from "@smthrs/flow";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+ import { PluginError } from "../PluginError.js";
11
+ /**
12
+ * Merges plugin layers left-to-right, wrapping each build failure as
13
+ * `layer_failed`, then seals the cache environment: it is supplied beneath
14
+ * the plugin layers and overrides anything they output, and an absent
15
+ * declaration stays absent.
16
+ *
17
+ * @private
18
+ * @since 1.0.0-rc.0
19
+ */
20
+ export const mergePluginLayers = (plugins, cacheEnvironment) => {
21
+ const layers = plugins.flatMap((plugin) => plugin.layer
22
+ ? [
23
+ Layer.catchCause(plugin.layer, (cause) => Layer.effectDiscard(Effect.fail(new PluginError({
24
+ code: "layer_failed",
25
+ message: `plugin "${plugin.name}" failed to build its layer`,
26
+ plugin: plugin.name,
27
+ cause
28
+ }))))
29
+ ]
30
+ : []);
31
+ const merged = (layers.length === 0
32
+ ? Layer.empty
33
+ : layers.reduce((accumulated, next) => Layer.provideMerge(next, accumulated)));
34
+ // The sealed environment is provided beneath the plugin layers so they can
35
+ // read it, then merged last so it overrides any plugin output. An absent
36
+ // declaration is sealed as `undefined`, so no plugin layer can declare or
37
+ // replace the composition's cache identity.
38
+ const environment = Layer.succeed(Action.CurrentCacheEnvironment)(cacheEnvironment);
39
+ return Layer.merge(Layer.provideMerge(merged, environment), environment);
40
+ };
41
+ //# sourceMappingURL=mergePluginLayers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergePluginLayers.js","sourceRoot":"","sources":["../../../src/internal/mergePluginLayers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,OAAsC,EACtC,gBAAqD,EACjB,EAAE;IACtC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CACxC,MAAM,CAAC,KAAK;QACV,CAAC,CAAC;YACA,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACvC,KAAK,CAAC,aAAa,CACjB,MAAM,CAAC,IAAI,CACT,IAAI,WAAW,CAAC;gBACd,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,WAAW,MAAM,CAAC,IAAI,6BAA6B;gBAC5D,MAAM,EAAE,MAAM,CAAC,IAAI;gBACnB,KAAK;aACN,CAAC,CACH,CACF,CAAuC;SAC3C;QACD,CAAC,CAAC,EAAE,CACP,CAAA;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QACjC,CAAC,CAAC,KAAK,CAAC,KAAK;QACb,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAuC,CAAA;IACtH,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,4CAA4C;IAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,gBAAgB,CAIjF,CAAA;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAA;AAC1E,CAAC,CAAA","sourcesContent":["/**\n * Effect layer composition for a resolved plugin list.\n *\n * @private\n * @since 1.0.0-rc.0\n */\n\nimport { Action } from \"@smthrs/flow\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport type { FlowsPlugin } from \"../Plugin.ts\"\nimport { PluginError } from \"../PluginError.ts\"\n\n/**\n * Merges plugin layers left-to-right, wrapping each build failure as\n * `layer_failed`, then seals the cache environment: it is supplied beneath\n * the plugin layers and overrides anything they output, and an absent\n * declaration stays absent.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const mergePluginLayers = <H>(\n plugins: ReadonlyArray<FlowsPlugin<H>>,\n cacheEnvironment: Action.CacheEnvironment | undefined\n): Layer.Layer<any, PluginError, any> => {\n const layers = plugins.flatMap((plugin) =>\n plugin.layer\n ? [\n Layer.catchCause(plugin.layer, (cause) =>\n Layer.effectDiscard(\n Effect.fail(\n new PluginError({\n code: \"layer_failed\",\n message: `plugin \"${plugin.name}\" failed to build its layer`,\n plugin: plugin.name,\n cause\n })\n )\n )) as Layer.Layer<any, PluginError, any>\n ]\n : []\n )\n const merged = (layers.length === 0\n ? Layer.empty\n : layers.reduce((accumulated, next) => Layer.provideMerge(next, accumulated))) as Layer.Layer<any, PluginError, any>\n // The sealed environment is provided beneath the plugin layers so they can\n // read it, then merged last so it overrides any plugin output. An absent\n // declaration is sealed as `undefined`, so no plugin layer can declare or\n // replace the composition's cache identity.\n const environment = Layer.succeed(Action.CurrentCacheEnvironment)(cacheEnvironment) as unknown as Layer.Layer<\n any,\n PluginError,\n any\n >\n return Layer.merge(Layer.provideMerge(merged, environment), environment)\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Cache identity admission for a resolved plugin list.
3
+ *
4
+ * @private
5
+ * @since 1.0.0-rc.0
6
+ */
7
+ import { Action } from "@smthrs/flow";
8
+ import * as Effect from "effect/Effect";
9
+ import type { FlowsPlugin } from "../Plugin.ts";
10
+ import { PluginError } from "../PluginError.ts";
11
+ /**
12
+ * Admits a caller cache environment and prepends one escaped `name@version`
13
+ * identity per plugin, in resolved order. An absent input stays absent. A
14
+ * versionless plugin is refused at the dollar-path it was admitted from.
15
+ *
16
+ * @private
17
+ * @since 1.0.0-rc.0
18
+ */
19
+ export declare const snapshotCacheEnvironment: <H>(input: unknown, plugins: ReadonlyArray<{
20
+ readonly plugin: FlowsPlugin<H>;
21
+ readonly path: string;
22
+ }>) => Effect.Effect<Action.CacheEnvironment | undefined, PluginError>;
23
+ //# sourceMappingURL=snapshotCacheEnvironment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshotCacheEnvironment.d.ts","sourceRoot":"","sources":["../../../src/internal/snapshotCacheEnvironment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EACxC,OAAO,OAAO,EACd,SAAS,aAAa,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,KACjF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,GAAG,SAAS,EAAE,WAAW,CAgDhE,CAAA"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Cache identity admission for a resolved plugin list.
3
+ *
4
+ * @private
5
+ * @since 1.0.0-rc.0
6
+ */
7
+ import { Action } from "@smthrs/flow";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Schema from "effect/Schema";
10
+ import { PluginError } from "../PluginError.js";
11
+ import * as Boundary from "./Boundary.js";
12
+ const escapePluginIdentityPart = (value) =>
13
+ // Percent must be escaped first so literal escape-looking text remains distinct from the `@` escape added next.
14
+ value.replaceAll("%", "%25").replaceAll("@", "%40");
15
+ /**
16
+ * Admits a caller cache environment and prepends one escaped `name@version`
17
+ * identity per plugin, in resolved order. An absent input stays absent. A
18
+ * versionless plugin is refused at the dollar-path it was admitted from.
19
+ *
20
+ * @private
21
+ * @since 1.0.0-rc.0
22
+ */
23
+ export const snapshotCacheEnvironment = (input, plugins) => {
24
+ if (input === undefined)
25
+ return Effect.succeed(undefined);
26
+ const pluginIdentities = [];
27
+ for (const { path, plugin } of plugins) {
28
+ if (plugin.version === undefined) {
29
+ return Effect.fail(new PluginError({
30
+ code: "cache_environment_invalid",
31
+ message: `cache environment requires a version for plugin "${plugin.name}"`,
32
+ path: `${path}.version`,
33
+ plugin: plugin.name
34
+ }));
35
+ }
36
+ pluginIdentities.push(`${escapePluginIdentityPart(plugin.name)}@${escapePluginIdentityPart(plugin.version)}`);
37
+ }
38
+ const admitted = Boundary.record(input);
39
+ if (!admitted.ok) {
40
+ return Effect.fail(new PluginError({
41
+ code: "cache_environment_invalid",
42
+ message: `cache environment ${admitted.complaint}`,
43
+ path: `$options.cacheEnvironment${admitted.path.slice(1)}`
44
+ }));
45
+ }
46
+ return Schema.decodeUnknownEffect(Action.CacheEnvironment)(admitted.value).pipe(Effect.mapError(() => new PluginError({
47
+ code: "cache_environment_invalid",
48
+ message: "cache environment does not match the complete cache identity schema",
49
+ path: "$options.cacheEnvironment"
50
+ })), Effect.map((environment) => {
51
+ const capabilities = {};
52
+ for (const name of Object.keys(environment.capabilities)) {
53
+ Object.defineProperty(capabilities, name, {
54
+ value: Object.freeze([...environment.capabilities[name]]),
55
+ enumerable: true
56
+ });
57
+ }
58
+ return Object.freeze({
59
+ layers: Object.freeze([...pluginIdentities, ...environment.layers]),
60
+ capabilities: Object.freeze(capabilities)
61
+ });
62
+ }));
63
+ };
64
+ //# sourceMappingURL=snapshotCacheEnvironment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshotCacheEnvironment.js","sourceRoot":"","sources":["../../../src/internal/snapshotCacheEnvironment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Cache identity admission for a resolved plugin list.\n *\n * @private\n * @since 1.0.0-rc.0\n */\n\nimport { Action } from \"@smthrs/flow\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type { FlowsPlugin } from \"../Plugin.ts\"\nimport { PluginError } from \"../PluginError.ts\"\nimport * as Boundary from \"./Boundary.ts\"\n\nconst escapePluginIdentityPart = (value: string): string =>\n // Percent must be escaped first so literal escape-looking text remains distinct from the `@` escape added next.\n value.replaceAll(\"%\", \"%25\").replaceAll(\"@\", \"%40\")\n\n/**\n * Admits a caller cache environment and prepends one escaped `name@version`\n * identity per plugin, in resolved order. An absent input stays absent. A\n * versionless plugin is refused at the dollar-path it was admitted from.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const snapshotCacheEnvironment = <H>(\n input: unknown,\n plugins: ReadonlyArray<{ readonly plugin: FlowsPlugin<H>; readonly path: string }>\n): Effect.Effect<Action.CacheEnvironment | undefined, PluginError> => {\n if (input === undefined) return Effect.succeed(undefined)\n const pluginIdentities: Array<string> = []\n for (const { path, plugin } of plugins) {\n if (plugin.version === undefined) {\n return Effect.fail(\n new PluginError({\n code: \"cache_environment_invalid\",\n message: `cache environment requires a version for plugin \"${plugin.name}\"`,\n path: `${path}.version`,\n plugin: plugin.name\n })\n )\n }\n pluginIdentities.push(`${escapePluginIdentityPart(plugin.name)}@${escapePluginIdentityPart(plugin.version)}`)\n }\n const admitted = Boundary.record(input)\n if (!admitted.ok) {\n return Effect.fail(\n new PluginError({\n code: \"cache_environment_invalid\",\n message: `cache environment ${admitted.complaint}`,\n path: `$options.cacheEnvironment${admitted.path.slice(1)}`\n })\n )\n }\n return Schema.decodeUnknownEffect(Action.CacheEnvironment)(admitted.value).pipe(\n Effect.mapError(() =>\n new PluginError({\n code: \"cache_environment_invalid\",\n message: \"cache environment does not match the complete cache identity schema\",\n path: \"$options.cacheEnvironment\"\n })\n ),\n Effect.map((environment) => {\n const capabilities: Record<string, ReadonlyArray<string>> = {}\n for (const name of Object.keys(environment.capabilities)) {\n Object.defineProperty(capabilities, name, {\n value: Object.freeze([...environment.capabilities[name]!]),\n enumerable: true\n })\n }\n return Object.freeze({\n layers: Object.freeze([...pluginIdentities, ...environment.layers]),\n capabilities: Object.freeze(capabilities)\n })\n })\n )\n}\n"]}