@telorun/sdk 0.77.0 → 0.79.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 +23 -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 +23 -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,188 @@
1
+ /**
2
+ * Suspension — the signal a parking kind raises, and the latch that catches a
3
+ * swallowed one. Slice 4 of `kernel/specs/durable-execution.md`.
4
+ *
5
+ * **Suspension is a distinct signal, not an error.** It unwinds the stack to the
6
+ * workflow boundary, so a `try:` step must not catch it, a composer's
7
+ * `catches:` must not map it, and a retry policy must not re-attempt it. Those
8
+ * three sites rethrow on {@link isSuspension} before they convert anything.
9
+ *
10
+ * **But naming the known swallowers is not a defence.** The signal passes
11
+ * through every controller between the parking kind and the workflow — HTTP
12
+ * handlers, agent tool loops, a cache view, and any third-party controller with
13
+ * a `catch (e)` in it. A swallowed suspension silently converts a park into a
14
+ * completed step and duplicates every effect after it, and a pure-conduit kernel
15
+ * cannot see that happen. Enumerating the swallowers is unbounded and would go
16
+ * stale on the next module.
17
+ *
18
+ * So it is **latched, not just thrown**: {@link parkRun} records the signal
19
+ * against the run handle at the moment it raises, and the workflow kind treats
20
+ * *an invocation that returned normally while a suspension is latched* as a hard
21
+ * error ({@link assertNotSwallowed}). Detection is O(1), needs no cooperation
22
+ * from the swallower, and turns an unbounded-surface silent corruption into one
23
+ * loud failure at the boundary that owns the run.
24
+ *
25
+ * **The latch lives here rather than on the backend's handle**, and the
26
+ * distinction is what makes it a guarantee: a backend that had to set it could
27
+ * forget to, and the failure of forgetting is the silent corruption this exists
28
+ * to catch. It is a `WeakMap` in the SDK — the one package with a single scope
29
+ * per process (`REALM_COLLAPSE_NAMES` symlinks it onto the kernel's copy), so
30
+ * the payload rule's "provider-private state hangs off an injected instance"
31
+ * does not bite: there is exactly one map however many controller bundles are
32
+ * loaded.
33
+ */
34
+
35
+ import type { InvokeContext } from "./cancellation.js";
36
+ import type { DurableRunHandle } from "./durable-run.js";
37
+ import { InvokeError } from "./invoke-error.js";
38
+
39
+ /**
40
+ * The backoff above which a retry PARKS instead of sleeping.
41
+ *
42
+ * A threshold is unavoidable and so is stating where it came from. Below it,
43
+ * sleeping in process is cheaper than a park: a park is a journal write, a
44
+ * process exit and a poller pass, and paying that to save a few seconds of an
45
+ * idle timer is worse on both counts. Above it, holding a process open to wait
46
+ * is precisely what durability exists to stop — and the plan's own retry
47
+ * example (`delay: 10s`, three attempts) sits deliberately below it, while a
48
+ * policy backing off to minutes sits above.
49
+ *
50
+ * Shared with the analyzer, which decides statically whether a step's declared
51
+ * policy COULD suspend, so the check and the behaviour cannot disagree about
52
+ * where the line is.
53
+ */
54
+ export const SUSPENDING_BACKOFF_MS = 30_000;
55
+
56
+ /** The code every suspension carries, so a runtime that only sees a shape can
57
+ * still recognise one. */
58
+ export const ERR_DURABLE_SUSPENDED = "ERR_DURABLE_SUSPENDED";
59
+
60
+ /** Where a parked run is waiting for. Exactly one half is meaningful to a
61
+ * backend at a time, but both may be present: an await with a deadline parks on
62
+ * its token AND is due at a time. */
63
+ export interface ParkUntil {
64
+ /** Epoch milliseconds at which the run becomes due. */
65
+ readonly at?: number;
66
+ /** The token a delivery must carry to wake this run. */
67
+ readonly token?: string;
68
+ }
69
+
70
+ /**
71
+ * The signal itself.
72
+ *
73
+ * An `Error` so it unwinds, and deliberately **not** an {@link InvokeError}: an
74
+ * `InvokeError` is the channel a `catches:` list maps by code, and a suspension
75
+ * that could be named there would be catchable by configuration — which is the
76
+ * corruption, spelled as a feature.
77
+ */
78
+ export class DurableSuspension extends Error {
79
+ readonly code = ERR_DURABLE_SUSPENDED;
80
+
81
+ constructor(
82
+ /** The run that parked. */
83
+ readonly runId: string,
84
+ /** The step path it parked at — the journal key its park is recorded under. */
85
+ readonly path: string,
86
+ /** The parking resource's `metadata.name`, so the diagnostic names what
87
+ * waited rather than only where. */
88
+ readonly resource: string,
89
+ readonly until: ParkUntil,
90
+ ) {
91
+ super(
92
+ `Run '${runId}' parked at '${path}' (${resource}). This is a suspension signal, not a ` +
93
+ `failure: it must reach the workflow that owns the run.`,
94
+ );
95
+ this.name = "DurableSuspension";
96
+ }
97
+ }
98
+
99
+ /** Is this the suspension signal? Structural rather than `instanceof`, for the
100
+ * same reason every other cross-boundary test here is: the SDK is one scope per
101
+ * process today, and a runtime that threads a handle it owns should still be
102
+ * recognised. */
103
+ export function isSuspension(err: unknown): err is DurableSuspension {
104
+ return (
105
+ typeof err === "object" &&
106
+ err !== null &&
107
+ (err as { code?: unknown }).code === ERR_DURABLE_SUSPENDED
108
+ );
109
+ }
110
+
111
+ const LATCHED = new WeakMap<DurableRunHandle, DurableSuspension>();
112
+
113
+ /**
114
+ * Park the run — the one way a parking kind suspends.
115
+ *
116
+ * Latches first, then asks the backend to record the park, then throws. Each of
117
+ * the three is load-bearing. Latching FIRST means a backend whose `park()`
118
+ * itself throws something else still leaves the evidence behind. Throwing here
119
+ * rather than trusting `park()`'s `Promise<never>` means a backend that returns
120
+ * — the one bug that would convert a wait into a completed step — is caught by
121
+ * the seam instead of by whatever ran next.
122
+ */
123
+ export async function parkRun(
124
+ handle: DurableRunHandle,
125
+ where: { readonly path: string; readonly resource: string },
126
+ until: ParkUntil,
127
+ ): Promise<never> {
128
+ const signal = new DurableSuspension(handle.runId, where.path, where.resource, until);
129
+ LATCHED.set(handle, signal);
130
+ await handle.park(where, until);
131
+ throw signal;
132
+ }
133
+
134
+ /** The suspension latched against this run, if one was raised during this
135
+ * execution. */
136
+ export function latchedSuspension(handle: DurableRunHandle): DurableSuspension | undefined {
137
+ return LATCHED.get(handle);
138
+ }
139
+
140
+ /**
141
+ * The workflow boundary's check: a body that returned normally while a
142
+ * suspension is latched swallowed one.
143
+ *
144
+ * Raised rather than repaired, because there is nothing to repair: every effect
145
+ * after the swallow already ran, un-parked and un-recorded, and the run's own
146
+ * record would say it completed. What is actionable is the pair of names — the
147
+ * resource that parked and the step path — since the swallower is somewhere
148
+ * between them.
149
+ */
150
+ export function assertNotSwallowed(handle: DurableRunHandle): void {
151
+ const signal = LATCHED.get(handle);
152
+ if (!signal) return;
153
+ throw new InvokeError(
154
+ "ERR_DURABLE_SUSPENSION_SWALLOWED",
155
+ `Run '${signal.runId}': '${signal.resource}' parked the run at step '${signal.path}', but ` +
156
+ `the body returned normally — something between them caught the suspension signal and ` +
157
+ `continued. Every step after the park has now run outside the journal, and the run would ` +
158
+ `have been recorded as completed. A controller in that path has a 'catch' that swallows ` +
159
+ `unknown errors; it must rethrow anything it did not recognise.`,
160
+ { run: signal.runId, path: signal.path, resource: signal.resource },
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Refuse to park inside a zone that forbids it.
166
+ *
167
+ * The runtime half of `noSuspend` — one rule rather than an enumeration of
168
+ * forbidden kinds, so a parking kind added later is covered without touching
169
+ * anything here. The zone's own declared sentence is printed verbatim: it is the
170
+ * author's statement of what is being held open, and nothing generated says it
171
+ * better.
172
+ */
173
+ export function assertMaySuspend(
174
+ ctx: { zoneAttributes?(ctx?: InvokeContext): readonly { kind: string; attributes: { noSuspend?: string } }[] },
175
+ invokeCtx: InvokeContext | undefined,
176
+ where: { readonly resource: string },
177
+ ): void {
178
+ for (const zone of ctx.zoneAttributes?.(invokeCtx) ?? []) {
179
+ const reason = zone.attributes.noSuspend;
180
+ if (!reason) continue;
181
+ throw new InvokeError(
182
+ "ERR_DURABLE_SUSPEND_FORBIDDEN",
183
+ `'${where.resource}' would park the run, but it is inside a ${zone.kind} zone that ` +
184
+ `cannot be held open across a suspension: ${reason}`,
185
+ { resource: where.resource, zone: zone.kind, reason },
186
+ );
187
+ }
188
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * The wire form of a {@link DurableTarget} — spec §5.3.
3
+ *
4
+ * Written in the slice where something first sends one across a process
5
+ * boundary, and not before: a normative format frozen with no consumer is the
6
+ * failure the sequencing rule exists to prevent. What crosses first is a child
7
+ * kernel, which is a real boundary — it shares no instance graph with its
8
+ * parent, so nothing about a target can survive by accident.
9
+ *
10
+ * **JSON, not a URI.** A journal entry already carries its target as JSON, and a
11
+ * second serialization vocabulary for one value is a second thing to keep
12
+ * agreeing. What this adds over `JSON.stringify` is what a FORMAT has to add:
13
+ * a canonical key order, so two runtimes producing the same identity produce the
14
+ * same bytes and an equality check needs no parser; a version tag, so a later
15
+ * form is refused rather than misread; and a stated set of required fields per
16
+ * form, so an incomplete identity is refused at the encoder rather than
17
+ * resolving to the wrong resource at the far end.
18
+ *
19
+ * **Three forms, discriminated by shape rather than by a tag.** Which one a
20
+ * target is follows from which fields it carries — `scope` makes it scoped,
21
+ * `pointer` makes it inline, neither makes it module-level — and the encoder
22
+ * refuses a value carrying both, since that is not a fourth form but a
23
+ * contradiction.
24
+ */
25
+ import type { DurableTarget } from "./durable-run.js";
26
+ import { InvokeError } from "./invoke-error.js";
27
+
28
+ /** Bumped only for a change a previous reader would MISREAD. A new optional
29
+ * field a reader can ignore is not one; a change to what an existing field
30
+ * means is. */
31
+ export const DURABLE_TARGET_ENCODING_VERSION = 1;
32
+
33
+ /**
34
+ * Canonical key order: `v`, `kind`, `name`, `module`, `pointer`, `scope`, and
35
+ * inside `scope`, `owner`, `site`, `stepPath`.
36
+ *
37
+ * Fixed rather than left to whichever code path built the target, because two
38
+ * paths producing the same identity must produce the same bytes — that is what
39
+ * lets a recipient compare, log and key on the encoded form without decoding it
40
+ * first. Enforced by BUILDING the payload in this order and letting
41
+ * `JSON.stringify` follow insertion order, not by passing these as its replacer
42
+ * array: a replacer array filters keys at EVERY depth, so the nested scope keys
43
+ * are absent from the top-level list and a scoped target would encode with an
44
+ * empty `scope` — the identity's whole distinguishing half, dropped silently.
45
+ */
46
+ const SCOPE_KEY_ORDER = ["owner", "site", "stepPath"] as const;
47
+
48
+ /**
49
+ * Encode a target for transport.
50
+ *
51
+ * Refuses rather than guesses. An identity missing what its form requires would
52
+ * decode at the far end into a resource that is merely *similar*, and a step
53
+ * executed against the wrong resource is the one failure durable execution must
54
+ * never produce quietly — so an incomplete target is an error at the sender,
55
+ * where the manifest that produced it is still in reach.
56
+ */
57
+ export function encodeDurableTarget(target: DurableTarget): string {
58
+ if (!target.kind || !target.name) {
59
+ throw new InvokeError(
60
+ "ERR_DURABLE_TARGET_UNENCODABLE",
61
+ `A step target must carry both a kind and a name to cross a process boundary; ` +
62
+ `got kind='${target.kind ?? ""}' name='${target.name ?? ""}'.`,
63
+ { target },
64
+ );
65
+ }
66
+ if (target.pointer !== undefined && (target.scoped || target.scope !== undefined)) {
67
+ throw new InvokeError(
68
+ "ERR_DURABLE_TARGET_UNENCODABLE",
69
+ `A step target is declared one way: at module level, inside a 'with:' scope, or ` +
70
+ `inline. This one carries both a scope and a pointer, which describes no ` +
71
+ `declaration site.`,
72
+ { target },
73
+ );
74
+ }
75
+ // The module is what disambiguates two libraries that each declare a `store`,
76
+ // and a recipient resolving without it would pick whichever it saw first. It
77
+ // is required for every form that leaves the process, and optional only on the
78
+ // in-process interface, where an entry written before it was recorded must
79
+ // still replay.
80
+ if (!target.module) {
81
+ throw new InvokeError(
82
+ "ERR_DURABLE_TARGET_UNENCODABLE",
83
+ `Step target ${target.kind} '${target.name}' carries no declaring module, so a ` +
84
+ `recipient could not tell it from a same-named resource in another module.`,
85
+ { target },
86
+ );
87
+ }
88
+ // A SCOPED target must carry its tuple, and the check is on `scoped` rather
89
+ // than on the presence of `scope` — otherwise a scoped target whose tuple
90
+ // could not be derived would pass here as module-level, which is precisely the
91
+ // misresolution this refusal exists to prevent: at the far end that identity
92
+ // names a DIFFERENT resource that merely shares the name.
93
+ if (target.scoped || target.scope !== undefined) {
94
+ for (const key of SCOPE_KEY_ORDER) {
95
+ if (target.scope?.[key]) continue;
96
+ throw new InvokeError(
97
+ "ERR_DURABLE_TARGET_UNENCODABLE",
98
+ `Step target ${target.kind} '${target.name}' is declared inside a 'with:' scope ` +
99
+ `but records no ${key}. A scoped instance is distinguished by its scope RUN, so ` +
100
+ `an identity missing that names every run of the scope at once.`,
101
+ { target },
102
+ );
103
+ }
104
+ }
105
+ const payload: Record<string, unknown> = {
106
+ v: DURABLE_TARGET_ENCODING_VERSION,
107
+ kind: target.kind,
108
+ name: target.name,
109
+ module: target.module,
110
+ ...(target.pointer === undefined ? {} : { pointer: target.pointer }),
111
+ ...(target.scope === undefined
112
+ ? {}
113
+ : {
114
+ scope: {
115
+ owner: target.scope.owner,
116
+ site: target.scope.site,
117
+ stepPath: target.scope.stepPath,
118
+ },
119
+ }),
120
+ };
121
+ return JSON.stringify(payload);
122
+ }
123
+
124
+ /**
125
+ * Decode a target received from another process.
126
+ *
127
+ * A version this reader does not know is REFUSED, never read as far as it
128
+ * understands: the fields it recognizes may mean something else in a form it has
129
+ * never seen, and resolving anyway is how a step gets executed against a
130
+ * resource nobody named.
131
+ */
132
+ export function decodeDurableTarget(encoded: string): DurableTarget {
133
+ let parsed: unknown;
134
+ try {
135
+ parsed = JSON.parse(encoded);
136
+ } catch (err) {
137
+ throw new InvokeError(
138
+ "ERR_DURABLE_TARGET_UNDECODABLE",
139
+ `A step target arrived that is not valid JSON: ${(err as Error).message}`,
140
+ { encoded },
141
+ { cause: err },
142
+ );
143
+ }
144
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
145
+ throw new InvokeError(
146
+ "ERR_DURABLE_TARGET_UNDECODABLE",
147
+ `A step target arrived that is not an object.`,
148
+ { encoded },
149
+ );
150
+ }
151
+ const raw = parsed as Record<string, unknown>;
152
+ if (raw.v !== DURABLE_TARGET_ENCODING_VERSION) {
153
+ throw new InvokeError(
154
+ "ERR_DURABLE_TARGET_UNDECODABLE",
155
+ `A step target arrived in encoding version ${String(raw.v)}, and this runtime reads ` +
156
+ `version ${DURABLE_TARGET_ENCODING_VERSION}. Reading the fields it recognizes would ` +
157
+ `risk resolving a resource nobody named.`,
158
+ { encoded },
159
+ );
160
+ }
161
+ if (typeof raw.kind !== "string" || typeof raw.name !== "string" || typeof raw.module !== "string") {
162
+ throw new InvokeError(
163
+ "ERR_DURABLE_TARGET_UNDECODABLE",
164
+ `A step target arrived without a kind, a name or a module.`,
165
+ { encoded },
166
+ );
167
+ }
168
+ const scope = raw.scope as Record<string, unknown> | undefined;
169
+ return {
170
+ kind: raw.kind,
171
+ name: raw.name,
172
+ module: raw.module,
173
+ ...(typeof raw.pointer === "string" ? { pointer: raw.pointer } : {}),
174
+ ...(scope &&
175
+ typeof scope.owner === "string" &&
176
+ typeof scope.site === "string" &&
177
+ typeof scope.stepPath === "string"
178
+ ? { scope: { owner: scope.owner, site: scope.site, stepPath: scope.stepPath } }
179
+ : {}),
180
+ };
181
+ }
package/src/duration.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Duration parsing — the shared time helper for controllers. Telo writes human
3
- * durations (`250ms`, `2s`, `1.5m`, `1h`) in manifests for timers, TTLs,
3
+ * durations (`250ms`, `2s`, `1.5m`, `1h`, `30d`) in manifests for timers, TTLs,
4
4
  * windows, connect timeouts, backoff, and so on; this is the single place that
5
5
  * turns one into a millisecond count. Consumers own their own error UX:
6
6
  * `tryParseDurationMs` returns `null` on a bad string so a caller can throw its
@@ -10,8 +10,8 @@
10
10
  */
11
11
  import { RuntimeError } from "./types.js";
12
12
 
13
- const DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h)$/;
14
- const UNIT_MS = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000 } as const;
13
+ const DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/;
14
+ const UNIT_MS = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 } as const;
15
15
 
16
16
  /** Parse a duration string to milliseconds, or `null` if it is not a valid
17
17
  * duration. Lets the caller own the error message and error type. */
@@ -21,7 +21,7 @@ export function tryParseDurationMs(value: string): number | null {
21
21
  return Number(match[1]) * UNIT_MS[match[2] as keyof typeof UNIT_MS];
22
22
  }
23
23
 
24
- /** Parse a duration string (`300s`, `5m`, `1h`, `50ms`) to milliseconds. An
24
+ /** Parse a duration string (`300s`, `5m`, `1h`, `30d`, `50ms`) to milliseconds. An
25
25
  * `undefined` value yields `fallback` (default `0`) for optional fields; an
26
26
  * invalid string throws a plain `Error`. */
27
27
  export function parseDurationMs(value: string | undefined, fallback = 0): number {
@@ -30,7 +30,7 @@ export function parseDurationMs(value: string | undefined, fallback = 0): number
30
30
  if (ms === null) {
31
31
  throw new RuntimeError(
32
32
  "ERR_INVALID_VALUE",
33
- `Invalid duration ${JSON.stringify(value)}; use a number with a unit, e.g. "250ms", "2s", "1.5m", "1h".`,
33
+ `Invalid duration ${JSON.stringify(value)}; use a number with a unit, e.g. "250ms", "2s", "1.5m", "1h", "30d".`,
34
34
  );
35
35
  }
36
36
  return ms;
@@ -115,6 +115,23 @@ export interface EvaluationContext {
115
115
 
116
116
  preInitHook?: PreInitHook;
117
117
 
118
+ /**
119
+ * The manifest a name was DECLARED with, resolved scope-local first and then
120
+ * up the enclosing chain; `alias` routes into that import's exported
121
+ * instances. Undefined when the name resolves to nothing.
122
+ *
123
+ * A DECLARATION, not an instance — nothing here yields one. It is what a
124
+ * contract typed from a referenced declaration reads
125
+ * (`x-telo-schema-projection-from`), which the pending-resource queue cannot
126
+ * answer because it has drained by the time a contract is bound.
127
+ *
128
+ * Optional, like every other kernel-supplied hook on this interface
129
+ * (`getDefinition?`, `preInitHook?`) and for the same reason: a third-party
130
+ * implementation must keep compiling. Absent reads as "no declaration in
131
+ * scope", which leaves a projected slot as its author wrote it.
132
+ */
133
+ resolveDeclaredManifest?(name: string, alias?: string): ResourceManifest | undefined;
134
+
118
135
  /** Looks up a registered resource definition by fully-qualified kind.
119
136
  * Set by the kernel; used for declared-throw-union checks. */
120
137
  getDefinition?: (kind: string) => ResourceDefinition | undefined;
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./capabilities/invokable.js";
5
5
  export * from "./ref.js";
6
6
  export * from "./type-schema-ref.js";
7
7
  export * from "./invoke-step.js";
8
+ export * from "./step-engine.js";
8
9
  export * from "./dispatch-invoke-ref.js";
9
10
  export * from "./capabilities/provider.js";
10
11
  export * from "./capabilities/runnable.js";
@@ -34,5 +35,8 @@ export * from "./runtime-event.js";
34
35
  export * from "./runtime-resource.js";
35
36
  export * from "./stream.js";
36
37
  export * from "./types.js";
38
+ export * from "./durable-run.js";
39
+ export * from "./durable-suspension.js";
40
+ export * from "./durable-target-encoding.js";
37
41
  export * from "./value-type.js";
38
-
42
+ export * from "./zone-attribute.js";