@smthrs/plan 0.0.0-stage → 1.0.0-rc.4

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 (199) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -2
  4. package/dist/cjs/CachePolicy.d.ts +131 -0
  5. package/dist/cjs/CachePolicy.d.ts.map +1 -0
  6. package/dist/cjs/CachePolicy.js +50 -0
  7. package/dist/cjs/CachePolicy.js.map +7 -0
  8. package/dist/cjs/Effects.d.ts +240 -0
  9. package/dist/cjs/Effects.d.ts.map +1 -0
  10. package/dist/cjs/Effects.js +76 -0
  11. package/dist/cjs/Effects.js.map +7 -0
  12. package/dist/cjs/FileSet.d.ts +288 -0
  13. package/dist/cjs/FileSet.d.ts.map +1 -0
  14. package/dist/cjs/FileSet.js +191 -0
  15. package/dist/cjs/FileSet.js.map +7 -0
  16. package/dist/cjs/GraphBuildError.d.ts +122 -0
  17. package/dist/cjs/GraphBuildError.d.ts.map +1 -0
  18. package/dist/cjs/GraphBuildError.js +71 -0
  19. package/dist/cjs/GraphBuildError.js.map +7 -0
  20. package/dist/cjs/KeyMaterial.d.ts +102 -0
  21. package/dist/cjs/KeyMaterial.d.ts.map +1 -0
  22. package/dist/cjs/KeyMaterial.js +64 -0
  23. package/dist/cjs/KeyMaterial.js.map +7 -0
  24. package/dist/cjs/Node.d.ts +606 -0
  25. package/dist/cjs/Node.d.ts.map +1 -0
  26. package/dist/cjs/Node.js +207 -0
  27. package/dist/cjs/Node.js.map +7 -0
  28. package/dist/cjs/Placement.d.ts +117 -0
  29. package/dist/cjs/Placement.d.ts.map +1 -0
  30. package/dist/cjs/Placement.js +46 -0
  31. package/dist/cjs/Placement.js.map +7 -0
  32. package/dist/cjs/Plan.d.ts +440 -0
  33. package/dist/cjs/Plan.d.ts.map +1 -0
  34. package/dist/cjs/Plan.js +508 -0
  35. package/dist/cjs/Plan.js.map +7 -0
  36. package/dist/cjs/PlanDiff.d.ts +58 -0
  37. package/dist/cjs/PlanDiff.d.ts.map +1 -0
  38. package/dist/cjs/PlanDiff.js +124 -0
  39. package/dist/cjs/PlanDiff.js.map +7 -0
  40. package/dist/cjs/Planned.d.ts +121 -0
  41. package/dist/cjs/Planned.d.ts.map +1 -0
  42. package/dist/cjs/Planned.js +72 -0
  43. package/dist/cjs/Planned.js.map +7 -0
  44. package/dist/cjs/Repetition.d.ts +31 -0
  45. package/dist/cjs/Repetition.d.ts.map +1 -0
  46. package/dist/cjs/Repetition.js +36 -0
  47. package/dist/cjs/Repetition.js.map +7 -0
  48. package/dist/cjs/Scheduling.d.ts +89 -0
  49. package/dist/cjs/Scheduling.d.ts.map +1 -0
  50. package/dist/cjs/Scheduling.js +76 -0
  51. package/dist/cjs/Scheduling.js.map +7 -0
  52. package/dist/cjs/StepKey.d.ts +333 -0
  53. package/dist/cjs/StepKey.d.ts.map +1 -0
  54. package/dist/cjs/StepKey.js +311 -0
  55. package/dist/cjs/StepKey.js.map +7 -0
  56. package/dist/cjs/index.d.ts +91 -0
  57. package/dist/cjs/index.d.ts.map +1 -0
  58. package/dist/cjs/index.js +59 -0
  59. package/dist/cjs/index.js.map +7 -0
  60. package/dist/cjs/internal/ConflictAnnotation.d.ts +59 -0
  61. package/dist/cjs/internal/ConflictAnnotation.d.ts.map +1 -0
  62. package/dist/cjs/internal/ConflictAnnotation.js +247 -0
  63. package/dist/cjs/internal/ConflictAnnotation.js.map +7 -0
  64. package/dist/cjs/internal/EffectCandidates.d.ts +19 -0
  65. package/dist/cjs/internal/EffectCandidates.d.ts.map +1 -0
  66. package/dist/cjs/internal/EffectCandidates.js +92 -0
  67. package/dist/cjs/internal/EffectCandidates.js.map +7 -0
  68. package/dist/cjs/internal/JsonMirror.d.ts +34 -0
  69. package/dist/cjs/internal/JsonMirror.d.ts.map +1 -0
  70. package/dist/cjs/internal/JsonMirror.js +195 -0
  71. package/dist/cjs/internal/JsonMirror.js.map +7 -0
  72. package/dist/cjs/internal/effects.d.ts +245 -0
  73. package/dist/cjs/internal/effects.d.ts.map +1 -0
  74. package/dist/cjs/internal/effects.js +290 -0
  75. package/dist/cjs/internal/effects.js.map +7 -0
  76. package/dist/cjs/internal/node.d.ts +526 -0
  77. package/dist/cjs/internal/node.d.ts.map +1 -0
  78. package/dist/cjs/internal/node.js +312 -0
  79. package/dist/cjs/internal/node.js.map +7 -0
  80. package/dist/cjs/package.json +1 -0
  81. package/dist/cjs/test/PlanFixtures.d.ts +127 -0
  82. package/dist/cjs/test/PlanFixtures.d.ts.map +1 -0
  83. package/dist/cjs/test/PlanFixtures.js +59 -0
  84. package/dist/cjs/test/PlanFixtures.js.map +7 -0
  85. package/dist/esm/CachePolicy.d.ts +131 -0
  86. package/dist/esm/CachePolicy.d.ts.map +1 -0
  87. package/dist/esm/CachePolicy.js +117 -0
  88. package/dist/esm/CachePolicy.js.map +1 -0
  89. package/dist/esm/Effects.d.ts +240 -0
  90. package/dist/esm/Effects.d.ts.map +1 -0
  91. package/dist/esm/Effects.js +167 -0
  92. package/dist/esm/Effects.js.map +1 -0
  93. package/dist/esm/FileSet.d.ts +288 -0
  94. package/dist/esm/FileSet.d.ts.map +1 -0
  95. package/dist/esm/FileSet.js +324 -0
  96. package/dist/esm/FileSet.js.map +1 -0
  97. package/dist/esm/GraphBuildError.d.ts +122 -0
  98. package/dist/esm/GraphBuildError.d.ts.map +1 -0
  99. package/dist/esm/GraphBuildError.js +146 -0
  100. package/dist/esm/GraphBuildError.js.map +1 -0
  101. package/dist/esm/KeyMaterial.d.ts +102 -0
  102. package/dist/esm/KeyMaterial.d.ts.map +1 -0
  103. package/dist/esm/KeyMaterial.js +85 -0
  104. package/dist/esm/KeyMaterial.js.map +1 -0
  105. package/dist/esm/Node.d.ts +606 -0
  106. package/dist/esm/Node.d.ts.map +1 -0
  107. package/dist/esm/Node.js +521 -0
  108. package/dist/esm/Node.js.map +1 -0
  109. package/dist/esm/Placement.d.ts +117 -0
  110. package/dist/esm/Placement.d.ts.map +1 -0
  111. package/dist/esm/Placement.js +65 -0
  112. package/dist/esm/Placement.js.map +1 -0
  113. package/dist/esm/Plan.d.ts +440 -0
  114. package/dist/esm/Plan.d.ts.map +1 -0
  115. package/dist/esm/Plan.js +673 -0
  116. package/dist/esm/Plan.js.map +1 -0
  117. package/dist/esm/PlanDiff.d.ts +58 -0
  118. package/dist/esm/PlanDiff.d.ts.map +1 -0
  119. package/dist/esm/PlanDiff.js +159 -0
  120. package/dist/esm/PlanDiff.js.map +1 -0
  121. package/dist/esm/Planned.d.ts +121 -0
  122. package/dist/esm/Planned.d.ts.map +1 -0
  123. package/dist/esm/Planned.js +147 -0
  124. package/dist/esm/Planned.js.map +1 -0
  125. package/dist/esm/Repetition.d.ts +31 -0
  126. package/dist/esm/Repetition.d.ts.map +1 -0
  127. package/dist/esm/Repetition.js +24 -0
  128. package/dist/esm/Repetition.js.map +1 -0
  129. package/dist/esm/Scheduling.d.ts +89 -0
  130. package/dist/esm/Scheduling.d.ts.map +1 -0
  131. package/dist/esm/Scheduling.js +82 -0
  132. package/dist/esm/Scheduling.js.map +1 -0
  133. package/dist/esm/StepKey.d.ts +333 -0
  134. package/dist/esm/StepKey.d.ts.map +1 -0
  135. package/dist/esm/StepKey.js +478 -0
  136. package/dist/esm/StepKey.js.map +1 -0
  137. package/dist/esm/index.d.ts +91 -0
  138. package/dist/esm/index.d.ts.map +1 -0
  139. package/dist/esm/index.js +91 -0
  140. package/dist/esm/index.js.map +1 -0
  141. package/dist/esm/internal/ConflictAnnotation.d.ts +59 -0
  142. package/dist/esm/internal/ConflictAnnotation.d.ts.map +1 -0
  143. package/dist/esm/internal/ConflictAnnotation.js +332 -0
  144. package/dist/esm/internal/ConflictAnnotation.js.map +1 -0
  145. package/dist/esm/internal/EffectCandidates.d.ts +19 -0
  146. package/dist/esm/internal/EffectCandidates.d.ts.map +1 -0
  147. package/dist/esm/internal/EffectCandidates.js +86 -0
  148. package/dist/esm/internal/EffectCandidates.js.map +1 -0
  149. package/dist/esm/internal/JsonMirror.d.ts +34 -0
  150. package/dist/esm/internal/JsonMirror.d.ts.map +1 -0
  151. package/dist/esm/internal/JsonMirror.js +204 -0
  152. package/dist/esm/internal/JsonMirror.js.map +1 -0
  153. package/dist/esm/internal/effects.d.ts +245 -0
  154. package/dist/esm/internal/effects.d.ts.map +1 -0
  155. package/dist/esm/internal/effects.js +459 -0
  156. package/dist/esm/internal/effects.js.map +1 -0
  157. package/dist/esm/internal/node.d.ts +526 -0
  158. package/dist/esm/internal/node.d.ts.map +1 -0
  159. package/dist/esm/internal/node.js +564 -0
  160. package/dist/esm/internal/node.js.map +1 -0
  161. package/dist/esm/test/PlanFixtures.d.ts +127 -0
  162. package/dist/esm/test/PlanFixtures.d.ts.map +1 -0
  163. package/dist/esm/test/PlanFixtures.js +60 -0
  164. package/dist/esm/test/PlanFixtures.js.map +1 -0
  165. package/docs/README.md +165 -0
  166. package/docs/api.md +1475 -0
  167. package/docs/concepts/authoring-ast.md +203 -0
  168. package/docs/concepts/effects-and-conflicts.md +135 -0
  169. package/docs/concepts/plan-value.md +168 -0
  170. package/docs/concepts/step-keys.md +179 -0
  171. package/docs/guides/author-a-node-graph.md +182 -0
  172. package/docs/guides/compile-a-plan.md +146 -0
  173. package/docs/guides/declare-file-effects.md +143 -0
  174. package/docs/guides/diff-two-plans.md +101 -0
  175. package/docs/installation.md +71 -0
  176. package/docs/quickstart.md +128 -0
  177. package/docs/testing.md +73 -0
  178. package/docs/troubleshooting.md +314 -0
  179. package/package.json +254 -3
  180. package/src/CachePolicy.ts +143 -0
  181. package/src/Effects.ts +284 -0
  182. package/src/FileSet.ts +411 -0
  183. package/src/GraphBuildError.ts +158 -0
  184. package/src/KeyMaterial.ts +106 -0
  185. package/src/Node.ts +871 -0
  186. package/src/Placement.ts +99 -0
  187. package/src/Plan.ts +868 -0
  188. package/src/PlanDiff.ts +199 -0
  189. package/src/Planned.ts +208 -0
  190. package/src/Repetition.ts +33 -0
  191. package/src/Scheduling.ts +154 -0
  192. package/src/StepKey.ts +710 -0
  193. package/src/index.ts +103 -0
  194. package/src/internal/ConflictAnnotation.ts +374 -0
  195. package/src/internal/EffectCandidates.ts +89 -0
  196. package/src/internal/JsonMirror.ts +255 -0
  197. package/src/internal/effects.ts +543 -0
  198. package/src/internal/node.ts +858 -0
  199. package/src/test/PlanFixtures.ts +76 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The caller-declared cache policy: how far a recorded sealed result may
3
+ * travel, and how long it stays servable.
4
+ *
5
+ * This is the ONE cache-policy model. `@smthrs/flow` publishes it as
6
+ * `CacheEnvironment.CachePolicy` and the engine reads it off a dispatched
7
+ * action; `@smthrs/patterns` declares it over a flow as `WithCache.Policy`. It
8
+ * used to be two declarations in those two packages, tied together by nothing
9
+ * but a re-typed identifier string, so a field added on one side was invisible
10
+ * to the other. It lives here because `@smthrs/plan` is the package both
11
+ * already depend on.
12
+ *
13
+ * The annotation key still reads `"@smthrs/flow/Action/CachePolicy"`.
14
+ * `@smthrs/engine-store`'s `ActionPersistence` reads the policy off stored and
15
+ * dispatched actions by that identifier, so retagging it would make every
16
+ * policy already written invisible at dispatch. The identifier is a wire
17
+ * value, not a location.
18
+ *
19
+ * @since 1.0.0-rc.0
20
+ */
21
+ import * as Context from "effect/Context";
22
+ import * as Schema from "effect/Schema";
23
+ /**
24
+ * How far a recorded sealed result may travel.
25
+ *
26
+ * `shared` is the content-addressed default: the key names the inputs and the
27
+ * environment, so any run on any host that would have produced the same bytes
28
+ * may reuse the row. `run` and `flow` narrow that on purpose — a step whose
29
+ * result is only meaningful inside one execution, or inside one flow, folds
30
+ * that identity into its key so a sibling never reads it.
31
+ *
32
+ * The old scope vocabulary was `run | workflow | global`; `flow` and `shared`
33
+ * are the same three levels named after the current concepts.
34
+ *
35
+ * @category models
36
+ * @since 1.0.0-rc.0
37
+ */
38
+ export declare const CacheScope: Schema.Literals<readonly ["run", "flow", "shared"]>;
39
+ /**
40
+ * The value form of {@link CacheScope}.
41
+ *
42
+ * @category models
43
+ * @since 1.0.0-rc.0
44
+ */
45
+ export type CacheScope = typeof CacheScope.Type;
46
+ /**
47
+ * The caller's declaration about the decay and the reach of a sealed result.
48
+ *
49
+ * `ttlMs` bounds the age of a row the engine may serve: past it the dispatch
50
+ * executes again and journals `cache-expired`, so the refusal is durable
51
+ * evidence a replay reads rather than a fresh clock reading. `scope` decides
52
+ * what the key names besides the inputs.
53
+ *
54
+ * Both fields are optional and both defaults are the pre-policy behavior: no
55
+ * age bound, and the reach the composition's cache environment already
56
+ * granted.
57
+ *
58
+ * @category schemas
59
+ * @since 1.0.0-rc.0
60
+ */
61
+ export declare const CachePolicy: Schema.Struct<{
62
+ readonly ttlMs: Schema.optionalKey<Schema.Int>;
63
+ readonly scope: Schema.optionalKey<Schema.Literals<readonly ["run", "flow", "shared"]>>;
64
+ }>;
65
+ /**
66
+ * The value form of {@link CachePolicy}.
67
+ *
68
+ * @category models
69
+ * @since 1.0.0-rc.0
70
+ */
71
+ export type CachePolicy = typeof CachePolicy.Type;
72
+ /**
73
+ * Annotation key carrying a declaration's {@link CachePolicy}.
74
+ *
75
+ * It is an annotation, not a field on the declaration, for the same reason
76
+ * placement and effects are: the policy is data that travels with the
77
+ * declaration and is read by whoever executes it, and adding it changes no
78
+ * existing call site.
79
+ *
80
+ * @category annotations
81
+ * @since 1.0.0-rc.0
82
+ */
83
+ export declare const CachePolicyAnnotation: Context.Service<{
84
+ readonly ttlMs?: number;
85
+ readonly scope?: "run" | "flow" | "shared";
86
+ }, {
87
+ readonly ttlMs?: number;
88
+ readonly scope?: "run" | "flow" | "shared";
89
+ }>;
90
+ /**
91
+ * Reads the cache policy an annotation bag carries, or `undefined` when it
92
+ * carries none.
93
+ *
94
+ * @category getters
95
+ * @since 1.0.0-rc.0
96
+ */
97
+ export declare const cachePolicyOf: (annotations: Context.Context<never>) => CachePolicy | undefined;
98
+ /**
99
+ * Attaches a cache policy to a declaration that can carry annotations.
100
+ *
101
+ * The declaration is not mutated: `annotate` answers a new one carrying the
102
+ * policy, which is what a plan captures. It is written against the `annotate`
103
+ * method rather than against an action or a flow type so the one declaration
104
+ * serves both, which is what `@smthrs/flow` publishes as
105
+ * `CacheEnvironment.withCache`.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { CachePolicy } from "@smthrs/plan"
110
+ * import { Action } from "@smthrs/flow"
111
+ * import * as Effect from "effect/Effect"
112
+ * import * as Schema from "effect/Schema"
113
+ *
114
+ * const compile = CachePolicy.annotate(
115
+ * Action.make({
116
+ * name: "build/compile",
117
+ * success: Schema.String,
118
+ * tier: "sealed",
119
+ * execute: Effect.succeed("dist/server.js")
120
+ * }),
121
+ * { ttlMs: 60_000, scope: "shared" }
122
+ * )
123
+ * ```
124
+ *
125
+ * @category combinators
126
+ * @since 1.0.0-rc.0
127
+ */
128
+ export declare const annotate: <A extends {
129
+ annotate: (key: typeof CachePolicyAnnotation, value: CachePolicy) => A;
130
+ }>(declaration: A, policy: CachePolicy) => A;
131
+ //# sourceMappingURL=CachePolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachePolicy.d.ts","sourceRoot":"","sources":["../../src/CachePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,qDAA6C,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAU/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW;;;EAGtB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAEjD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;;;EAAkE,CAAA;AAEpG;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAG,WAAW,GAAG,SACJ,CAAA;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS;IAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,qBAAqB,EAAE,KAAK,EAAE,WAAW,KAAK,CAAC,CAAA;CAAE,EAC3G,aAAa,CAAC,EACd,QAAQ,WAAW,KAClB,CAAwD,CAAA"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The caller-declared cache policy: how far a recorded sealed result may
3
+ * travel, and how long it stays servable.
4
+ *
5
+ * This is the ONE cache-policy model. `@smthrs/flow` publishes it as
6
+ * `CacheEnvironment.CachePolicy` and the engine reads it off a dispatched
7
+ * action; `@smthrs/patterns` declares it over a flow as `WithCache.Policy`. It
8
+ * used to be two declarations in those two packages, tied together by nothing
9
+ * but a re-typed identifier string, so a field added on one side was invisible
10
+ * to the other. It lives here because `@smthrs/plan` is the package both
11
+ * already depend on.
12
+ *
13
+ * The annotation key still reads `"@smthrs/flow/Action/CachePolicy"`.
14
+ * `@smthrs/engine-store`'s `ActionPersistence` reads the policy off stored and
15
+ * dispatched actions by that identifier, so retagging it would make every
16
+ * policy already written invisible at dispatch. The identifier is a wire
17
+ * value, not a location.
18
+ *
19
+ * @since 1.0.0-rc.0
20
+ */
21
+ import * as Context from "effect/Context";
22
+ import * as Option from "effect/Option";
23
+ import * as Schema from "effect/Schema";
24
+ /**
25
+ * How far a recorded sealed result may travel.
26
+ *
27
+ * `shared` is the content-addressed default: the key names the inputs and the
28
+ * environment, so any run on any host that would have produced the same bytes
29
+ * may reuse the row. `run` and `flow` narrow that on purpose — a step whose
30
+ * result is only meaningful inside one execution, or inside one flow, folds
31
+ * that identity into its key so a sibling never reads it.
32
+ *
33
+ * The old scope vocabulary was `run | workflow | global`; `flow` and `shared`
34
+ * are the same three levels named after the current concepts.
35
+ *
36
+ * @category models
37
+ * @since 1.0.0-rc.0
38
+ */
39
+ export const CacheScope = Schema.Literals(["run", "flow", "shared"]);
40
+ /**
41
+ * A positive whole number of milliseconds.
42
+ *
43
+ * @private
44
+ * @since 1.0.0-rc.0
45
+ */
46
+ const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
47
+ /**
48
+ * The caller's declaration about the decay and the reach of a sealed result.
49
+ *
50
+ * `ttlMs` bounds the age of a row the engine may serve: past it the dispatch
51
+ * executes again and journals `cache-expired`, so the refusal is durable
52
+ * evidence a replay reads rather than a fresh clock reading. `scope` decides
53
+ * what the key names besides the inputs.
54
+ *
55
+ * Both fields are optional and both defaults are the pre-policy behavior: no
56
+ * age bound, and the reach the composition's cache environment already
57
+ * granted.
58
+ *
59
+ * @category schemas
60
+ * @since 1.0.0-rc.0
61
+ */
62
+ export const CachePolicy = Schema.Struct({
63
+ ttlMs: Schema.optionalKey(PositiveMillis),
64
+ scope: Schema.optionalKey(CacheScope)
65
+ });
66
+ /**
67
+ * Annotation key carrying a declaration's {@link CachePolicy}.
68
+ *
69
+ * It is an annotation, not a field on the declaration, for the same reason
70
+ * placement and effects are: the policy is data that travels with the
71
+ * declaration and is read by whoever executes it, and adding it changes no
72
+ * existing call site.
73
+ *
74
+ * @category annotations
75
+ * @since 1.0.0-rc.0
76
+ */
77
+ export const CachePolicyAnnotation = Context.Service("@smthrs/flow/Action/CachePolicy");
78
+ /**
79
+ * Reads the cache policy an annotation bag carries, or `undefined` when it
80
+ * carries none.
81
+ *
82
+ * @category getters
83
+ * @since 1.0.0-rc.0
84
+ */
85
+ export const cachePolicyOf = (annotations) => Option.getOrUndefined(Context.getOption(annotations, CachePolicyAnnotation));
86
+ /**
87
+ * Attaches a cache policy to a declaration that can carry annotations.
88
+ *
89
+ * The declaration is not mutated: `annotate` answers a new one carrying the
90
+ * policy, which is what a plan captures. It is written against the `annotate`
91
+ * method rather than against an action or a flow type so the one declaration
92
+ * serves both, which is what `@smthrs/flow` publishes as
93
+ * `CacheEnvironment.withCache`.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * import { CachePolicy } from "@smthrs/plan"
98
+ * import { Action } from "@smthrs/flow"
99
+ * import * as Effect from "effect/Effect"
100
+ * import * as Schema from "effect/Schema"
101
+ *
102
+ * const compile = CachePolicy.annotate(
103
+ * Action.make({
104
+ * name: "build/compile",
105
+ * success: Schema.String,
106
+ * tier: "sealed",
107
+ * execute: Effect.succeed("dist/server.js")
108
+ * }),
109
+ * { ttlMs: 60_000, scope: "shared" }
110
+ * )
111
+ * ```
112
+ *
113
+ * @category combinators
114
+ * @since 1.0.0-rc.0
115
+ */
116
+ export const annotate = (declaration, policy) => declaration.annotate(CachePolicyAnnotation, policy);
117
+ //# sourceMappingURL=CachePolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachePolicy.js","sourceRoot":"","sources":["../../src/CachePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;AAUpE;;;;;GAKG;AACH,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;AAEhE;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC;IACzC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC;CACtC,CAAC,CAAA;AAUF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAc,iCAAiC,CAAC,CAAA;AAEpG;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,WAAmC,EAA2B,EAAE,CAC5F,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,qBAAqB,CAAC,CAAC,CAAA;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,WAAc,EACd,MAAmB,EAChB,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA","sourcesContent":["/**\n * The caller-declared cache policy: how far a recorded sealed result may\n * travel, and how long it stays servable.\n *\n * This is the ONE cache-policy model. `@smthrs/flow` publishes it as\n * `CacheEnvironment.CachePolicy` and the engine reads it off a dispatched\n * action; `@smthrs/patterns` declares it over a flow as `WithCache.Policy`. It\n * used to be two declarations in those two packages, tied together by nothing\n * but a re-typed identifier string, so a field added on one side was invisible\n * to the other. It lives here because `@smthrs/plan` is the package both\n * already depend on.\n *\n * The annotation key still reads `\"@smthrs/flow/Action/CachePolicy\"`.\n * `@smthrs/engine-store`'s `ActionPersistence` reads the policy off stored and\n * dispatched actions by that identifier, so retagging it would make every\n * policy already written invisible at dispatch. The identifier is a wire\n * value, not a location.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Context from \"effect/Context\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * How far a recorded sealed result may travel.\n *\n * `shared` is the content-addressed default: the key names the inputs and the\n * environment, so any run on any host that would have produced the same bytes\n * may reuse the row. `run` and `flow` narrow that on purpose — a step whose\n * result is only meaningful inside one execution, or inside one flow, folds\n * that identity into its key so a sibling never reads it.\n *\n * The old scope vocabulary was `run | workflow | global`; `flow` and `shared`\n * are the same three levels named after the current concepts.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const CacheScope = Schema.Literals([\"run\", \"flow\", \"shared\"])\n\n/**\n * The value form of {@link CacheScope}.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type CacheScope = typeof CacheScope.Type\n\n/**\n * A positive whole number of milliseconds.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nconst PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0))\n\n/**\n * The caller's declaration about the decay and the reach of a sealed result.\n *\n * `ttlMs` bounds the age of a row the engine may serve: past it the dispatch\n * executes again and journals `cache-expired`, so the refusal is durable\n * evidence a replay reads rather than a fresh clock reading. `scope` decides\n * what the key names besides the inputs.\n *\n * Both fields are optional and both defaults are the pre-policy behavior: no\n * age bound, and the reach the composition's cache environment already\n * granted.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n */\nexport const CachePolicy = Schema.Struct({\n ttlMs: Schema.optionalKey(PositiveMillis),\n scope: Schema.optionalKey(CacheScope)\n})\n\n/**\n * The value form of {@link CachePolicy}.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type CachePolicy = typeof CachePolicy.Type\n\n/**\n * Annotation key carrying a declaration's {@link CachePolicy}.\n *\n * It is an annotation, not a field on the declaration, for the same reason\n * placement and effects are: the policy is data that travels with the\n * declaration and is read by whoever executes it, and adding it changes no\n * existing call site.\n *\n * @category annotations\n * @since 1.0.0-rc.0\n */\nexport const CachePolicyAnnotation = Context.Service<CachePolicy>(\"@smthrs/flow/Action/CachePolicy\")\n\n/**\n * Reads the cache policy an annotation bag carries, or `undefined` when it\n * carries none.\n *\n * @category getters\n * @since 1.0.0-rc.0\n */\nexport const cachePolicyOf = (annotations: Context.Context<never>): CachePolicy | undefined =>\n Option.getOrUndefined(Context.getOption(annotations, CachePolicyAnnotation))\n\n/**\n * Attaches a cache policy to a declaration that can carry annotations.\n *\n * The declaration is not mutated: `annotate` answers a new one carrying the\n * policy, which is what a plan captures. It is written against the `annotate`\n * method rather than against an action or a flow type so the one declaration\n * serves both, which is what `@smthrs/flow` publishes as\n * `CacheEnvironment.withCache`.\n *\n * @example\n * ```ts\n * import { CachePolicy } from \"@smthrs/plan\"\n * import { Action } from \"@smthrs/flow\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n *\n * const compile = CachePolicy.annotate(\n * Action.make({\n * name: \"build/compile\",\n * success: Schema.String,\n * tier: \"sealed\",\n * execute: Effect.succeed(\"dist/server.js\")\n * }),\n * { ttlMs: 60_000, scope: \"shared\" }\n * )\n * ```\n *\n * @category combinators\n * @since 1.0.0-rc.0\n */\nexport const annotate = <A extends { annotate: (key: typeof CachePolicyAnnotation, value: CachePolicy) => A }>(\n declaration: A,\n policy: CachePolicy\n): A => declaration.annotate(CachePolicyAnnotation, policy)\n"]}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Pure effect declarations used to describe flow read and write envelopes.
3
+ *
4
+ * This is the ONE effect-envelope model. `@smthrs/flow` enforces it while it
5
+ * builds a graph, `@smthrs/core` re-exports it, and `@smthrs/patterns` reads it
6
+ * to intersect a decorator's declaration with the flow it wraps. It lives here
7
+ * because `@smthrs/plan` is the lowest package all three depend on, beside the
8
+ * `tier` and `onConflict` vocabularies a plan already speaks.
9
+ *
10
+ * Two layers: the declaration API, which a flow author and a catalog use, and
11
+ * the prepared matching API below it, which a graph builder uses to check one
12
+ * envelope against many steps without re-reading the envelope each time.
13
+ *
14
+ * Governing contract: `packages/smithers/flows/plan/docs/api.md`, published as
15
+ * https://smithers.sh/docs/reference/api/plan.
16
+ *
17
+ * @since 0.0.0
18
+ */
19
+ import * as Context from "effect/Context";
20
+ import * as Schema from "effect/Schema";
21
+ /**
22
+ * How reversible the work beneath a declaration is.
23
+ *
24
+ * This is the ONE tier vocabulary. `KeyMaterial.kind` is this schema, and so
25
+ * is `@smthrs/flow`'s `Action.Tier`: an action's durability and a declared
26
+ * envelope's ceiling are the same three words, and a fourth spelling of them
27
+ * in one of the three places would fork identity silently.
28
+ *
29
+ * @category schemas
30
+ * @since 1.0.0-rc.0
31
+ */
32
+ export declare const Tier: Schema.Literals<readonly ["sealed", "compensable", "irreversible"]>;
33
+ /**
34
+ * The value form of {@link Tier}.
35
+ *
36
+ * @category models
37
+ * @since 1.0.0-rc.0
38
+ */
39
+ export type Tier = typeof Tier.Type;
40
+ /**
41
+ * How hermetic the work beneath a declaration is.
42
+ *
43
+ * This is the ONE mode vocabulary: the two words were written out in
44
+ * `Declaration`, in `MakeOptions`, and twice more inside the matcher.
45
+ *
46
+ * @category schemas
47
+ * @since 1.0.0-rc.0
48
+ */
49
+ export declare const Mode: Schema.Literals<readonly ["hermetic", "expected"]>;
50
+ /**
51
+ * The value form of {@link Mode}.
52
+ *
53
+ * @category models
54
+ * @since 1.0.0-rc.0
55
+ */
56
+ export type Mode = typeof Mode.Type;
57
+ /**
58
+ * What a plan does when two nodes write the same path.
59
+ *
60
+ * This is the ONE conflict vocabulary: `Declaration.onConflict` declares it and
61
+ * `Plan.PairStrategy` is this same schema, resolved per pair by
62
+ * `Plan.compile`.
63
+ *
64
+ * @category schemas
65
+ * @since 1.0.0-rc.0
66
+ */
67
+ export declare const ConflictStrategy: Schema.Literals<readonly ["serialize", "lane", "fail"]>;
68
+ /**
69
+ * The value form of {@link ConflictStrategy}.
70
+ *
71
+ * @category models
72
+ * @since 1.0.0-rc.0
73
+ */
74
+ export type ConflictStrategy = typeof ConflictStrategy.Type;
75
+ /**
76
+ * The annotation key a flow's declared effect ENVELOPE is carried under.
77
+ *
78
+ * One key, here, because two graph builders read it: `@smthrs/flow`'s
79
+ * `Graph.build` enforces the envelope it names, and `@smthrs/core` re-exports
80
+ * this key as `Annotations.Effects`. While they were separate keys a flow
81
+ * annotated for one builder was invisible to the other.
82
+ *
83
+ * It is a `Context` key, not hashed material: annotating a flow with an
84
+ * envelope changes what a build ADMITS, and the declaration reaches identity
85
+ * only through `KeyMaterial.kind`.
86
+ *
87
+ * @category annotations
88
+ * @since 1.0.0-rc.0
89
+ */
90
+ export declare const Envelope: Context.Service<Declaration, Declaration>;
91
+ /**
92
+ * The prepared matching API: what a graph builder needs to check one envelope
93
+ * against every step it encloses, and to find the write overlaps between two
94
+ * declarations, at the cost the public functions above pay once rather than
95
+ * once per step.
96
+ *
97
+ * {@link prepareEnvelope} reads an envelope once into the form
98
+ * {@link narrowPrepared} answers from, which is what makes a wide envelope cost
99
+ * its size per build rather than per node. {@link indexPaths},
100
+ * {@link rankPaths} and {@link overlapRanks} are the same decomposition for
101
+ * write overlap, which {@link overlaps} wraps for the pairwise case.
102
+ * {@link boundedEffects} snapshots a declaration into builder-owned data,
103
+ * refusing through the caller's own error before the copy grows past its limit,
104
+ * and {@link maximumPathLength} and {@link maximumGlobs} are the per-path bounds
105
+ * that refusal enforces.
106
+ *
107
+ * @category matching
108
+ * @since 1.0.0-rc.0
109
+ */
110
+ export type { PathIndex, PreparedEnvelope, Ranked } from "./internal/effects.ts";
111
+ /**
112
+ * The prepared matching API. See the type re-exports above for what each member
113
+ * is for.
114
+ *
115
+ * @category matching
116
+ * @since 1.0.0-rc.0
117
+ */
118
+ export { boundedEffects, indexPaths, maximumGlobs, maximumPathLength, narrowPrepared, overlapRanks, prepareEnvelope, rankPaths } from "./internal/effects.ts";
119
+ /**
120
+ * A normalized description of the resources a flow or step may read and
121
+ * write.
122
+ *
123
+ * @category models
124
+ * @since 0.0.0
125
+ * @slop
126
+ */
127
+ export interface Declaration {
128
+ readonly reads: ReadonlyArray<string>;
129
+ readonly writes: ReadonlyArray<string>;
130
+ readonly mode: Mode;
131
+ readonly onConflict: ConflictStrategy;
132
+ readonly tier?: Tier | undefined;
133
+ }
134
+ /**
135
+ * Input accepted by {@link make}. Iterables are normalized into sorted,
136
+ * duplicate-free arrays so declarations are deterministic key material.
137
+ * Envelope entries and declared paths must already be path-normalized: no
138
+ * separator or dot-segment rewriting is performed. A declared path containing
139
+ * a whole `.` or `..` segment is never covered and therefore surfaces from
140
+ * {@link narrow} as an `effect_outside_envelope` diagnostic.
141
+ *
142
+ * @category models
143
+ * @since 0.0.0
144
+ * @slop
145
+ */
146
+ export interface MakeOptions {
147
+ readonly reads: Iterable<string>;
148
+ readonly writes: Iterable<string>;
149
+ readonly mode: Mode;
150
+ readonly onConflict: ConflictStrategy;
151
+ readonly tier?: Tier | undefined;
152
+ }
153
+ /**
154
+ * A result of checking that a step declaration narrows a flow envelope.
155
+ *
156
+ * @category models
157
+ * @since 0.0.0
158
+ * @slop
159
+ */
160
+ export type NarrowResult = {
161
+ readonly ok: true;
162
+ } | {
163
+ readonly ok: false;
164
+ readonly code: "effect_outside_envelope" | "effect_mode_widening" | "effect_tier_widening";
165
+ readonly paths: ReadonlyArray<string>;
166
+ };
167
+ /**
168
+ * Constructs a deterministic effect declaration.
169
+ *
170
+ * @category constructors
171
+ * @since 0.0.0
172
+ * @slop
173
+ */
174
+ export declare const make: (input: MakeOptions) => Declaration;
175
+ /**
176
+ * Checks whether `path` is covered by an envelope entry.
177
+ *
178
+ * The grammar is exhaustive: an exact path matches itself; `*` and `**` match
179
+ * everything; `prefix*` matches by string prefix; and `prefix/**` matches
180
+ * `prefix/` and everything below it, but not bare `prefix`. This is
181
+ * intentionally not full minimatch syntax. Envelope entries and declared paths
182
+ * must be normalized. A path containing a whole `.` or `..` segment is never
183
+ * covered, so {@link narrow} reports it as `effect_outside_envelope` rather
184
+ * than silently accepting an escape.
185
+ *
186
+ * @category predicates
187
+ * @since 0.0.0
188
+ * @slop
189
+ */
190
+ export declare const covers: (envelope: string, path: string) => boolean;
191
+ /**
192
+ * Verifies that a step declaration stays within an enclosing flow envelope.
193
+ *
194
+ * Read and write paths must be covered independently. A step may tighten
195
+ * `expected` to `hermetic`, but cannot widen `hermetic` to `expected`.
196
+ * Effect tiers narrow from irreversible to compensable to sealed.
197
+ *
198
+ * The envelope's lists are prepared once: exact entries go into a set and
199
+ * covering patterns collapse to their outermost prefixes, sorted, so each step
200
+ * path costs one dot-segment scan, one lookup, and one binary search ending in
201
+ * one prefix comparison, whatever the envelope's width or how many of its
202
+ * patterns nest. A graph build prepares each envelope once for every node it
203
+ * encloses.
204
+ *
205
+ * @category validation
206
+ * @since 0.0.0
207
+ * @slop
208
+ */
209
+ export declare const narrow: (envelope: Declaration, step: Declaration) => NarrowResult;
210
+ /**
211
+ * Returns the concrete or narrower write declarations shared by two effect
212
+ * declarations. The result is sorted and duplicate-free.
213
+ *
214
+ * Two declarations of the same literal path always overlap, including a path
215
+ * {@link covers} refuses to match because it carries a `.` or `..` segment.
216
+ * Glob coverage stays strict for those paths, so an unnormalized declaration
217
+ * still escapes no envelope, but two writers naming the same unnormalized path
218
+ * are still detected as writing the same resource.
219
+ *
220
+ * The union of both declarations is indexed once: the distinct paths are
221
+ * sorted, each is scanned once for a dot segment, and each pattern's prefix
222
+ * is located by binary search. Exact paths then match through a merge of two
223
+ * rank lists and each covering pattern enumerates the other declaration's
224
+ * ranks inside its interval, so the cost is linear in the two declarations
225
+ * plus the matches, whatever the paths' lengths or how many patterns nest.
226
+ *
227
+ * @category analysis
228
+ * @since 0.0.0
229
+ * @slop
230
+ */
231
+ export declare const overlaps: (a: Declaration, b: Declaration) => ReadonlyArray<string>;
232
+ /**
233
+ * Returns a sealed, hermetic copy of an effect declaration.
234
+ *
235
+ * @category constructors
236
+ * @since 0.0.0
237
+ * @slop
238
+ */
239
+ export declare const sealed: (declaration: Declaration) => Declaration;
240
+ //# sourceMappingURL=Effects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Effects.d.ts","sourceRoot":"","sources":["../../src/Effects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,qEAA6D,CAAA;AAE9E;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAA;AAEnC;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,oDAA4C,CAAA;AAE7D;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAA;AAEnC;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,yDAAiD,CAAA;AAE9E;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAA;AAE3D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,2CAAgE,CAAA;AAErF;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEhF;;;;;;GAMG;AACH,OAAO,EACL,cAAc,EACd,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,SAAS,EACV,MAAM,uBAAuB,CAAA;AAE9B;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACtC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAA;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACjC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAA;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GACrB;IACA,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAA;IAClB,QAAQ,CAAC,IAAI,EAAE,yBAAyB,GAAG,sBAAsB,GAAG,sBAAsB,CAAA;IAC1F,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACtC,CAAA;AAIH;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,WAAW,KAAG,WAMxC,CAAA;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,OAGvD,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,MAAM,GAAI,UAAU,WAAW,EAAE,MAAM,WAAW,KAAG,YACL,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,QAAQ,GAAI,GAAG,WAAW,EAAE,GAAG,WAAW,KAAG,aAAa,CAAC,MAAM,CAI7E,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,GAAI,aAAa,WAAW,KAAG,WACU,CAAA"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Pure effect declarations used to describe flow read and write envelopes.
3
+ *
4
+ * This is the ONE effect-envelope model. `@smthrs/flow` enforces it while it
5
+ * builds a graph, `@smthrs/core` re-exports it, and `@smthrs/patterns` reads it
6
+ * to intersect a decorator's declaration with the flow it wraps. It lives here
7
+ * because `@smthrs/plan` is the lowest package all three depend on, beside the
8
+ * `tier` and `onConflict` vocabularies a plan already speaks.
9
+ *
10
+ * Two layers: the declaration API, which a flow author and a catalog use, and
11
+ * the prepared matching API below it, which a graph builder uses to check one
12
+ * envelope against many steps without re-reading the envelope each time.
13
+ *
14
+ * Governing contract: `packages/smithers/flows/plan/docs/api.md`, published as
15
+ * https://smithers.sh/docs/reference/api/plan.
16
+ *
17
+ * @since 0.0.0
18
+ */
19
+ import * as Context from "effect/Context";
20
+ import * as Schema from "effect/Schema";
21
+ import * as Index from "./internal/effects.js";
22
+ /**
23
+ * How reversible the work beneath a declaration is.
24
+ *
25
+ * This is the ONE tier vocabulary. `KeyMaterial.kind` is this schema, and so
26
+ * is `@smthrs/flow`'s `Action.Tier`: an action's durability and a declared
27
+ * envelope's ceiling are the same three words, and a fourth spelling of them
28
+ * in one of the three places would fork identity silently.
29
+ *
30
+ * @category schemas
31
+ * @since 1.0.0-rc.0
32
+ */
33
+ export const Tier = Schema.Literals(["sealed", "compensable", "irreversible"]);
34
+ /**
35
+ * How hermetic the work beneath a declaration is.
36
+ *
37
+ * This is the ONE mode vocabulary: the two words were written out in
38
+ * `Declaration`, in `MakeOptions`, and twice more inside the matcher.
39
+ *
40
+ * @category schemas
41
+ * @since 1.0.0-rc.0
42
+ */
43
+ export const Mode = Schema.Literals(["hermetic", "expected"]);
44
+ /**
45
+ * What a plan does when two nodes write the same path.
46
+ *
47
+ * This is the ONE conflict vocabulary: `Declaration.onConflict` declares it and
48
+ * `Plan.PairStrategy` is this same schema, resolved per pair by
49
+ * `Plan.compile`.
50
+ *
51
+ * @category schemas
52
+ * @since 1.0.0-rc.0
53
+ */
54
+ export const ConflictStrategy = Schema.Literals(["serialize", "lane", "fail"]);
55
+ /**
56
+ * The annotation key a flow's declared effect ENVELOPE is carried under.
57
+ *
58
+ * One key, here, because two graph builders read it: `@smthrs/flow`'s
59
+ * `Graph.build` enforces the envelope it names, and `@smthrs/core` re-exports
60
+ * this key as `Annotations.Effects`. While they were separate keys a flow
61
+ * annotated for one builder was invisible to the other.
62
+ *
63
+ * It is a `Context` key, not hashed material: annotating a flow with an
64
+ * envelope changes what a build ADMITS, and the declaration reaches identity
65
+ * only through `KeyMaterial.kind`.
66
+ *
67
+ * @category annotations
68
+ * @since 1.0.0-rc.0
69
+ */
70
+ export const Envelope = Context.Service("@smthrs/plan/Effects/Envelope");
71
+ /**
72
+ * The prepared matching API. See the type re-exports above for what each member
73
+ * is for.
74
+ *
75
+ * @category matching
76
+ * @since 1.0.0-rc.0
77
+ */
78
+ export { boundedEffects, indexPaths, maximumGlobs, maximumPathLength, narrowPrepared, overlapRanks, prepareEnvelope, rankPaths } from "./internal/effects.js";
79
+ const normalize = (paths) => [...new Set(paths)].sort();
80
+ /**
81
+ * Constructs a deterministic effect declaration.
82
+ *
83
+ * @category constructors
84
+ * @since 0.0.0
85
+ * @slop
86
+ */
87
+ export const make = (input) => ({
88
+ reads: normalize(input.reads),
89
+ writes: normalize(input.writes),
90
+ mode: input.mode,
91
+ onConflict: input.onConflict,
92
+ ...(input.tier === undefined ? {} : { tier: input.tier })
93
+ });
94
+ /**
95
+ * Checks whether `path` is covered by an envelope entry.
96
+ *
97
+ * The grammar is exhaustive: an exact path matches itself; `*` and `**` match
98
+ * everything; `prefix*` matches by string prefix; and `prefix/**` matches
99
+ * `prefix/` and everything below it, but not bare `prefix`. This is
100
+ * intentionally not full minimatch syntax. Envelope entries and declared paths
101
+ * must be normalized. A path containing a whole `.` or `..` segment is never
102
+ * covered, so {@link narrow} reports it as `effect_outside_envelope` rather
103
+ * than silently accepting an escape.
104
+ *
105
+ * @category predicates
106
+ * @since 0.0.0
107
+ * @slop
108
+ */
109
+ export const covers = (envelope, path) => {
110
+ if (Index.hasDotSegment(path))
111
+ return false;
112
+ return envelope === path || (Index.isGlob(envelope) && path.startsWith(Index.globPrefix(envelope)));
113
+ };
114
+ /**
115
+ * Verifies that a step declaration stays within an enclosing flow envelope.
116
+ *
117
+ * Read and write paths must be covered independently. A step may tighten
118
+ * `expected` to `hermetic`, but cannot widen `hermetic` to `expected`.
119
+ * Effect tiers narrow from irreversible to compensable to sealed.
120
+ *
121
+ * The envelope's lists are prepared once: exact entries go into a set and
122
+ * covering patterns collapse to their outermost prefixes, sorted, so each step
123
+ * path costs one dot-segment scan, one lookup, and one binary search ending in
124
+ * one prefix comparison, whatever the envelope's width or how many of its
125
+ * patterns nest. A graph build prepares each envelope once for every node it
126
+ * encloses.
127
+ *
128
+ * @category validation
129
+ * @since 0.0.0
130
+ * @slop
131
+ */
132
+ export const narrow = (envelope, step) => Index.narrowPrepared(Index.prepareEnvelope(envelope), step);
133
+ /**
134
+ * Returns the concrete or narrower write declarations shared by two effect
135
+ * declarations. The result is sorted and duplicate-free.
136
+ *
137
+ * Two declarations of the same literal path always overlap, including a path
138
+ * {@link covers} refuses to match because it carries a `.` or `..` segment.
139
+ * Glob coverage stays strict for those paths, so an unnormalized declaration
140
+ * still escapes no envelope, but two writers naming the same unnormalized path
141
+ * are still detected as writing the same resource.
142
+ *
143
+ * The union of both declarations is indexed once: the distinct paths are
144
+ * sorted, each is scanned once for a dot segment, and each pattern's prefix
145
+ * is located by binary search. Exact paths then match through a merge of two
146
+ * rank lists and each covering pattern enumerates the other declaration's
147
+ * ranks inside its interval, so the cost is linear in the two declarations
148
+ * plus the matches, whatever the paths' lengths or how many patterns nest.
149
+ *
150
+ * @category analysis
151
+ * @since 0.0.0
152
+ * @slop
153
+ */
154
+ export const overlaps = (a, b) => {
155
+ const indexed = Index.indexPaths([a.writes, b.writes]);
156
+ return Index.overlapRanks(indexed, Index.rankPaths(indexed, a.writes), Index.rankPaths(indexed, b.writes))
157
+ .map((rank) => indexed.paths[rank]);
158
+ };
159
+ /**
160
+ * Returns a sealed, hermetic copy of an effect declaration.
161
+ *
162
+ * @category constructors
163
+ * @since 0.0.0
164
+ * @slop
165
+ */
166
+ export const sealed = (declaration) => make({ ...declaration, mode: "hermetic", tier: "sealed" });
167
+ //# sourceMappingURL=Effects.js.map