@telorun/sdk 0.77.0 → 0.80.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 (61) hide show
  1. package/dist/cancellation.d.ts +50 -1
  2. package/dist/cancellation.d.ts.map +1 -1
  3. package/dist/contract-errors.d.ts +8 -1
  4. package/dist/contract-errors.d.ts.map +1 -1
  5. package/dist/contract-errors.js +8 -0
  6. package/dist/durable-run.d.ts +310 -0
  7. package/dist/durable-run.d.ts.map +1 -0
  8. package/dist/durable-run.js +223 -0
  9. package/dist/durable-suspension.d.ts +143 -0
  10. package/dist/durable-suspension.d.ts.map +1 -0
  11. package/dist/durable-suspension.js +153 -0
  12. package/dist/durable-target-encoding.d.ts +49 -0
  13. package/dist/durable-target-encoding.d.ts.map +1 -0
  14. package/dist/durable-target-encoding.js +121 -0
  15. package/dist/duration.d.ts +1 -1
  16. package/dist/duration.js +5 -5
  17. package/dist/evaluation-context.d.ts +16 -0
  18. package/dist/evaluation-context.d.ts.map +1 -1
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -0
  22. package/dist/invoke-step.d.ts +86 -1
  23. package/dist/invoke-step.d.ts.map +1 -1
  24. package/dist/invoke-step.js +261 -18
  25. package/dist/resource-context.d.ts +37 -0
  26. package/dist/resource-context.d.ts.map +1 -1
  27. package/dist/resource-instance.d.ts +21 -1
  28. package/dist/resource-instance.d.ts.map +1 -1
  29. package/dist/resource-instance.js +6 -2
  30. package/dist/step-engine.d.ts +170 -0
  31. package/dist/step-engine.d.ts.map +1 -0
  32. package/dist/step-engine.js +365 -0
  33. package/dist/zone-attribute.d.ts +101 -0
  34. package/dist/zone-attribute.d.ts.map +1 -0
  35. package/dist/zone-attribute.js +130 -0
  36. package/dist/zone-attributes/entries/atomic.json +7 -0
  37. package/dist/zone-attributes/entries/idempotent.json +6 -0
  38. package/dist/zone-attributes/entries/index.d.ts +3 -0
  39. package/dist/zone-attributes/entries/index.d.ts.map +1 -0
  40. package/dist/zone-attributes/entries/index.js +13 -0
  41. package/dist/zone-attributes/entries/no-suspend.json +6 -0
  42. package/dist/zone-attributes/entries/replayed.json +6 -0
  43. package/package.json +1 -1
  44. package/src/cancellation.ts +50 -1
  45. package/src/contract-errors.ts +9 -0
  46. package/src/durable-run.ts +450 -0
  47. package/src/durable-suspension.ts +188 -0
  48. package/src/durable-target-encoding.ts +181 -0
  49. package/src/duration.ts +5 -5
  50. package/src/evaluation-context.ts +17 -0
  51. package/src/index.ts +5 -1
  52. package/src/invoke-step.ts +378 -24
  53. package/src/resource-context.ts +37 -0
  54. package/src/resource-instance.ts +32 -2
  55. package/src/step-engine.ts +627 -0
  56. package/src/zone-attribute.ts +208 -0
  57. package/src/zone-attributes/entries/atomic.json +7 -0
  58. package/src/zone-attributes/entries/idempotent.json +6 -0
  59. package/src/zone-attributes/entries/index.ts +14 -0
  60. package/src/zone-attributes/entries/no-suspend.json +6 -0
  61. package/src/zone-attributes/entries/replayed.json +6 -0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Zone attributes — what an `x-telo-provides-zone` object form declares about
3
+ * the region a body slot establishes, and the single accessor every surface
4
+ * reads that vocabulary through (the `value-type.ts` precedent, itself the
5
+ * `ref-slot.ts` one).
6
+ *
7
+ * A body slot that CONSTRAINS its contents is a body slot that already
8
+ * ESTABLISHES a zone — a transaction, a lease, an idempotency claim, a durable
9
+ * run are all of them — so the constraints are attributes on the annotation
10
+ * rather than a second annotation family that would have to restate the zone's
11
+ * location, its `extends` resolution and its runtime open call.
12
+ *
13
+ * THE VOCABULARY IS DATA; THE MEANING IS THE CONSUMER'S. Entries live at
14
+ * `sdk/zone-attributes/*.json` (see the README there) and are copied in by the
15
+ * root `prepare`. Both kernels read the identical files, because `noSuspend` is
16
+ * what stops a run parking inside a lease wherever that run executes. An entry
17
+ * declares a name, a value schema and its `requires:` dependencies, and no code:
18
+ * there is nothing per entry to implement.
19
+ *
20
+ * THE SET IS CLOSED. `x-telo-ref`'s `use` is a closed set on the same annotation
21
+ * family and nothing has needed to extend it; capabilities and value types are
22
+ * closed. The argument for openness inverts on inspection — `metadata.categories`
23
+ * is open precisely because NOTHING BRANCHES ON IT, while every zone attribute
24
+ * exists to be branched on and every reader is core. And a third-party attribute
25
+ * could only ever be HALF an attribute: a module cannot contribute an analyzer
26
+ * pass, so it would get a runtime reader here and no static check, while the
27
+ * failure directions that justify validating this vocabulary at all — an unread
28
+ * `noSuspend`, an unread `atomic` — are exactly the ones only a static check
29
+ * catches.
30
+ *
31
+ * THE REGISTRY IS IN THE SDK for the reasons the value-type one is: it is
32
+ * dependency-free and Node-built-in-free (so the browser-side analyzer can read
33
+ * it), and it is the only placement a module controller can reach.
34
+ */
35
+
36
+ import type { ZoneEntry } from "./cancellation.js";
37
+ import { ZONE_ATTRIBUTE_ENTRY_FILES } from "./zone-attributes/entries/index.js";
38
+
39
+ /** One zone attribute, exactly as its entry file declares it. */
40
+ export interface ZoneAttributeEntry {
41
+ /** The bare name an author writes as a key inside the annotation. Bare rather
42
+ * than `Telo.`-qualified because the position already implies the namespace
43
+ * and a closed set has no second namespace to disambiguate against. */
44
+ readonly name: string;
45
+ /** JSON Schema the declared value must satisfy — always the author's REASON,
46
+ * required by being the value itself rather than a sibling of a boolean. That
47
+ * is also what makes a type check possible at all: there is no `true` to
48
+ * accept, so `atomic: true` fails this schema. */
49
+ readonly value: Record<string, unknown>;
50
+ /** Attributes that must be declared alongside this one. Compiled to JSON
51
+ * Schema's `dependentRequired`, so the completeness rule lives in the data
52
+ * beside the thing it constrains rather than as a hardcoded pair of names. */
53
+ readonly requires: readonly string[];
54
+ readonly description: string;
55
+ }
56
+
57
+ class ZoneAttributeEntryError extends Error {
58
+ constructor(file: string, detail: string) {
59
+ super(`Invalid zone-attribute entry '${file}': ${detail}`);
60
+ this.name = "ZoneAttributeEntryError";
61
+ }
62
+ }
63
+
64
+ const ENTRY_KEYS = ["name", "value", "requires", "description", "$comment"] as const;
65
+
66
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
67
+ return typeof value === "object" && value !== null && !Array.isArray(value);
68
+ }
69
+
70
+ function requireString(file: string, node: Record<string, unknown>, key: string): string {
71
+ const value = node[key];
72
+ if (typeof value !== "string" || value.length === 0) {
73
+ throw new ZoneAttributeEntryError(file, `'${key}' must be a non-empty string`);
74
+ }
75
+ return value;
76
+ }
77
+
78
+ /**
79
+ * Read one entry file's parsed data.
80
+ *
81
+ * Reading is STRICT and the vocabulary is closed at every level, for the reason
82
+ * the value-type reader is: a malformed or typo'd entry's only other outcome is
83
+ * an attribute that quietly is not in the vocabulary — which reads to an author
84
+ * as "unknown name", pointing at their manifest instead of at the entry.
85
+ */
86
+ export function parseZoneAttributeEntry(file: string, data: unknown): ZoneAttributeEntry {
87
+ if (!isPlainObject(data)) throw new ZoneAttributeEntryError(file, "an entry must be a mapping");
88
+ for (const key of Object.keys(data)) {
89
+ if (!(ENTRY_KEYS as readonly string[]).includes(key)) {
90
+ throw new ZoneAttributeEntryError(
91
+ file,
92
+ `an entry has no key '${key}'. Known keys: ${ENTRY_KEYS.join(", ")}.`,
93
+ );
94
+ }
95
+ }
96
+
97
+ const name = requireString(file, data, "name");
98
+ // Bare names, checked here rather than left to convention: a qualified one
99
+ // would be a key nothing resolves, and the closed set has nothing to qualify
100
+ // against.
101
+ if (!/^[a-z][A-Za-z0-9]*$/.test(name)) {
102
+ throw new ZoneAttributeEntryError(
103
+ file,
104
+ `'name' must be a bare camelCase word — the annotation position already implies ` +
105
+ `the namespace, and a closed set has no second namespace to qualify against`,
106
+ );
107
+ }
108
+
109
+ if (!isPlainObject(data.value)) {
110
+ throw new ZoneAttributeEntryError(file, "'value' must be a JSON Schema mapping");
111
+ }
112
+
113
+ const requires = data.requires === undefined ? [] : data.requires;
114
+ if (!Array.isArray(requires) || requires.some((r) => typeof r !== "string" || !r)) {
115
+ throw new ZoneAttributeEntryError(file, "'requires' must be a sequence of attribute names");
116
+ }
117
+ if (requires.includes(name)) {
118
+ throw new ZoneAttributeEntryError(file, `'requires' names '${name}' itself`);
119
+ }
120
+
121
+ return {
122
+ name,
123
+ value: data.value,
124
+ requires: requires as readonly string[],
125
+ description: requireString(file, data, "description"),
126
+ };
127
+ }
128
+
129
+ function buildRegistry(): ReadonlyMap<string, ZoneAttributeEntry> {
130
+ const registry = new Map<string, ZoneAttributeEntry>();
131
+ for (const [file, data] of ZONE_ATTRIBUTE_ENTRY_FILES) {
132
+ const entry = parseZoneAttributeEntry(file, data);
133
+ if (registry.has(entry.name)) {
134
+ throw new ZoneAttributeEntryError(file, `'${entry.name}' is already declared by another entry`);
135
+ }
136
+ registry.set(entry.name, entry);
137
+ }
138
+ // A `requires:` naming an attribute no entry declares would compile to a
139
+ // `dependentRequired` clause nothing can ever satisfy, so every declaration of
140
+ // the depending attribute would be rejected with no way to fix it. Checked
141
+ // after the whole set is read, since entries are order-independent.
142
+ for (const entry of registry.values()) {
143
+ for (const dependency of entry.requires) {
144
+ if (!registry.has(dependency)) {
145
+ throw new ZoneAttributeEntryError(
146
+ `${entry.name}.json`,
147
+ `'requires' names '${dependency}', which no entry declares`,
148
+ );
149
+ }
150
+ }
151
+ }
152
+ // Defence in depth against the packaging mistake, whose failure is
153
+ // indistinguishable from an author's typo: every declared attribute becomes an
154
+ // unknown name, reported against manifests that are correct.
155
+ if (registry.size === 0) {
156
+ throw new Error(
157
+ "The zone-attribute vocabulary is empty. `sdk/zone-attributes/*.json` did not reach " +
158
+ "this build — check the file allowlist of whatever packaged it. Continuing would " +
159
+ "report every declared attribute as an unknown name, while enforcing none of them.",
160
+ );
161
+ }
162
+ return registry;
163
+ }
164
+
165
+ /** Every declared zone attribute, keyed by its bare name. */
166
+ export const ZONE_ATTRIBUTES: ReadonlyMap<string, ZoneAttributeEntry> = buildRegistry();
167
+
168
+ /** The declared names, in entry order — what a diagnostic listing the closed
169
+ * vocabulary prints. */
170
+ export function zoneAttributeNames(): string[] {
171
+ return [...ZONE_ATTRIBUTES.keys()];
172
+ }
173
+
174
+ /**
175
+ * The attributes a zone declares, keyed by name, with the author's reason as the
176
+ * value.
177
+ *
178
+ * A typed record rather than a string-keyed bag, which the closed vocabulary is
179
+ * what makes possible. It is a readability gain and NOT a semantic one — the
180
+ * kernel still interprets nothing and branches on no name, exactly as
181
+ * `readRefSlot` hands back `use` without acting on it.
182
+ */
183
+ export type ZoneAttributes = {
184
+ readonly [K in "atomic" | "idempotent" | "noSuspend" | "replayed"]?: string;
185
+ };
186
+
187
+ /**
188
+ * One open zone, paired with what it declares about everything inside it.
189
+ *
190
+ * The kind is carried so a consumer can name the zone in a diagnostic — "the
191
+ * `Sql.Transaction` you are inside forbids parking" — while the attributes are
192
+ * what it actually branches on.
193
+ */
194
+ export interface OpenZoneAttributes {
195
+ /** Canonical `<module>.<Kind>` of the providing kind. */
196
+ readonly kind: string;
197
+ /** What this zone declares, with each author's reason as the value. */
198
+ readonly attributes: ZoneAttributes;
199
+ /** The open entry itself, so a consumer that must ASK something about this
200
+ * particular zone has it in hand — a durable journal answering "do my writes
201
+ * land inside your atomicity?" needs the entry, not the kind.
202
+ *
203
+ * This is not the rejected "attributes on the entry" shape inverted: the
204
+ * entry stays three identities and carries nothing new, it merely travels
205
+ * BESIDE the attributes instead of being looked up again by a caller that
206
+ * would have to re-walk the stack to find it. */
207
+ readonly entry: ZoneEntry;
208
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$comment": "Distinct from `idempotent`, and the exactly-once machinery reads the difference literally: per-step journaling is safe inside a transaction PRECISELY because a rollback erases the journal's own entries too. A region that rolls nothing back has no such property, so it declares `idempotent` instead — borrowing this name there would let a journal's attestation relax collapse and record entries for effects that will re-run.",
3
+ "name": "atomic",
4
+ "requires": ["noSuspend"],
5
+ "value": { "type": "string", "minLength": 1 },
6
+ "description": "Effects inside are discarded together on failure, so a consumer recording them individually would record work a rollback erases."
7
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$comment": "Worn by a provider that ENFORCES the property (Idempotency.Once, whose claim makes it true) and by one that merely ASSERTS it on the author's word (Durable.Idempotent). Which one an author reaches for is a cost decision, visible in the manifest as a different kind rather than as a flag on one.",
3
+ "name": "idempotent",
4
+ "value": { "type": "string", "minLength": 1 },
5
+ "description": "Re-executing the zone is observably a no-op — the same writes land, or land once. Nothing is discarded, so unlike an atomic region there is no rollback for a consumer's own records to participate in."
6
+ }
@@ -0,0 +1,14 @@
1
+ // GENERATED by scripts/copy-zone-attribute-entries.mjs — do not edit, and do not commit.
2
+ // Source: sdk/zone-attributes/*.json (lexically ordered).
3
+ import e0 from "./atomic.json" with { type: "json" };
4
+ import e1 from "./idempotent.json" with { type: "json" };
5
+ import e2 from "./no-suspend.json" with { type: "json" };
6
+ import e3 from "./replayed.json" with { type: "json" };
7
+
8
+ /** Every zone-attribute entry file, in the order the registry reads them. */
9
+ export const ZONE_ATTRIBUTE_ENTRY_FILES: ReadonlyArray<readonly [file: string, data: unknown]> = [
10
+ ["atomic.json", e0],
11
+ ["idempotent.json", e1],
12
+ ["no-suspend.json", e2],
13
+ ["replayed.json", e3],
14
+ ];
@@ -0,0 +1,6 @@
1
+ {
2
+ "$comment": "The clearest case for the reason-as-value design: one name is worn by a transaction holding a connection, a lease that lapses unrenewed, an idempotency claim, and later a deadline scope whose bound would elapse while parked. Four providers, four different reasons, one attribute — without the prose the pressure would be to invent `connectionHeld`, `leaseHeld` and `deadlineBound` as separate entries.",
3
+ "name": "noSuspend",
4
+ "value": { "type": "string", "minLength": 1 },
5
+ "description": "The zone holds something bounded that cannot outlive the current process — a connection, a lease, a claim — so execution inside it must not park and resume elsewhere."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$comment": "This attribute, and nothing else, is what makes a zone durable to the static checks. Keying them on the bare `x-telo-provides-zone` annotation would apply DURABLE_NONDETERMINISM and its siblings inside every Sql.Transaction in the ecosystem, since transactions, leases and idempotency claims all provide zones. Keying them on a `Durable.Run` kind would instead put a module's kind into analyzer code.",
3
+ "name": "replayed",
4
+ "value": { "type": "string", "minLength": 1 },
5
+ "description": "Execution inside may be re-run from a record of a previous execution, so it must reach the same decisions on every pass and the values it produces must be serializable."
6
+ }