@manifesto-ai/core 0.3.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/README.md +148 -498
  2. package/dist/__tests__/apply.test.d.ts +2 -0
  3. package/dist/__tests__/apply.test.d.ts.map +1 -0
  4. package/dist/__tests__/apply.test.js +144 -0
  5. package/dist/__tests__/apply.test.js.map +1 -0
  6. package/dist/__tests__/jcs.test.d.ts +2 -0
  7. package/dist/__tests__/jcs.test.d.ts.map +1 -0
  8. package/dist/__tests__/jcs.test.js +45 -0
  9. package/dist/__tests__/jcs.test.js.map +1 -0
  10. package/dist/core/apply.d.ts +17 -0
  11. package/dist/core/apply.d.ts.map +1 -0
  12. package/dist/core/apply.js +130 -0
  13. package/dist/core/apply.js.map +1 -0
  14. package/dist/core/compute.d.ts +17 -0
  15. package/dist/core/compute.d.ts.map +1 -0
  16. package/dist/core/compute.js +287 -0
  17. package/dist/core/compute.js.map +1 -0
  18. package/dist/core/compute.test.d.ts +2 -0
  19. package/dist/core/compute.test.d.ts.map +1 -0
  20. package/dist/core/compute.test.js +633 -0
  21. package/dist/core/compute.test.js.map +1 -0
  22. package/dist/core/explain.d.ts +14 -0
  23. package/dist/core/explain.d.ts.map +1 -0
  24. package/dist/core/explain.js +78 -0
  25. package/dist/core/explain.js.map +1 -0
  26. package/dist/core/index.d.ts +5 -0
  27. package/dist/core/index.d.ts.map +1 -0
  28. package/dist/core/index.js +5 -0
  29. package/dist/core/index.js.map +1 -0
  30. package/dist/core/validate.d.ts +16 -0
  31. package/dist/core/validate.d.ts.map +1 -0
  32. package/dist/core/validate.js +361 -0
  33. package/dist/core/validate.js.map +1 -0
  34. package/dist/core/validate.test.d.ts +2 -0
  35. package/dist/core/validate.test.d.ts.map +1 -0
  36. package/dist/core/validate.test.js +638 -0
  37. package/dist/core/validate.test.js.map +1 -0
  38. package/dist/core/validation-utils.d.ts +20 -0
  39. package/dist/core/validation-utils.d.ts.map +1 -0
  40. package/dist/core/validation-utils.js +289 -0
  41. package/dist/core/validation-utils.js.map +1 -0
  42. package/dist/errors.d.ts +30 -0
  43. package/dist/errors.d.ts.map +1 -0
  44. package/dist/errors.js +51 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/evaluator/computed.d.ts +14 -0
  47. package/dist/evaluator/computed.d.ts.map +1 -0
  48. package/dist/evaluator/computed.js +60 -0
  49. package/dist/evaluator/computed.js.map +1 -0
  50. package/dist/evaluator/context.d.ts +59 -0
  51. package/dist/evaluator/context.d.ts.map +1 -0
  52. package/dist/evaluator/context.js +41 -0
  53. package/dist/evaluator/context.js.map +1 -0
  54. package/dist/evaluator/dag.d.ts +30 -0
  55. package/dist/evaluator/dag.d.ts.map +1 -0
  56. package/dist/evaluator/dag.js +121 -0
  57. package/dist/evaluator/dag.js.map +1 -0
  58. package/dist/evaluator/expr.d.ts +11 -0
  59. package/dist/evaluator/expr.d.ts.map +1 -0
  60. package/dist/evaluator/expr.js +649 -0
  61. package/dist/evaluator/expr.js.map +1 -0
  62. package/dist/evaluator/expr.test.d.ts +2 -0
  63. package/dist/evaluator/expr.test.d.ts.map +1 -0
  64. package/dist/evaluator/expr.test.js +449 -0
  65. package/dist/evaluator/expr.test.js.map +1 -0
  66. package/dist/evaluator/flow.d.ts +35 -0
  67. package/dist/evaluator/flow.d.ts.map +1 -0
  68. package/dist/evaluator/flow.js +387 -0
  69. package/dist/evaluator/flow.js.map +1 -0
  70. package/dist/evaluator/flow.test.d.ts +2 -0
  71. package/dist/evaluator/flow.test.d.ts.map +1 -0
  72. package/dist/evaluator/flow.test.js +499 -0
  73. package/dist/evaluator/flow.test.js.map +1 -0
  74. package/dist/evaluator/index.d.ts +6 -0
  75. package/dist/evaluator/index.d.ts.map +1 -0
  76. package/dist/evaluator/index.js +6 -0
  77. package/dist/evaluator/index.js.map +1 -0
  78. package/dist/factories.d.ts +22 -0
  79. package/dist/factories.d.ts.map +1 -0
  80. package/dist/factories.js +44 -0
  81. package/dist/factories.js.map +1 -0
  82. package/dist/index.d.ts +47 -11
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +30 -45
  85. package/dist/index.js.map +1 -1
  86. package/dist/index.test.d.ts +2 -0
  87. package/dist/index.test.d.ts.map +1 -0
  88. package/dist/index.test.js +13 -0
  89. package/dist/index.test.js.map +1 -0
  90. package/dist/schema/action.d.ts +14 -0
  91. package/dist/schema/action.d.ts.map +1 -0
  92. package/dist/schema/action.js +30 -0
  93. package/dist/schema/action.js.map +1 -0
  94. package/dist/schema/common.d.ts +37 -0
  95. package/dist/schema/common.d.ts.map +1 -0
  96. package/dist/schema/common.js +20 -0
  97. package/dist/schema/common.js.map +1 -0
  98. package/dist/schema/computed.d.ts +23 -0
  99. package/dist/schema/computed.d.ts.map +1 -0
  100. package/dist/schema/computed.js +34 -0
  101. package/dist/schema/computed.js.map +1 -0
  102. package/dist/schema/domain.d.ts +50 -0
  103. package/dist/schema/domain.d.ts.map +1 -0
  104. package/dist/schema/domain.js +60 -0
  105. package/dist/schema/domain.js.map +1 -0
  106. package/dist/schema/expr.d.ts +303 -0
  107. package/dist/schema/expr.d.ts.map +1 -0
  108. package/dist/schema/expr.js +283 -0
  109. package/dist/schema/expr.js.map +1 -0
  110. package/dist/schema/field.d.ts +48 -0
  111. package/dist/schema/field.d.ts.map +1 -0
  112. package/dist/schema/field.js +31 -0
  113. package/dist/schema/field.js.map +1 -0
  114. package/dist/schema/flow.d.ts +103 -0
  115. package/dist/schema/flow.d.ts.map +1 -0
  116. package/dist/schema/flow.js +82 -0
  117. package/dist/schema/flow.js.map +1 -0
  118. package/dist/schema/host-context.d.ts +12 -0
  119. package/dist/schema/host-context.d.ts.map +1 -0
  120. package/dist/schema/host-context.js +23 -0
  121. package/dist/schema/host-context.js.map +1 -0
  122. package/dist/schema/index.d.ts +13 -2
  123. package/dist/schema/index.d.ts.map +1 -1
  124. package/dist/schema/index.js +25 -2
  125. package/dist/schema/index.js.map +1 -1
  126. package/dist/schema/patch.d.ts +59 -0
  127. package/dist/schema/patch.d.ts.map +1 -0
  128. package/dist/schema/patch.js +60 -0
  129. package/dist/schema/patch.js.map +1 -0
  130. package/dist/schema/result.d.ts +142 -0
  131. package/dist/schema/result.d.ts.map +1 -0
  132. package/dist/schema/result.js +94 -0
  133. package/dist/schema/result.js.map +1 -0
  134. package/dist/schema/snapshot.d.ts +153 -0
  135. package/dist/schema/snapshot.d.ts.map +1 -0
  136. package/dist/schema/snapshot.js +160 -0
  137. package/dist/schema/snapshot.js.map +1 -0
  138. package/dist/schema/trace.d.ts +98 -0
  139. package/dist/schema/trace.d.ts.map +1 -0
  140. package/dist/schema/trace.js +90 -0
  141. package/dist/schema/trace.js.map +1 -0
  142. package/dist/schema/type-spec.d.ts +34 -0
  143. package/dist/schema/type-spec.d.ts.map +1 -0
  144. package/dist/schema/type-spec.js +40 -0
  145. package/dist/schema/type-spec.js.map +1 -0
  146. package/dist/utils/canonical.d.ts +37 -0
  147. package/dist/utils/canonical.d.ts.map +1 -0
  148. package/dist/utils/canonical.js +122 -0
  149. package/dist/utils/canonical.js.map +1 -0
  150. package/dist/utils/canonical.test.d.ts +2 -0
  151. package/dist/utils/canonical.test.d.ts.map +1 -0
  152. package/dist/utils/canonical.test.js +183 -0
  153. package/dist/utils/canonical.test.js.map +1 -0
  154. package/dist/utils/hash.d.ts +28 -0
  155. package/dist/utils/hash.d.ts.map +1 -0
  156. package/dist/utils/hash.js +152 -0
  157. package/dist/utils/hash.js.map +1 -0
  158. package/dist/utils/hash.test.d.ts +2 -0
  159. package/dist/utils/hash.test.d.ts.map +1 -0
  160. package/dist/utils/hash.test.js +170 -0
  161. package/dist/utils/hash.test.js.map +1 -0
  162. package/dist/utils/index.d.ts +4 -0
  163. package/dist/utils/index.d.ts.map +1 -0
  164. package/dist/utils/index.js +4 -0
  165. package/dist/utils/index.js.map +1 -0
  166. package/dist/utils/path.d.ts +40 -0
  167. package/dist/utils/path.d.ts.map +1 -0
  168. package/dist/utils/path.js +132 -0
  169. package/dist/utils/path.js.map +1 -0
  170. package/dist/utils/path.test.d.ts +2 -0
  171. package/dist/utils/path.test.d.ts.map +1 -0
  172. package/dist/utils/path.test.js +191 -0
  173. package/dist/utils/path.test.js.map +1 -0
  174. package/package.json +28 -41
  175. package/LICENSE +0 -21
  176. package/dist/dag/graph.d.ts +0 -62
  177. package/dist/dag/graph.d.ts.map +0 -1
  178. package/dist/dag/graph.js +0 -244
  179. package/dist/dag/graph.js.map +0 -1
  180. package/dist/dag/index.d.ts +0 -4
  181. package/dist/dag/index.d.ts.map +0 -1
  182. package/dist/dag/index.js +0 -4
  183. package/dist/dag/index.js.map +0 -1
  184. package/dist/dag/propagation.d.ts +0 -58
  185. package/dist/dag/propagation.d.ts.map +0 -1
  186. package/dist/dag/propagation.js +0 -224
  187. package/dist/dag/propagation.js.map +0 -1
  188. package/dist/dag/topological.d.ts +0 -33
  189. package/dist/dag/topological.d.ts.map +0 -1
  190. package/dist/dag/topological.js +0 -173
  191. package/dist/dag/topological.js.map +0 -1
  192. package/dist/domain/define.d.ts +0 -82
  193. package/dist/domain/define.d.ts.map +0 -1
  194. package/dist/domain/define.js +0 -105
  195. package/dist/domain/define.js.map +0 -1
  196. package/dist/domain/index.d.ts +0 -4
  197. package/dist/domain/index.d.ts.map +0 -1
  198. package/dist/domain/index.js +0 -4
  199. package/dist/domain/index.js.map +0 -1
  200. package/dist/domain/types.d.ts +0 -203
  201. package/dist/domain/types.d.ts.map +0 -1
  202. package/dist/domain/types.js +0 -2
  203. package/dist/domain/types.js.map +0 -1
  204. package/dist/domain/validate.d.ts +0 -17
  205. package/dist/domain/validate.d.ts.map +0 -1
  206. package/dist/domain/validate.js +0 -204
  207. package/dist/domain/validate.js.map +0 -1
  208. package/dist/effect/index.d.ts +0 -4
  209. package/dist/effect/index.d.ts.map +0 -1
  210. package/dist/effect/index.js +0 -4
  211. package/dist/effect/index.js.map +0 -1
  212. package/dist/effect/result.d.ts +0 -100
  213. package/dist/effect/result.d.ts.map +0 -1
  214. package/dist/effect/result.js +0 -163
  215. package/dist/effect/result.js.map +0 -1
  216. package/dist/effect/runner.d.ts +0 -98
  217. package/dist/effect/runner.d.ts.map +0 -1
  218. package/dist/effect/runner.js +0 -321
  219. package/dist/effect/runner.js.map +0 -1
  220. package/dist/effect/types.d.ts +0 -169
  221. package/dist/effect/types.d.ts.map +0 -1
  222. package/dist/effect/types.js +0 -28
  223. package/dist/effect/types.js.map +0 -1
  224. package/dist/expression/analyzer.d.ts +0 -42
  225. package/dist/expression/analyzer.d.ts.map +0 -1
  226. package/dist/expression/analyzer.js +0 -166
  227. package/dist/expression/analyzer.js.map +0 -1
  228. package/dist/expression/evaluator.d.ts +0 -16
  229. package/dist/expression/evaluator.d.ts.map +0 -1
  230. package/dist/expression/evaluator.js +0 -382
  231. package/dist/expression/evaluator.js.map +0 -1
  232. package/dist/expression/index.d.ts +0 -5
  233. package/dist/expression/index.d.ts.map +0 -1
  234. package/dist/expression/index.js +0 -5
  235. package/dist/expression/index.js.map +0 -1
  236. package/dist/expression/parser.d.ts +0 -37
  237. package/dist/expression/parser.d.ts.map +0 -1
  238. package/dist/expression/parser.js +0 -201
  239. package/dist/expression/parser.js.map +0 -1
  240. package/dist/expression/types.d.ts +0 -123
  241. package/dist/expression/types.d.ts.map +0 -1
  242. package/dist/expression/types.js +0 -10
  243. package/dist/expression/types.js.map +0 -1
  244. package/dist/policy/field-policy.d.ts +0 -63
  245. package/dist/policy/field-policy.d.ts.map +0 -1
  246. package/dist/policy/field-policy.js +0 -138
  247. package/dist/policy/field-policy.js.map +0 -1
  248. package/dist/policy/index.d.ts +0 -3
  249. package/dist/policy/index.d.ts.map +0 -1
  250. package/dist/policy/index.js +0 -3
  251. package/dist/policy/index.js.map +0 -1
  252. package/dist/policy/precondition.d.ts +0 -58
  253. package/dist/policy/precondition.d.ts.map +0 -1
  254. package/dist/policy/precondition.js +0 -115
  255. package/dist/policy/precondition.js.map +0 -1
  256. package/dist/runtime/index.d.ts +0 -4
  257. package/dist/runtime/index.d.ts.map +0 -1
  258. package/dist/runtime/index.js +0 -4
  259. package/dist/runtime/index.js.map +0 -1
  260. package/dist/runtime/runtime.d.ts +0 -94
  261. package/dist/runtime/runtime.d.ts.map +0 -1
  262. package/dist/runtime/runtime.js +0 -294
  263. package/dist/runtime/runtime.js.map +0 -1
  264. package/dist/runtime/snapshot.d.ts +0 -39
  265. package/dist/runtime/snapshot.d.ts.map +0 -1
  266. package/dist/runtime/snapshot.js +0 -264
  267. package/dist/runtime/snapshot.js.map +0 -1
  268. package/dist/runtime/subscription.d.ts +0 -82
  269. package/dist/runtime/subscription.d.ts.map +0 -1
  270. package/dist/runtime/subscription.js +0 -222
  271. package/dist/runtime/subscription.js.map +0 -1
  272. package/dist/schema/integration.d.ts +0 -89
  273. package/dist/schema/integration.d.ts.map +0 -1
  274. package/dist/schema/integration.js +0 -171
  275. package/dist/schema/integration.js.map +0 -1
  276. package/dist/schema/validation.d.ts +0 -51
  277. package/dist/schema/validation.d.ts.map +0 -1
  278. package/dist/schema/validation.js +0 -212
  279. package/dist/schema/validation.js.map +0 -1
@@ -0,0 +1,633 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { compute } from "./compute.js";
3
+ import { createSnapshot, createIntent } from "../factories.js";
4
+ import { hashSchemaSync } from "../utils/hash.js";
5
+ const BASE_STATE_FIELDS = {
6
+ dummy: { type: "string", required: true },
7
+ count: { type: "number", required: true },
8
+ name: { type: "string", required: true },
9
+ balance: { type: "number", required: true },
10
+ a: { type: "number", required: true },
11
+ b: { type: "number", required: true },
12
+ loading: { type: "boolean", required: true },
13
+ started: { type: "boolean", required: true },
14
+ completed: { type: "boolean", required: true },
15
+ value: { type: "string", required: true },
16
+ todos: {
17
+ type: "array",
18
+ required: true,
19
+ items: {
20
+ type: "object",
21
+ required: true,
22
+ fields: {
23
+ completed: { type: "boolean", required: true },
24
+ },
25
+ },
26
+ },
27
+ fromBalance: { type: "number", required: true },
28
+ toBalance: { type: "number", required: true },
29
+ done: { type: "boolean", required: true },
30
+ };
31
+ const BASE_COMPUTED_FIELDS = {
32
+ "computed.dummy": {
33
+ expr: { kind: "get", path: "dummy" },
34
+ deps: ["dummy"],
35
+ },
36
+ };
37
+ const BASE_ACTIONS = {
38
+ noop: {
39
+ flow: { kind: "halt", reason: "noop" },
40
+ },
41
+ };
42
+ // Helper to create a minimal domain schema
43
+ function createTestSchema(overrides = {}) {
44
+ const { state, computed, actions: overrideActions, hash, types, ...restOverrides } = overrides;
45
+ const stateFields = {
46
+ ...BASE_STATE_FIELDS,
47
+ ...(state?.fields ?? {}),
48
+ };
49
+ const computedFields = {
50
+ ...BASE_COMPUTED_FIELDS,
51
+ ...(computed?.fields ?? {}),
52
+ };
53
+ const actions = {
54
+ ...BASE_ACTIONS,
55
+ ...(overrideActions ?? {}),
56
+ };
57
+ const schemaWithoutHash = {
58
+ id: "manifesto:test",
59
+ version: "1.0.0",
60
+ ...restOverrides,
61
+ types: types ?? {},
62
+ state: { fields: stateFields },
63
+ computed: { fields: computedFields },
64
+ actions,
65
+ };
66
+ return {
67
+ ...schemaWithoutHash,
68
+ hash: hash ?? hashSchemaSync(schemaWithoutHash),
69
+ };
70
+ }
71
+ const HOST_CONTEXT = { now: 0, randomSeed: "seed" };
72
+ let intentCounter = 0;
73
+ const nextIntentId = () => `intent-${intentCounter++}`;
74
+ const createTestIntent = (type, input) => input === undefined
75
+ ? createIntent(type, nextIntentId())
76
+ : createIntent(type, input, nextIntentId());
77
+ const createTestSnapshot = (data, schemaHash) => createSnapshot(data, schemaHash, HOST_CONTEXT);
78
+ const computeWithContext = (schema, snapshot, intent) => compute(schema, snapshot, intent);
79
+ describe("compute", () => {
80
+ describe("Basic Intent Processing", () => {
81
+ it("should process a simple action", async () => {
82
+ const schema = createTestSchema({
83
+ actions: {
84
+ increment: {
85
+ flow: {
86
+ kind: "patch",
87
+ op: "set",
88
+ path: "count",
89
+ value: {
90
+ kind: "add",
91
+ left: { kind: "coalesce", args: [{ kind: "get", path: "count" }, { kind: "lit", value: 0 }] },
92
+ right: { kind: "lit", value: 1 },
93
+ },
94
+ },
95
+ },
96
+ },
97
+ });
98
+ const snapshot = createTestSnapshot({ count: 0 }, schema.hash);
99
+ const intent = createTestIntent("increment");
100
+ const result = await computeWithContext(schema, snapshot, intent);
101
+ expect(result.status).toBe("complete");
102
+ expect(result.snapshot.data).toEqual({ count: 1 });
103
+ expect(result.snapshot.meta.version).toBe(1);
104
+ });
105
+ it("should handle unknown action", async () => {
106
+ const schema = createTestSchema();
107
+ const snapshot = createTestSnapshot({}, schema.hash);
108
+ const intent = createTestIntent("nonexistent");
109
+ const result = await computeWithContext(schema, snapshot, intent);
110
+ expect(result.status).toBe("error");
111
+ expect(result.snapshot.system.lastError?.code).toBe("UNKNOWN_ACTION");
112
+ });
113
+ it("should handle action with input", async () => {
114
+ const schema = createTestSchema({
115
+ actions: {
116
+ setName: {
117
+ flow: {
118
+ kind: "patch",
119
+ op: "set",
120
+ path: "name",
121
+ value: { kind: "get", path: "input.name" },
122
+ },
123
+ },
124
+ },
125
+ });
126
+ const snapshot = createTestSnapshot({}, schema.hash);
127
+ const intent = createTestIntent("setName", { name: "Alice" });
128
+ const result = await computeWithContext(schema, snapshot, intent);
129
+ expect(result.status).toBe("complete");
130
+ expect(result.snapshot.data).toEqual({ name: "Alice" });
131
+ });
132
+ it("should reject intent without intentId", async () => {
133
+ const schema = createTestSchema({
134
+ actions: {
135
+ increment: {
136
+ flow: {
137
+ kind: "patch",
138
+ op: "set",
139
+ path: "count",
140
+ value: { kind: "lit", value: 1 },
141
+ },
142
+ },
143
+ },
144
+ });
145
+ const snapshot = createTestSnapshot({ count: 0 }, schema.hash);
146
+ const intent = { type: "increment", input: undefined, intentId: "" };
147
+ const result = await computeWithContext(schema, snapshot, intent);
148
+ expect(result.status).toBe("error");
149
+ expect(result.snapshot.system.lastError?.code).toBe("INVALID_INPUT");
150
+ });
151
+ });
152
+ describe("Meta Access", () => {
153
+ it("should expose meta intentId without mutating input", async () => {
154
+ const schema = createTestSchema({
155
+ actions: {
156
+ markIntent: {
157
+ flow: {
158
+ kind: "patch",
159
+ op: "set",
160
+ path: "value",
161
+ value: { kind: "get", path: "meta.intentId" },
162
+ },
163
+ },
164
+ },
165
+ });
166
+ const snapshot = createTestSnapshot({}, schema.hash);
167
+ const intent = createTestIntent("markIntent", { name: "Alice" });
168
+ const result = await computeWithContext(schema, snapshot, intent);
169
+ expect(result.status).toBe("complete");
170
+ expect(result.snapshot.data).toEqual({ value: intent.intentId });
171
+ expect(result.snapshot.input).toEqual({ name: "Alice" });
172
+ });
173
+ });
174
+ describe("Availability Check", () => {
175
+ it("should check availability condition", async () => {
176
+ const schema = createTestSchema({
177
+ actions: {
178
+ withdraw: {
179
+ available: {
180
+ kind: "gt",
181
+ left: { kind: "get", path: "balance" },
182
+ right: { kind: "lit", value: 0 },
183
+ },
184
+ flow: {
185
+ kind: "patch",
186
+ op: "set",
187
+ path: "balance",
188
+ value: {
189
+ kind: "sub",
190
+ left: { kind: "get", path: "balance" },
191
+ right: { kind: "get", path: "input.amount" },
192
+ },
193
+ },
194
+ },
195
+ },
196
+ });
197
+ // Should succeed when balance > 0
198
+ const snapshot1 = createTestSnapshot({ balance: 100 }, schema.hash);
199
+ const intent1 = createTestIntent("withdraw", { amount: 50 });
200
+ const result1 = await computeWithContext(schema, snapshot1, intent1);
201
+ expect(result1.status).toBe("complete");
202
+ expect(result1.snapshot.data).toEqual({ balance: 50 });
203
+ // Should fail when balance = 0
204
+ const snapshot2 = createTestSnapshot({ balance: 0 }, schema.hash);
205
+ const intent2 = createTestIntent("withdraw", { amount: 50 });
206
+ const result2 = await computeWithContext(schema, snapshot2, intent2);
207
+ expect(result2.status).toBe("error");
208
+ expect(result2.snapshot.system.lastError?.code).toBe("ACTION_UNAVAILABLE");
209
+ });
210
+ it("should fail when availability does not return boolean", async () => {
211
+ const schema = createTestSchema({
212
+ actions: {
213
+ invalidAvailable: {
214
+ available: {
215
+ kind: "add",
216
+ left: { kind: "lit", value: 1 },
217
+ right: { kind: "lit", value: 2 },
218
+ },
219
+ flow: {
220
+ kind: "patch",
221
+ op: "set",
222
+ path: "count",
223
+ value: { kind: "lit", value: 1 },
224
+ },
225
+ },
226
+ },
227
+ });
228
+ const snapshot = createTestSnapshot({ count: 0 }, schema.hash);
229
+ const intent = createTestIntent("invalidAvailable");
230
+ const result = await computeWithContext(schema, snapshot, intent);
231
+ expect(result.status).toBe("error");
232
+ expect(result.snapshot.system.lastError?.code).toBe("TYPE_MISMATCH");
233
+ });
234
+ });
235
+ describe("Input Validation", () => {
236
+ it("should reject invalid input types", async () => {
237
+ const schema = createTestSchema({
238
+ actions: {
239
+ setCount: {
240
+ input: {
241
+ type: "object",
242
+ required: true,
243
+ fields: {
244
+ value: { type: "number", required: true },
245
+ },
246
+ },
247
+ flow: {
248
+ kind: "patch",
249
+ op: "set",
250
+ path: "count",
251
+ value: { kind: "get", path: "input.value" },
252
+ },
253
+ },
254
+ },
255
+ });
256
+ const snapshot = createTestSnapshot({ count: 0 }, schema.hash);
257
+ const intent = createTestIntent("setCount", { value: "not-a-number" });
258
+ const result = await computeWithContext(schema, snapshot, intent);
259
+ expect(result.status).toBe("error");
260
+ expect(result.snapshot.system.lastError?.code).toBe("INVALID_INPUT");
261
+ });
262
+ it("should reject missing required input fields", async () => {
263
+ const schema = createTestSchema({
264
+ actions: {
265
+ setCount: {
266
+ input: {
267
+ type: "object",
268
+ required: true,
269
+ fields: {
270
+ value: { type: "number", required: true },
271
+ },
272
+ },
273
+ flow: {
274
+ kind: "patch",
275
+ op: "set",
276
+ path: "count",
277
+ value: { kind: "get", path: "input.value" },
278
+ },
279
+ },
280
+ },
281
+ });
282
+ const snapshot = createTestSnapshot({ count: 0 }, schema.hash);
283
+ const intent = createTestIntent("setCount", {});
284
+ const result = await computeWithContext(schema, snapshot, intent);
285
+ expect(result.status).toBe("error");
286
+ expect(result.snapshot.system.lastError?.code).toBe("INVALID_INPUT");
287
+ });
288
+ it("should reject unknown input fields", async () => {
289
+ const schema = createTestSchema({
290
+ actions: {
291
+ setCount: {
292
+ input: {
293
+ type: "object",
294
+ required: true,
295
+ fields: {
296
+ value: { type: "number", required: true },
297
+ },
298
+ },
299
+ flow: {
300
+ kind: "patch",
301
+ op: "set",
302
+ path: "count",
303
+ value: { kind: "get", path: "input.value" },
304
+ },
305
+ },
306
+ },
307
+ });
308
+ const snapshot = createTestSnapshot({ count: 0 }, schema.hash);
309
+ const intent = createTestIntent("setCount", { value: 1, extra: 2 });
310
+ const result = await computeWithContext(schema, snapshot, intent);
311
+ expect(result.status).toBe("error");
312
+ expect(result.snapshot.system.lastError?.code).toBe("INVALID_INPUT");
313
+ });
314
+ });
315
+ describe("Computed Values", () => {
316
+ it("should recompute computed values after action", async () => {
317
+ const schema = createTestSchema({
318
+ computed: {
319
+ fields: {
320
+ "computed.total": {
321
+ expr: {
322
+ kind: "add",
323
+ left: { kind: "coalesce", args: [{ kind: "get", path: "a" }, { kind: "lit", value: 0 }] },
324
+ right: { kind: "coalesce", args: [{ kind: "get", path: "b" }, { kind: "lit", value: 0 }] },
325
+ },
326
+ deps: ["a", "b"],
327
+ },
328
+ },
329
+ },
330
+ actions: {
331
+ setA: {
332
+ flow: {
333
+ kind: "patch",
334
+ op: "set",
335
+ path: "a",
336
+ value: { kind: "get", path: "input.value" },
337
+ },
338
+ },
339
+ },
340
+ });
341
+ const snapshot = createTestSnapshot({ a: 10, b: 20 }, schema.hash);
342
+ const intent = createTestIntent("setA", { value: 100 });
343
+ const result = await computeWithContext(schema, snapshot, intent);
344
+ expect(result.status).toBe("complete");
345
+ expect(result.snapshot.data).toEqual({ a: 100, b: 20 });
346
+ expect(result.snapshot.computed["computed.total"]).toBe(120);
347
+ });
348
+ });
349
+ describe("Effects (Pending Status)", () => {
350
+ it("should return pending status when effect is encountered", async () => {
351
+ const schema = createTestSchema({
352
+ actions: {
353
+ fetchData: {
354
+ flow: {
355
+ kind: "seq",
356
+ steps: [
357
+ { kind: "patch", op: "set", path: "loading", value: { kind: "lit", value: true } },
358
+ {
359
+ kind: "effect",
360
+ type: "http",
361
+ params: {
362
+ url: { kind: "lit", value: "https://api.example.com/data" },
363
+ },
364
+ },
365
+ ],
366
+ },
367
+ },
368
+ },
369
+ });
370
+ const snapshot = createTestSnapshot({}, schema.hash);
371
+ const intent = createTestIntent("fetchData");
372
+ const result = await computeWithContext(schema, snapshot, intent);
373
+ expect(result.status).toBe("pending");
374
+ expect(result.snapshot.data).toEqual({ loading: true });
375
+ expect(result.snapshot.system.pendingRequirements).toHaveLength(1);
376
+ expect(result.snapshot.system.pendingRequirements[0].type).toBe("http");
377
+ });
378
+ });
379
+ describe("Halt", () => {
380
+ it("should return halted status when halt is encountered", async () => {
381
+ const schema = createTestSchema({
382
+ actions: {
383
+ conditionalHalt: {
384
+ flow: {
385
+ kind: "seq",
386
+ steps: [
387
+ { kind: "patch", op: "set", path: "started", value: { kind: "lit", value: true } },
388
+ {
389
+ kind: "if",
390
+ cond: { kind: "get", path: "input.shouldHalt" },
391
+ then: { kind: "halt", reason: "User requested halt" },
392
+ },
393
+ { kind: "patch", op: "set", path: "completed", value: { kind: "lit", value: true } },
394
+ ],
395
+ },
396
+ },
397
+ },
398
+ });
399
+ // With halt
400
+ const snapshot1 = createTestSnapshot({}, schema.hash);
401
+ const intent1 = createTestIntent("conditionalHalt", { shouldHalt: true });
402
+ const result1 = await computeWithContext(schema, snapshot1, intent1);
403
+ expect(result1.status).toBe("halted");
404
+ expect(result1.snapshot.data).toEqual({ started: true });
405
+ // Without halt
406
+ const snapshot2 = createTestSnapshot({}, schema.hash);
407
+ const intent2 = createTestIntent("conditionalHalt", { shouldHalt: false });
408
+ const result2 = await computeWithContext(schema, snapshot2, intent2);
409
+ expect(result2.status).toBe("complete");
410
+ expect(result2.snapshot.data).toEqual({ started: true, completed: true });
411
+ });
412
+ });
413
+ describe("Error Handling", () => {
414
+ it("should handle fail flow", async () => {
415
+ const schema = createTestSchema({
416
+ actions: {
417
+ validateInput: {
418
+ flow: {
419
+ kind: "if",
420
+ cond: { kind: "isNull", arg: { kind: "get", path: "input.value" } },
421
+ then: { kind: "fail", code: "MISSING_VALUE", message: { kind: "lit", value: "Value is required" } },
422
+ else: { kind: "patch", op: "set", path: "value", value: { kind: "get", path: "input.value" } },
423
+ },
424
+ },
425
+ },
426
+ });
427
+ // With null input
428
+ const snapshot1 = createTestSnapshot({}, schema.hash);
429
+ const intent1 = createTestIntent("validateInput", { value: null });
430
+ const result1 = await computeWithContext(schema, snapshot1, intent1);
431
+ expect(result1.status).toBe("error");
432
+ expect(result1.snapshot.system.lastError?.message).toBe("Value is required");
433
+ // With valid input
434
+ const snapshot2 = createTestSnapshot({}, schema.hash);
435
+ const intent2 = createTestIntent("validateInput", { value: "test" });
436
+ const result2 = await computeWithContext(schema, snapshot2, intent2);
437
+ expect(result2.status).toBe("complete");
438
+ expect(result2.snapshot.data).toEqual({ value: "test" });
439
+ });
440
+ });
441
+ describe("Trace Generation", () => {
442
+ it("should generate trace graph", async () => {
443
+ const schema = createTestSchema({
444
+ actions: {
445
+ simpleAction: {
446
+ flow: {
447
+ kind: "seq",
448
+ steps: [
449
+ { kind: "patch", op: "set", path: "a", value: { kind: "lit", value: 1 } },
450
+ { kind: "patch", op: "set", path: "b", value: { kind: "lit", value: 2 } },
451
+ ],
452
+ },
453
+ },
454
+ },
455
+ });
456
+ const snapshot = createTestSnapshot({}, schema.hash);
457
+ const intent = createTestIntent("simpleAction");
458
+ const result = await computeWithContext(schema, snapshot, intent);
459
+ expect(result.trace).toBeDefined();
460
+ expect(result.trace.intent).toEqual({ type: "simpleAction", input: undefined });
461
+ expect(result.trace.baseVersion).toBe(0);
462
+ expect(result.trace.resultVersion).toBe(1);
463
+ expect(result.trace.duration).toBeGreaterThanOrEqual(0);
464
+ expect(result.trace.terminatedBy).toBe("complete");
465
+ });
466
+ });
467
+ describe("Complex Scenarios", () => {
468
+ it("should handle a todo app workflow", async () => {
469
+ const schema = createTestSchema({
470
+ computed: {
471
+ fields: {
472
+ "computed.activeCount": {
473
+ expr: {
474
+ kind: "len",
475
+ arg: {
476
+ kind: "filter",
477
+ array: { kind: "coalesce", args: [{ kind: "get", path: "todos" }, { kind: "lit", value: [] }] },
478
+ predicate: { kind: "not", arg: { kind: "get", path: "$item.completed" } },
479
+ },
480
+ },
481
+ deps: ["todos"],
482
+ },
483
+ },
484
+ },
485
+ actions: {
486
+ addTodo: {
487
+ flow: {
488
+ kind: "patch",
489
+ op: "set",
490
+ path: "todos",
491
+ value: {
492
+ kind: "coalesce",
493
+ args: [
494
+ {
495
+ kind: "if",
496
+ cond: { kind: "isNull", arg: { kind: "get", path: "todos" } },
497
+ then: { kind: "lit", value: [] },
498
+ else: { kind: "get", path: "todos" },
499
+ },
500
+ { kind: "lit", value: [] },
501
+ ],
502
+ },
503
+ },
504
+ },
505
+ },
506
+ });
507
+ // First add - initialize todos array
508
+ const snapshot = createTestSnapshot({}, schema.hash);
509
+ const intent = createTestIntent("addTodo", { text: "Test todo" });
510
+ const result = await computeWithContext(schema, snapshot, intent);
511
+ expect(result.status).toBe("complete");
512
+ expect(result.snapshot.computed["computed.activeCount"]).toBe(0);
513
+ });
514
+ it("should handle sequential operations with state dependencies", async () => {
515
+ const schema = createTestSchema({
516
+ actions: {
517
+ transfer: {
518
+ flow: {
519
+ kind: "seq",
520
+ steps: [
521
+ {
522
+ kind: "patch",
523
+ op: "set",
524
+ path: "fromBalance",
525
+ value: {
526
+ kind: "sub",
527
+ left: { kind: "get", path: "fromBalance" },
528
+ right: { kind: "get", path: "input.amount" },
529
+ },
530
+ },
531
+ {
532
+ kind: "patch",
533
+ op: "set",
534
+ path: "toBalance",
535
+ value: {
536
+ kind: "add",
537
+ left: { kind: "get", path: "toBalance" },
538
+ right: { kind: "get", path: "input.amount" },
539
+ },
540
+ },
541
+ ],
542
+ },
543
+ },
544
+ },
545
+ });
546
+ const snapshot = createTestSnapshot({
547
+ fromBalance: 100,
548
+ toBalance: 50,
549
+ }, schema.hash);
550
+ const intent = createTestIntent("transfer", { amount: 30 });
551
+ const result = await computeWithContext(schema, snapshot, intent);
552
+ expect(result.status).toBe("complete");
553
+ expect(result.snapshot.data).toEqual({
554
+ fromBalance: 70,
555
+ toBalance: 80,
556
+ });
557
+ });
558
+ });
559
+ describe("Version Management", () => {
560
+ it("should increment version on each compute", async () => {
561
+ const schema = createTestSchema({
562
+ actions: {
563
+ noop: {
564
+ flow: { kind: "seq", steps: [] },
565
+ },
566
+ },
567
+ });
568
+ const snapshot = createTestSnapshot({}, schema.hash);
569
+ expect(snapshot.meta.version).toBe(0);
570
+ const result1 = await computeWithContext(schema, snapshot, createTestIntent("noop"));
571
+ expect(result1.snapshot.meta.version).toBe(1);
572
+ const result2 = await computeWithContext(schema, result1.snapshot, createTestIntent("noop"));
573
+ expect(result2.snapshot.meta.version).toBe(2);
574
+ });
575
+ });
576
+ describe("System State", () => {
577
+ it("should track errors in system.errors", async () => {
578
+ const schema = createTestSchema({
579
+ actions: {
580
+ fail: {
581
+ flow: { kind: "fail", code: "TEST_ERROR" },
582
+ },
583
+ },
584
+ });
585
+ const snapshot = createTestSnapshot({}, schema.hash);
586
+ const result1 = await computeWithContext(schema, snapshot, createTestIntent("fail"));
587
+ expect(result1.snapshot.system.errors).toHaveLength(1);
588
+ expect(result1.snapshot.system.lastError?.code).toBe("VALIDATION_ERROR");
589
+ // Run again to accumulate errors
590
+ const result2 = await computeWithContext(schema, result1.snapshot, createTestIntent("fail"));
591
+ expect(result2.snapshot.system.errors).toHaveLength(2);
592
+ });
593
+ it("should reset currentAction after completion", async () => {
594
+ const schema = createTestSchema({
595
+ actions: {
596
+ test: {
597
+ flow: { kind: "patch", op: "set", path: "done", value: { kind: "lit", value: true } },
598
+ },
599
+ },
600
+ });
601
+ const snapshot = createTestSnapshot({}, schema.hash);
602
+ const result = await computeWithContext(schema, snapshot, createTestIntent("test"));
603
+ expect(result.snapshot.system.currentAction).toBeNull();
604
+ expect(result.snapshot.system.status).toBe("idle");
605
+ });
606
+ });
607
+ describe("Determinism", () => {
608
+ it("should produce identical results for same inputs", async () => {
609
+ const schema = createTestSchema({
610
+ actions: {
611
+ increment: {
612
+ flow: {
613
+ kind: "patch",
614
+ op: "set",
615
+ path: "count",
616
+ value: {
617
+ kind: "add",
618
+ left: { kind: "get", path: "count" },
619
+ right: { kind: "lit", value: 1 },
620
+ },
621
+ },
622
+ },
623
+ },
624
+ });
625
+ const snapshot = createTestSnapshot({ count: 1 }, schema.hash);
626
+ const intent = createIntent("increment", "intent-fixed");
627
+ const result1 = await compute(schema, snapshot, intent);
628
+ const result2 = await compute(schema, snapshot, intent);
629
+ expect(result1).toEqual(result2);
630
+ });
631
+ });
632
+ });
633
+ //# sourceMappingURL=compute.test.js.map