@spaceteams/weft 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.mts +488 -0
- package/dist/index.mjs +956 -0
- package/package.json +33 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
//#region src/draft/index.ts
|
|
3
|
+
function createDraft(draftId, base, overlay) {
|
|
4
|
+
return {
|
|
5
|
+
draftId,
|
|
6
|
+
base,
|
|
7
|
+
overlay
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function isEmptyDraft(draft) {
|
|
11
|
+
return Object.keys(draft.overlay).length === 0;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/evaluate/index.ts
|
|
15
|
+
const defaultEvaluateMode = "strict";
|
|
16
|
+
function evaluate(model, provided, mode = defaultEvaluateMode) {
|
|
17
|
+
const values = /* @__PURE__ */ new Map();
|
|
18
|
+
const missing = /* @__PURE__ */ new Map();
|
|
19
|
+
const trace = [];
|
|
20
|
+
for (const input of model.inputs) {
|
|
21
|
+
if (!(input.key.id in provided)) if (mode === "strict") throw new Error(`Missing input: ${input.key.id}`);
|
|
22
|
+
else {
|
|
23
|
+
missing.set(input.key.id, {
|
|
24
|
+
kind: "missing-input",
|
|
25
|
+
key: input.key.id
|
|
26
|
+
});
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
values.set(input.key.id, provided[input.key.id]);
|
|
30
|
+
}
|
|
31
|
+
for (const target of model.orderedRuleTargets) {
|
|
32
|
+
const rule = model.ruleByTarget.get(target);
|
|
33
|
+
if (!rule) throw new Error(`Missing compiled rule: ${target}`);
|
|
34
|
+
const inputs = {};
|
|
35
|
+
const missingDeps = [];
|
|
36
|
+
for (const dep of rule.deps) {
|
|
37
|
+
if (!values.has(dep.id)) if (mode === "strict") throw new Error(`Missing value: ${dep.id}`);
|
|
38
|
+
else {
|
|
39
|
+
missingDeps.push(dep);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
inputs[dep.id] = values.get(dep.id);
|
|
43
|
+
}
|
|
44
|
+
if (missingDeps.length > 0) {
|
|
45
|
+
missing.set(target, {
|
|
46
|
+
kind: "rule-not-run",
|
|
47
|
+
key: target,
|
|
48
|
+
because: missingDeps.map((d) => d.id)
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const get = (key) => {
|
|
53
|
+
if (!values.has(key.id)) throw new Error(`Invariant violation: missing dependency reached rule eval: ${key.id}`);
|
|
54
|
+
return values.get(key.id);
|
|
55
|
+
};
|
|
56
|
+
const { output, detail } = rule.eval(get);
|
|
57
|
+
values.set(target, output);
|
|
58
|
+
const deps = model.depsByTarget.get(target) ?? [];
|
|
59
|
+
const ruleMeta = model.ruleMeta.get(target);
|
|
60
|
+
const keyMeta = model.keyMeta.get(target);
|
|
61
|
+
trace.push({
|
|
62
|
+
target,
|
|
63
|
+
deps,
|
|
64
|
+
ruleSpec: rule.spec,
|
|
65
|
+
ruleMeta,
|
|
66
|
+
keyMeta,
|
|
67
|
+
detail: detail ?? {},
|
|
68
|
+
inputs,
|
|
69
|
+
output
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
values,
|
|
74
|
+
missing,
|
|
75
|
+
order: model.orderedRuleTargets,
|
|
76
|
+
trace
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/facts.ts
|
|
81
|
+
function mapToFactBag(map) {
|
|
82
|
+
return Object.fromEntries([...map.entries()].sort(([a], [b]) => a.localeCompare(b)));
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/model/snapshot-model.ts
|
|
86
|
+
function snapshotModel(model) {
|
|
87
|
+
return {
|
|
88
|
+
inputKeys: [...model.inputKeys],
|
|
89
|
+
rules: model.rules.map((r) => ({
|
|
90
|
+
target: r.target.id,
|
|
91
|
+
spec: r.spec
|
|
92
|
+
}))
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/overlay/diff-group.ts
|
|
97
|
+
function groupDiffByOrigin(result, deltas) {
|
|
98
|
+
const overlayInputs = [];
|
|
99
|
+
const derivedValues = [];
|
|
100
|
+
const other = [];
|
|
101
|
+
for (const delta of deltas) {
|
|
102
|
+
const origin = result.origins.get(delta.key);
|
|
103
|
+
if (origin?.kind === "overlay") overlayInputs.push(delta);
|
|
104
|
+
else if (origin?.kind === "derived") derivedValues.push(delta);
|
|
105
|
+
else other.push(delta);
|
|
106
|
+
}
|
|
107
|
+
const groups = [];
|
|
108
|
+
if (overlayInputs.length) groups.push({
|
|
109
|
+
label: "Overlay inputs",
|
|
110
|
+
deltas: overlayInputs
|
|
111
|
+
});
|
|
112
|
+
if (derivedValues.length) groups.push({
|
|
113
|
+
label: "Derived values",
|
|
114
|
+
deltas: derivedValues
|
|
115
|
+
});
|
|
116
|
+
if (other.length) groups.push({
|
|
117
|
+
label: "Other",
|
|
118
|
+
deltas: other
|
|
119
|
+
});
|
|
120
|
+
return groups;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/overlay/explain-diff.ts
|
|
124
|
+
function explainDiffs(result, deltas) {
|
|
125
|
+
const changedKeys = new Set(deltas.map((d) => d.key));
|
|
126
|
+
const traceByTarget = new Map(result.trace.map((step) => [step.target, step]));
|
|
127
|
+
return deltas.map((delta) => {
|
|
128
|
+
const step = traceByTarget.get(delta.key);
|
|
129
|
+
if (!step) return { delta };
|
|
130
|
+
return {
|
|
131
|
+
delta,
|
|
132
|
+
dependencies: step.deps.map((depKey) => ({
|
|
133
|
+
key: depKey,
|
|
134
|
+
changed: changedKeys.has(depKey)
|
|
135
|
+
}))
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/snapshot/canonicalize.ts
|
|
141
|
+
function canonicalize(value) {
|
|
142
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
143
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
144
|
+
if (typeof value === "object" && value !== null) {
|
|
145
|
+
if (value instanceof Date) return value.toISOString();
|
|
146
|
+
const sortedKeys = Object.keys(value).sort();
|
|
147
|
+
const obj = value;
|
|
148
|
+
const out = {};
|
|
149
|
+
for (const key of sortedKeys) out[key] = canonicalize(obj[key]);
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Unsupported value for canonicalization: ${JSON.stringify(value)}`);
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/snapshot/canonicalizeValue.ts
|
|
156
|
+
function canonicalizeValue(model, key, value) {
|
|
157
|
+
const semantics = model.semantics.get(key);
|
|
158
|
+
if (semantics?.encode) return semantics.encode(value);
|
|
159
|
+
else return canonicalize(value);
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/snapshot/canonicalizeDelta.ts
|
|
163
|
+
function canonicalizeDelta(model, delta) {
|
|
164
|
+
switch (delta.kind) {
|
|
165
|
+
case "added": return {
|
|
166
|
+
key: delta.key,
|
|
167
|
+
kind: "added",
|
|
168
|
+
after: canonicalizeValue(model, delta.key, delta.after)
|
|
169
|
+
};
|
|
170
|
+
case "removed": return {
|
|
171
|
+
key: delta.key,
|
|
172
|
+
kind: "removed",
|
|
173
|
+
before: canonicalizeValue(model, delta.key, delta.before)
|
|
174
|
+
};
|
|
175
|
+
case "changed": return {
|
|
176
|
+
key: delta.key,
|
|
177
|
+
kind: "changed",
|
|
178
|
+
before: canonicalizeValue(model, delta.key, delta.before),
|
|
179
|
+
after: canonicalizeValue(model, delta.key, delta.after)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/snapshot/canonicalizeFacts.ts
|
|
185
|
+
function canonicalizeFacts(model, facts) {
|
|
186
|
+
const result = {};
|
|
187
|
+
for (const [key, value] of Object.entries(facts)) result[key] = canonicalizeValue(model, key, value);
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/snapshot/fingerprint.ts
|
|
192
|
+
function fingerprintCanonical(value) {
|
|
193
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
194
|
+
}
|
|
195
|
+
function fingerprintValue(value) {
|
|
196
|
+
return fingerprintCanonical(canonicalize(value));
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/model/model.ts
|
|
200
|
+
function getDependencies(model, key) {
|
|
201
|
+
return model.depsByTarget.get(key.id) ?? [];
|
|
202
|
+
}
|
|
203
|
+
function getDependents(model, key) {
|
|
204
|
+
return model.dependentsByKey.get(key.id) ?? [];
|
|
205
|
+
}
|
|
206
|
+
function getDeclaredKeys(model) {
|
|
207
|
+
return [...model.inputKeys, ...model.orderedRuleTargets];
|
|
208
|
+
}
|
|
209
|
+
function upstreamOf(model, key) {
|
|
210
|
+
const visited = /* @__PURE__ */ new Set();
|
|
211
|
+
const stack = [...model.depsByTarget.get(key.id) ?? []];
|
|
212
|
+
while (stack.length > 0) {
|
|
213
|
+
const current = stack.pop();
|
|
214
|
+
if (visited.has(current)) continue;
|
|
215
|
+
visited.add(current);
|
|
216
|
+
const deps = model.depsByTarget.get(current);
|
|
217
|
+
if (deps) {
|
|
218
|
+
for (const dep of deps) if (!visited.has(dep)) stack.push(dep);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return sortKeysByModelOrder(model, visited);
|
|
222
|
+
}
|
|
223
|
+
function downstreamOf(model, key) {
|
|
224
|
+
const visited = /* @__PURE__ */ new Set();
|
|
225
|
+
const stack = [...model.dependentsByKey.get(key.id) ?? []];
|
|
226
|
+
while (stack.length > 0) {
|
|
227
|
+
const current = stack.pop();
|
|
228
|
+
if (visited.has(current)) continue;
|
|
229
|
+
visited.add(current);
|
|
230
|
+
const dependents = model.dependentsByKey.get(current);
|
|
231
|
+
if (dependents) {
|
|
232
|
+
for (const dependent of dependents) if (!visited.has(dependent)) stack.push(dependent);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return sortKeysByModelOrder(model, visited);
|
|
236
|
+
}
|
|
237
|
+
function sortKeysByModelOrder(model, keys) {
|
|
238
|
+
const keySet = new Set(keys);
|
|
239
|
+
return [...model.inputKeys.filter((k) => keySet.has(k)), ...model.orderedRuleTargets.filter((k) => keySet.has(k))];
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/overlay/diff-results.ts
|
|
243
|
+
function diffResults(model, before, after) {
|
|
244
|
+
const deltas = [];
|
|
245
|
+
for (const key of getDeclaredKeys(model)) {
|
|
246
|
+
const hasBefore = before.values.has(key);
|
|
247
|
+
const hasAfter = after.values.has(key);
|
|
248
|
+
if (!hasBefore && !hasAfter) continue;
|
|
249
|
+
if (hasBefore !== hasAfter) {
|
|
250
|
+
deltas.push(hasBefore ? {
|
|
251
|
+
key,
|
|
252
|
+
kind: "removed",
|
|
253
|
+
before: before.values.get(key)
|
|
254
|
+
} : {
|
|
255
|
+
key,
|
|
256
|
+
kind: "added",
|
|
257
|
+
after: after.values.get(key)
|
|
258
|
+
});
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const beforeValue = before.values.get(key);
|
|
262
|
+
const afterValue = after.values.get(key);
|
|
263
|
+
if (!(model.semantics.get(key)?.eq ?? Object.is)(beforeValue, afterValue)) deltas.push({
|
|
264
|
+
key,
|
|
265
|
+
kind: "changed",
|
|
266
|
+
before: beforeValue,
|
|
267
|
+
after: afterValue
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return { deltas };
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/overlay/apply-overlay.ts
|
|
274
|
+
function applyOverlay(base, overlay) {
|
|
275
|
+
return {
|
|
276
|
+
base,
|
|
277
|
+
overlay,
|
|
278
|
+
effective: {
|
|
279
|
+
...base,
|
|
280
|
+
...overlay
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/overlay/evaluate-overlay.ts
|
|
286
|
+
function evaluateOverlay(model, base, overlay, mode = defaultEvaluateMode) {
|
|
287
|
+
const facts = applyOverlay(base, overlay);
|
|
288
|
+
const evaluated = evaluate(model, facts.effective, mode);
|
|
289
|
+
const origins = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const input of model.inputs) {
|
|
291
|
+
const key = input.key.id;
|
|
292
|
+
if (key in overlay) origins.set(key, { kind: "overlay" });
|
|
293
|
+
else if (key in base) origins.set(key, { kind: "base" });
|
|
294
|
+
}
|
|
295
|
+
for (const rule of model.rules) origins.set(rule.target.id, { kind: "derived" });
|
|
296
|
+
return {
|
|
297
|
+
...evaluated,
|
|
298
|
+
overlayedFacts: facts,
|
|
299
|
+
origins
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/draft/evaluate-draft.ts
|
|
304
|
+
function evaluateDraft(model, draft, mode = defaultEvaluateMode) {
|
|
305
|
+
const baseResult = evaluate(model, draft.base, mode);
|
|
306
|
+
const result = evaluateOverlay(model, draft.base, draft.overlay, mode);
|
|
307
|
+
const { deltas } = diffResults(model, baseResult, result);
|
|
308
|
+
return {
|
|
309
|
+
draft,
|
|
310
|
+
result,
|
|
311
|
+
deltas
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/draft/analyze-draft.ts
|
|
316
|
+
function analyzeDraft(model, draft, mode = defaultEvaluateMode) {
|
|
317
|
+
const normalized = normalizeDraft(model, draft);
|
|
318
|
+
const evaluated = evaluateDraft(model, normalized.draft, mode);
|
|
319
|
+
const impact = analyzeImpact(model, evaluated);
|
|
320
|
+
return {
|
|
321
|
+
evaluated,
|
|
322
|
+
groupedDiffs: groupDiffByOrigin(evaluated.result, evaluated.deltas),
|
|
323
|
+
changes: explainDiffs(evaluated.result, evaluated.deltas),
|
|
324
|
+
impact,
|
|
325
|
+
normalizationIssues: normalized.issues
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function normalizeDraft(model, draft) {
|
|
329
|
+
const normalized = {
|
|
330
|
+
draftId: draft.draftId,
|
|
331
|
+
base: draft.base,
|
|
332
|
+
overlay: {},
|
|
333
|
+
meta: draft.meta
|
|
334
|
+
};
|
|
335
|
+
const issues = [];
|
|
336
|
+
for (const key of Object.keys(draft.overlay)) {
|
|
337
|
+
if (!model.inputKeys.includes(key)) {
|
|
338
|
+
issues.push({
|
|
339
|
+
level: "warning",
|
|
340
|
+
key,
|
|
341
|
+
message: `Key "${key}" is not an input key and will be ignored`
|
|
342
|
+
});
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const base = draft.base[key];
|
|
346
|
+
let overlay = draft.overlay[key];
|
|
347
|
+
const semantics = model.semantics.get(key);
|
|
348
|
+
const normalize = semantics?.normalize;
|
|
349
|
+
if (normalize) overlay = normalize(overlay);
|
|
350
|
+
if ((semantics?.eq ?? Object.is)(base, overlay)) continue;
|
|
351
|
+
normalized.overlay[key] = overlay;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
draft: normalized,
|
|
355
|
+
issues
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function analyzeImpact(model, evaluated) {
|
|
359
|
+
const changed = new Set(evaluated.deltas.map((d) => d.key));
|
|
360
|
+
const direct = [];
|
|
361
|
+
const affected = [];
|
|
362
|
+
const terminal = [];
|
|
363
|
+
for (const key of changed) {
|
|
364
|
+
const origin = evaluated.result.origins.get(key);
|
|
365
|
+
if (origin?.kind === "overlay") direct.push(key);
|
|
366
|
+
else if (origin?.kind === "derived") affected.push(key);
|
|
367
|
+
}
|
|
368
|
+
for (const key of changed) if (!model.dependentsByKey.get(key)?.some((d) => changed.has(d))) terminal.push(key);
|
|
369
|
+
return {
|
|
370
|
+
direct,
|
|
371
|
+
affected,
|
|
372
|
+
terminal
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function freezeDraftAnalysis(model, analysis) {
|
|
376
|
+
const modelShape = snapshotModel(model);
|
|
377
|
+
const draft = analysis.evaluated.draft;
|
|
378
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
379
|
+
const snapshot = {
|
|
380
|
+
modelFingerprint: fingerprintValue(modelShape),
|
|
381
|
+
baseFingerprint: fingerprintValue(draft.base),
|
|
382
|
+
overlayFingerprint: fingerprintValue(draft.overlay),
|
|
383
|
+
analysisFingerprint: fingerprintValue({
|
|
384
|
+
model: modelShape,
|
|
385
|
+
base: draft.base,
|
|
386
|
+
overlay: draft.overlay
|
|
387
|
+
}),
|
|
388
|
+
createdAt: now
|
|
389
|
+
};
|
|
390
|
+
return {
|
|
391
|
+
draftId: draft.draftId,
|
|
392
|
+
snapshot,
|
|
393
|
+
base: canonicalizeFacts(model, draft.base),
|
|
394
|
+
overlay: canonicalizeFacts(model, draft.overlay),
|
|
395
|
+
effective: canonicalizeFacts(model, analysis.evaluated.result.overlayedFacts.effective),
|
|
396
|
+
values: canonicalizeFacts(model, mapToFactBag(analysis.evaluated.result.values)),
|
|
397
|
+
deltas: analysis.evaluated.deltas.map((delta) => canonicalizeDelta(model, delta)),
|
|
398
|
+
groupedDiffs: analysis.groupedDiffs.map((group) => ({
|
|
399
|
+
label: group.label,
|
|
400
|
+
deltas: group.deltas.map((delta) => canonicalizeDelta(model, delta))
|
|
401
|
+
})),
|
|
402
|
+
changes: analysis.changes.map((change) => ({
|
|
403
|
+
delta: canonicalizeDelta(model, change.delta),
|
|
404
|
+
dependencies: change.dependencies
|
|
405
|
+
})),
|
|
406
|
+
impact: analysis.impact,
|
|
407
|
+
normalizationIssues: analysis.normalizationIssues,
|
|
408
|
+
frozenAt: now
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/inspect/inspect-diff-target.ts
|
|
413
|
+
function inspectDiffTarget(model, result, changes, target) {
|
|
414
|
+
const changeByKey = new Map(changes.map((change) => [change.delta.key, change]));
|
|
415
|
+
const stepByTarget = new Map(result.trace.map((step) => [step.target, step]));
|
|
416
|
+
function build(key, parentStep) {
|
|
417
|
+
const step = stepByTarget.get(key);
|
|
418
|
+
const change = changeByKey.get(key);
|
|
419
|
+
if (!step) {
|
|
420
|
+
const keyMeta = model.keyMeta.get(key);
|
|
421
|
+
return {
|
|
422
|
+
key,
|
|
423
|
+
kind: "input",
|
|
424
|
+
meta: { key: keyMeta },
|
|
425
|
+
execution: { value: parentStep?.inputs[key] },
|
|
426
|
+
change,
|
|
427
|
+
label: keyMeta?.label ?? key,
|
|
428
|
+
children: []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
key,
|
|
433
|
+
kind: step?.ruleSpec?.op ?? "rule",
|
|
434
|
+
meta: {
|
|
435
|
+
key: step?.keyMeta,
|
|
436
|
+
rule: step?.ruleMeta
|
|
437
|
+
},
|
|
438
|
+
structure: { ruleSpec: step?.ruleSpec },
|
|
439
|
+
execution: {
|
|
440
|
+
value: step?.output,
|
|
441
|
+
trace: step
|
|
442
|
+
},
|
|
443
|
+
change,
|
|
444
|
+
label: step?.keyMeta?.label ?? key,
|
|
445
|
+
children: step?.deps.map((dep) => build(dep, step)) ?? []
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return build(target);
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/inspect/inspect-model-target.ts
|
|
452
|
+
function inspectModelTarget(model, target) {
|
|
453
|
+
function build(key) {
|
|
454
|
+
const rule = model.ruleByTarget.get(key);
|
|
455
|
+
const keyMeta = model.keyMeta.get(key);
|
|
456
|
+
const ruleMeta = model.ruleMeta.get(key);
|
|
457
|
+
return {
|
|
458
|
+
key,
|
|
459
|
+
kind: rule ? rule?.spec?.op ?? "rule" : "input",
|
|
460
|
+
meta: {
|
|
461
|
+
key: keyMeta,
|
|
462
|
+
rule: ruleMeta
|
|
463
|
+
},
|
|
464
|
+
structure: { ruleSpec: rule?.spec },
|
|
465
|
+
label: keyMeta?.label ?? key,
|
|
466
|
+
children: rule?.deps.map((dep) => build(dep.id)) ?? []
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return build(target);
|
|
470
|
+
}
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/inspect/inspect-trace-target.ts
|
|
473
|
+
function inspectTraceTarget(model, trace, target) {
|
|
474
|
+
const stepByTarget = new Map(trace.map((s) => [s.target, s]));
|
|
475
|
+
function build(key, parentStep) {
|
|
476
|
+
const step = stepByTarget.get(key);
|
|
477
|
+
if (!step) {
|
|
478
|
+
const keyMeta = model.keyMeta.get(key);
|
|
479
|
+
return {
|
|
480
|
+
key,
|
|
481
|
+
kind: "input",
|
|
482
|
+
meta: { key: keyMeta },
|
|
483
|
+
execution: { value: parentStep?.inputs[key] },
|
|
484
|
+
label: keyMeta?.label ?? key,
|
|
485
|
+
children: []
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
key,
|
|
490
|
+
kind: step?.ruleSpec?.op ?? "rule",
|
|
491
|
+
meta: {
|
|
492
|
+
key: step?.keyMeta,
|
|
493
|
+
rule: step?.ruleMeta
|
|
494
|
+
},
|
|
495
|
+
structure: { ruleSpec: step?.ruleSpec },
|
|
496
|
+
execution: {
|
|
497
|
+
value: step?.output,
|
|
498
|
+
trace: step
|
|
499
|
+
},
|
|
500
|
+
label: step?.keyMeta?.label ?? key,
|
|
501
|
+
children: step?.deps.map((dep) => build(dep, step)) ?? []
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return build(target);
|
|
505
|
+
}
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/inspect/inspection-node-to-ascii.ts
|
|
508
|
+
const formatLabel = (node, { showMeta, showChange }) => {
|
|
509
|
+
let label = node.meta?.rule?.label ?? node.label;
|
|
510
|
+
if (showMeta) label += ` [${node.kind}]`;
|
|
511
|
+
if (node.change && showChange) {
|
|
512
|
+
const delta = node.change.delta;
|
|
513
|
+
switch (delta.kind) {
|
|
514
|
+
case "added":
|
|
515
|
+
label += ` = ${String(delta.after)} (addded)`;
|
|
516
|
+
break;
|
|
517
|
+
case "changed":
|
|
518
|
+
label += ` = ${String(delta.before)} -> ${delta.after} (changed)`;
|
|
519
|
+
break;
|
|
520
|
+
case "removed":
|
|
521
|
+
label += ` = ${String(delta.before)} (removed)`;
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
} else if (node.execution?.value !== void 0) label += ` = ${String(node.execution?.value)}`;
|
|
525
|
+
const detail = node.execution?.trace?.detail;
|
|
526
|
+
if (detail) switch (detail.op) {
|
|
527
|
+
case "decision": {
|
|
528
|
+
const tableDetail = detail;
|
|
529
|
+
label += tableDetail.usedDefault ? " :: default" : ` :: ${tableDetail.matchedRowLabel ?? tableDetail.matchedRowId}`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return label;
|
|
533
|
+
};
|
|
534
|
+
const inspectionNodeToAscii = (root, options) => {
|
|
535
|
+
const toAscii = (node, prefix = "", isLast = true) => {
|
|
536
|
+
const lines = [`${prefix}${isLast ? "└── " : "├── "}${formatLabel(node, options)}`];
|
|
537
|
+
const childPrefix = prefix + (isLast ? " " : "│ ");
|
|
538
|
+
const children = node.children.map((dep, index) => toAscii(dep, childPrefix, index === node.children.length - 1));
|
|
539
|
+
return [...lines, ...children].join("\n");
|
|
540
|
+
};
|
|
541
|
+
return toAscii(root);
|
|
542
|
+
};
|
|
543
|
+
//#endregion
|
|
544
|
+
//#region src/key.ts
|
|
545
|
+
function key(id) {
|
|
546
|
+
return {
|
|
547
|
+
__kind: "key",
|
|
548
|
+
id
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/model/compile-model.ts
|
|
553
|
+
function compileModel(model) {
|
|
554
|
+
const issues = [];
|
|
555
|
+
const inputKeys = /* @__PURE__ */ new Set();
|
|
556
|
+
for (const input of model.inputs) {
|
|
557
|
+
const id = input.key.id;
|
|
558
|
+
if (inputKeys.has(id)) issues.push({
|
|
559
|
+
level: "error",
|
|
560
|
+
code: "DUPLICATE_INPUT",
|
|
561
|
+
message: `Input "${id}" is declared more than once.`,
|
|
562
|
+
keys: [id]
|
|
563
|
+
});
|
|
564
|
+
inputKeys.add(id);
|
|
565
|
+
}
|
|
566
|
+
const ruleByTarget = /* @__PURE__ */ new Map();
|
|
567
|
+
for (const rule of model.rules) {
|
|
568
|
+
const targetId = rule.target.id;
|
|
569
|
+
if (ruleByTarget.has(targetId)) {
|
|
570
|
+
issues.push({
|
|
571
|
+
level: "error",
|
|
572
|
+
code: "DUPLICATE_RULE_TARGET",
|
|
573
|
+
message: `Rule target "${targetId}" is declared more than once.`,
|
|
574
|
+
keys: [targetId]
|
|
575
|
+
});
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
ruleByTarget.set(targetId, rule);
|
|
579
|
+
}
|
|
580
|
+
for (const inputId of inputKeys) if (ruleByTarget.has(inputId)) issues.push({
|
|
581
|
+
level: "error",
|
|
582
|
+
code: "INPUT_RULE_CONFLICT",
|
|
583
|
+
message: `Key "${inputId}" is declared as both input and rule target.`,
|
|
584
|
+
keys: [inputId]
|
|
585
|
+
});
|
|
586
|
+
const knownKeys = new Set([...inputKeys, ...ruleByTarget.keys()]);
|
|
587
|
+
const depsByTarget = /* @__PURE__ */ new Map();
|
|
588
|
+
const dependentsByKeyMutable = /* @__PURE__ */ new Map();
|
|
589
|
+
for (const rule of model.rules) {
|
|
590
|
+
const targetId = rule.target.id;
|
|
591
|
+
const depIds = rule.deps.map((dep) => dep.id);
|
|
592
|
+
depsByTarget.set(targetId, depIds);
|
|
593
|
+
for (const depId of depIds) {
|
|
594
|
+
if (!knownKeys.has(depId)) {
|
|
595
|
+
issues.push({
|
|
596
|
+
level: "error",
|
|
597
|
+
code: "MISSING_DEPENDENCY",
|
|
598
|
+
message: `Rule "${targetId}" depends on unkown key "${depId}".`,
|
|
599
|
+
keys: [targetId, depId]
|
|
600
|
+
});
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const dependents = dependentsByKeyMutable.get(depId);
|
|
604
|
+
if (dependents) dependents.push(targetId);
|
|
605
|
+
else dependentsByKeyMutable.set(depId, [targetId]);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (issues.some((issue) => issue.level === "error")) return {
|
|
609
|
+
ok: false,
|
|
610
|
+
issues
|
|
611
|
+
};
|
|
612
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
613
|
+
for (const targetId of ruleByTarget.keys()) inDegree.set(targetId, 0);
|
|
614
|
+
for (const [targetId, depIds] of depsByTarget) {
|
|
615
|
+
let count = 0;
|
|
616
|
+
for (const depId of depIds) if (ruleByTarget.has(depId)) count += 1;
|
|
617
|
+
inDegree.set(targetId, count);
|
|
618
|
+
}
|
|
619
|
+
const queue = [];
|
|
620
|
+
for (const [targetId, degree] of inDegree) if (degree === 0) queue.push(targetId);
|
|
621
|
+
const orderedRuleTargets = [];
|
|
622
|
+
while (queue.length > 0) {
|
|
623
|
+
const current = queue.shift();
|
|
624
|
+
orderedRuleTargets.push(current);
|
|
625
|
+
const dependents = dependentsByKeyMutable.get(current) ?? [];
|
|
626
|
+
for (const dependentTarget of dependents) {
|
|
627
|
+
if (!inDegree.has(dependentTarget)) continue;
|
|
628
|
+
const nextDegree = inDegree.get(dependentTarget) - 1;
|
|
629
|
+
inDegree.set(dependentTarget, nextDegree);
|
|
630
|
+
if (nextDegree === 0) queue.push(dependentTarget);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (orderedRuleTargets.length !== ruleByTarget.size) {
|
|
634
|
+
const unresolved = [...ruleByTarget.keys()].filter((targetId) => !orderedRuleTargets.includes(targetId));
|
|
635
|
+
issues.push({
|
|
636
|
+
level: "error",
|
|
637
|
+
code: "CYCLE",
|
|
638
|
+
message: `Cycle detected among rule targets "${unresolved.join(", ")}"`,
|
|
639
|
+
keys: unresolved
|
|
640
|
+
});
|
|
641
|
+
return {
|
|
642
|
+
ok: false,
|
|
643
|
+
issues
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
const dependentsByKey = new Map([...dependentsByKeyMutable.entries()].map(([k, v]) => [k, [...v]]));
|
|
647
|
+
const declaredKeys = [...model.inputs.map((i) => [i.key.id, i.key]), ...model.rules.map((r) => [r.target.id, r.target])];
|
|
648
|
+
return {
|
|
649
|
+
ok: true,
|
|
650
|
+
issues,
|
|
651
|
+
model: {
|
|
652
|
+
keys: new Map(declaredKeys),
|
|
653
|
+
semantics: model.semantics,
|
|
654
|
+
inputs: model.inputs,
|
|
655
|
+
rules: model.rules,
|
|
656
|
+
keyMeta: model.keyMeta,
|
|
657
|
+
ruleMeta: model.ruleMeta,
|
|
658
|
+
inputKeys: [...inputKeys],
|
|
659
|
+
orderedRuleTargets,
|
|
660
|
+
ruleByTarget,
|
|
661
|
+
depsByTarget,
|
|
662
|
+
dependentsByKey
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region src/input.ts
|
|
668
|
+
function input(key) {
|
|
669
|
+
return {
|
|
670
|
+
kind: "input",
|
|
671
|
+
key
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/model/create-model.ts
|
|
676
|
+
function createModel() {
|
|
677
|
+
const inputs = [];
|
|
678
|
+
const rules = [];
|
|
679
|
+
const keyMeta = /* @__PURE__ */ new Map();
|
|
680
|
+
const ruleMeta = /* @__PURE__ */ new Map();
|
|
681
|
+
const semanticsMap = /* @__PURE__ */ new Map();
|
|
682
|
+
return {
|
|
683
|
+
input(k, meta = {}, semantics) {
|
|
684
|
+
inputs.push(input(k));
|
|
685
|
+
keyMeta.set(k.id, meta);
|
|
686
|
+
if (semantics) semanticsMap.set(k.id, semantics);
|
|
687
|
+
return k;
|
|
688
|
+
},
|
|
689
|
+
rule(r, meta = {}, semantics) {
|
|
690
|
+
rules.push(r);
|
|
691
|
+
ruleMeta.set(r.target.id, meta);
|
|
692
|
+
if (semantics) semanticsMap.set(r.target.id, semantics);
|
|
693
|
+
return r.target;
|
|
694
|
+
},
|
|
695
|
+
build() {
|
|
696
|
+
return {
|
|
697
|
+
inputs,
|
|
698
|
+
rules,
|
|
699
|
+
semantics: semanticsMap,
|
|
700
|
+
keyMeta,
|
|
701
|
+
ruleMeta
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/model/model-graph.ts
|
|
708
|
+
function toGraph(model) {
|
|
709
|
+
return {
|
|
710
|
+
nodes: [...model.inputKeys, ...model.orderedRuleTargets],
|
|
711
|
+
edges: model.orderedRuleTargets.flatMap((to) => (model.depsByTarget.get(to) ?? []).map((from) => ({
|
|
712
|
+
from,
|
|
713
|
+
to
|
|
714
|
+
})))
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function subgraph(model, includedKeys) {
|
|
718
|
+
const included = new Set(includedKeys);
|
|
719
|
+
const nodes = [...model.inputKeys.filter((k) => included.has(k)), ...model.orderedRuleTargets.filter((k) => included.has(k))];
|
|
720
|
+
const edges = [];
|
|
721
|
+
for (const target of model.orderedRuleTargets) {
|
|
722
|
+
if (!included.has(target)) continue;
|
|
723
|
+
const deps = model.depsByTarget.get(target) ?? [];
|
|
724
|
+
for (const dep of deps) if (included.has(dep)) edges.push({
|
|
725
|
+
from: dep,
|
|
726
|
+
to: target
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
nodes,
|
|
731
|
+
edges
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function upstreamGraphOf(model, key, options) {
|
|
735
|
+
const keys = new Set(upstreamOf(model, key));
|
|
736
|
+
if (options?.includeTarget ?? true) keys.add(key.id);
|
|
737
|
+
return subgraph(model, [...keys]);
|
|
738
|
+
}
|
|
739
|
+
function downstreamGraphOf(model, key, options) {
|
|
740
|
+
const keys = new Set(downstreamOf(model, key));
|
|
741
|
+
if (options?.includeTarget ?? true) keys.add(key.id);
|
|
742
|
+
return subgraph(model, [...keys]);
|
|
743
|
+
}
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/rule/index.ts
|
|
746
|
+
function rule(def) {
|
|
747
|
+
return {
|
|
748
|
+
__kind: "rule",
|
|
749
|
+
spec: def.spec,
|
|
750
|
+
target: def.target,
|
|
751
|
+
deps: def.deps,
|
|
752
|
+
eval: def.eval
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|
|
756
|
+
//#region src/rule/operand.ts
|
|
757
|
+
function resolveOperand(operand, get) {
|
|
758
|
+
return operand.__kind === "value" ? operand.value : get(operand);
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/rule/decision.ts
|
|
762
|
+
function evalPredicate(p, ops, get) {
|
|
763
|
+
if (p.op === "in") return p.values.some((v) => ops.eq(get(p.source), resolveOperand(v, get)));
|
|
764
|
+
else {
|
|
765
|
+
const left = get(p.source);
|
|
766
|
+
const right = resolveOperand(p.right, get);
|
|
767
|
+
switch (p.op) {
|
|
768
|
+
case "eq": return ops.eq(get(p.source), right);
|
|
769
|
+
case "gt": return ops.compare(left, right) > 0;
|
|
770
|
+
case "gte": return ops.compare(left, right) >= 0;
|
|
771
|
+
case "lt": return ops.compare(left, right) < 0;
|
|
772
|
+
case "lte": return ops.compare(left, right) <= 0;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function predicateDependencies(p) {
|
|
777
|
+
if ("right" in p && p.right.__kind === "key") return [p.source, p.right];
|
|
778
|
+
return [p.source];
|
|
779
|
+
}
|
|
780
|
+
function decisionDependencies(table) {
|
|
781
|
+
const deps = /* @__PURE__ */ new Map();
|
|
782
|
+
for (const row of table.rows) for (const predicate of row.when) for (const dep of predicateDependencies(predicate)) deps.set(dep.id, dep);
|
|
783
|
+
if (table.default?.__kind === "key") deps.set(table.default.id, table.default);
|
|
784
|
+
return [...deps.values()];
|
|
785
|
+
}
|
|
786
|
+
function toSpec(ops, table) {
|
|
787
|
+
const spec = {
|
|
788
|
+
op: "decision",
|
|
789
|
+
opsDescriptor: {
|
|
790
|
+
family: ops.family,
|
|
791
|
+
version: ops.version
|
|
792
|
+
},
|
|
793
|
+
table: {
|
|
794
|
+
name: table.name,
|
|
795
|
+
rows: table.rows
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
if (table.default) spec.table.default = table.default;
|
|
799
|
+
return spec;
|
|
800
|
+
}
|
|
801
|
+
function decision(ops, target, table) {
|
|
802
|
+
return rule({
|
|
803
|
+
target,
|
|
804
|
+
spec: toSpec(ops, table),
|
|
805
|
+
deps: decisionDependencies(table),
|
|
806
|
+
eval: (get) => {
|
|
807
|
+
for (const row of table.rows) if (row.when.every((p) => evalPredicate(p, ops, get))) return {
|
|
808
|
+
output: resolveOperand(row.output, get),
|
|
809
|
+
detail: {
|
|
810
|
+
op: "decision",
|
|
811
|
+
tableName: table.name,
|
|
812
|
+
matchedRowId: row.id,
|
|
813
|
+
matchedRowLabel: row.label,
|
|
814
|
+
usedDefault: false
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
if (table.default === void 0) throw new Error(`No matching row found for decision table ${table.name}`);
|
|
818
|
+
return {
|
|
819
|
+
output: resolveOperand(table.default, get),
|
|
820
|
+
detail: {
|
|
821
|
+
op: "decision",
|
|
822
|
+
tableName: table.name,
|
|
823
|
+
usedDefault: true
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region src/rule/projection.ts
|
|
831
|
+
function projection(target, source, field) {
|
|
832
|
+
return rule({
|
|
833
|
+
target,
|
|
834
|
+
spec: {
|
|
835
|
+
op: "project",
|
|
836
|
+
source: source.id,
|
|
837
|
+
field: String(field)
|
|
838
|
+
},
|
|
839
|
+
deps: [source],
|
|
840
|
+
eval: (get) => {
|
|
841
|
+
return { output: get(source)[field] };
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region src/rule/ratio.ts
|
|
847
|
+
function ratio(ops, target, numerator, denominator) {
|
|
848
|
+
return rule({
|
|
849
|
+
target,
|
|
850
|
+
spec: {
|
|
851
|
+
op: "ratio",
|
|
852
|
+
opsDescriptor: {
|
|
853
|
+
family: ops.family,
|
|
854
|
+
version: ops.version
|
|
855
|
+
},
|
|
856
|
+
numerator: numerator.id,
|
|
857
|
+
denominator: denominator.id
|
|
858
|
+
},
|
|
859
|
+
deps: [numerator, denominator],
|
|
860
|
+
eval: (get) => {
|
|
861
|
+
return { output: ops.div(get(numerator), get(denominator)) };
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region src/rule/scale.ts
|
|
867
|
+
function scale(ops, target, input, factor) {
|
|
868
|
+
return rule({
|
|
869
|
+
target,
|
|
870
|
+
spec: {
|
|
871
|
+
op: "scale",
|
|
872
|
+
opsDescriptor: {
|
|
873
|
+
family: ops.family,
|
|
874
|
+
version: ops.version
|
|
875
|
+
},
|
|
876
|
+
input: input.id,
|
|
877
|
+
factor: factor.id
|
|
878
|
+
},
|
|
879
|
+
deps: [input, factor],
|
|
880
|
+
eval: (get) => {
|
|
881
|
+
return { output: ops.scale(get(input), get(factor)) };
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
//#endregion
|
|
886
|
+
//#region src/rule/sum.ts
|
|
887
|
+
function sum(ops, target, deps) {
|
|
888
|
+
return rule({
|
|
889
|
+
target,
|
|
890
|
+
spec: {
|
|
891
|
+
op: "sum",
|
|
892
|
+
opsDescriptor: {
|
|
893
|
+
family: ops.family,
|
|
894
|
+
version: ops.version
|
|
895
|
+
},
|
|
896
|
+
deps: deps.map((d) => d.id)
|
|
897
|
+
},
|
|
898
|
+
deps,
|
|
899
|
+
eval: (get) => {
|
|
900
|
+
return { output: deps.reduce((acc, b) => ops.add(acc, get(b)), ops.zero()) };
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/rule/weighted-sum.ts
|
|
906
|
+
function weightedSum(ops, target, deps) {
|
|
907
|
+
return rule({
|
|
908
|
+
target,
|
|
909
|
+
spec: {
|
|
910
|
+
op: "weighted-sum",
|
|
911
|
+
opsDescriptor: {
|
|
912
|
+
family: ops.family,
|
|
913
|
+
version: ops.version
|
|
914
|
+
},
|
|
915
|
+
deps: deps.map((d) => d.key.id),
|
|
916
|
+
weights: deps.map((d) => d.weight)
|
|
917
|
+
},
|
|
918
|
+
deps: deps.map((d) => d.key),
|
|
919
|
+
eval: (get) => {
|
|
920
|
+
return { output: deps.reduce((acc, { key, weight }) => {
|
|
921
|
+
const factor = resolveOperand(weight, get);
|
|
922
|
+
const value = get(key);
|
|
923
|
+
return ops.add(acc, ops.scale(value, factor));
|
|
924
|
+
}, ops.zero()) };
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/semantics/algebra.ts
|
|
930
|
+
const defaultOps = {
|
|
931
|
+
family: "default/unknown",
|
|
932
|
+
version: "1",
|
|
933
|
+
eq: Object.is
|
|
934
|
+
};
|
|
935
|
+
const defaultNumberOps = {
|
|
936
|
+
family: "default/number",
|
|
937
|
+
version: "1",
|
|
938
|
+
eq: (a, b) => a === b,
|
|
939
|
+
compare: (a, b) => a < b ? -1 : a > b ? 1 : 0,
|
|
940
|
+
zero: () => 0,
|
|
941
|
+
add: (a, b) => a + b,
|
|
942
|
+
sub: (a, b) => a - b,
|
|
943
|
+
one: () => 1,
|
|
944
|
+
scale: (value, factor) => value * factor,
|
|
945
|
+
div: (a, b) => a / b
|
|
946
|
+
};
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/value.ts
|
|
949
|
+
function value(value) {
|
|
950
|
+
return {
|
|
951
|
+
__kind: "value",
|
|
952
|
+
value
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
//#endregion
|
|
956
|
+
export { analyzeDraft, compileModel, createDraft, createModel, decision, defaultEvaluateMode, defaultNumberOps, defaultOps, diffResults, downstreamGraphOf, downstreamOf, evaluate, evaluateDraft, evaluateOverlay, explainDiffs, freezeDraftAnalysis, getDeclaredKeys, getDependencies, getDependents, inspectDiffTarget, inspectModelTarget, inspectTraceTarget, inspectionNodeToAscii, isEmptyDraft, key, projection, ratio, resolveOperand, rule, scale, sortKeysByModelOrder, subgraph, sum, toGraph, upstreamGraphOf, upstreamOf, value, weightedSum };
|