@graphorin/core 0.5.0 → 0.6.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 (92) hide show
  1. package/CHANGELOG.md +98 -1
  2. package/README.md +6 -6
  3. package/dist/channels/channels.d.ts +3 -3
  4. package/dist/channels/channels.d.ts.map +1 -1
  5. package/dist/channels/channels.js.map +1 -1
  6. package/dist/channels/directive.d.ts +8 -3
  7. package/dist/channels/directive.d.ts.map +1 -1
  8. package/dist/channels/directive.js +8 -3
  9. package/dist/channels/directive.js.map +1 -1
  10. package/dist/channels/dispatch.d.ts +9 -1
  11. package/dist/channels/dispatch.d.ts.map +1 -1
  12. package/dist/channels/dispatch.js +9 -1
  13. package/dist/channels/dispatch.js.map +1 -1
  14. package/dist/channels/durable.d.ts +94 -0
  15. package/dist/channels/durable.d.ts.map +1 -0
  16. package/dist/channels/durable.js +106 -0
  17. package/dist/channels/durable.js.map +1 -0
  18. package/dist/channels/index.d.ts +2 -1
  19. package/dist/channels/index.js +2 -1
  20. package/dist/channels/pause.d.ts +4 -4
  21. package/dist/channels/pause.d.ts.map +1 -1
  22. package/dist/channels/pause.js +4 -4
  23. package/dist/channels/pause.js.map +1 -1
  24. package/dist/contracts/auth-token-store.d.ts +2 -2
  25. package/dist/contracts/checkpoint-store.d.ts +41 -5
  26. package/dist/contracts/checkpoint-store.d.ts.map +1 -1
  27. package/dist/contracts/checkpoint-store.js +25 -0
  28. package/dist/contracts/checkpoint-store.js.map +1 -0
  29. package/dist/contracts/embedder.d.ts +1 -1
  30. package/dist/contracts/index.d.ts +4 -4
  31. package/dist/contracts/index.js +2 -1
  32. package/dist/contracts/local-provider-trust.d.ts +4 -4
  33. package/dist/contracts/logger.d.ts +1 -1
  34. package/dist/contracts/logger.js.map +1 -1
  35. package/dist/contracts/memory-store.d.ts +1 -1
  36. package/dist/contracts/memory-store.d.ts.map +1 -1
  37. package/dist/contracts/oauth-server-store.d.ts +1 -1
  38. package/dist/contracts/preferred-model.d.ts +4 -4
  39. package/dist/contracts/preferred-model.js.map +1 -1
  40. package/dist/contracts/provider.d.ts +31 -5
  41. package/dist/contracts/provider.d.ts.map +1 -1
  42. package/dist/contracts/reasoning-retention.d.ts +3 -3
  43. package/dist/contracts/redaction-validator.d.ts +1 -1
  44. package/dist/contracts/sandbox.d.ts +1 -1
  45. package/dist/contracts/secret-value.d.ts +1 -1
  46. package/dist/contracts/secret-value.js.map +1 -1
  47. package/dist/contracts/secrets-store.d.ts +3 -3
  48. package/dist/contracts/session-store.d.ts +9 -7
  49. package/dist/contracts/session-store.d.ts.map +1 -1
  50. package/dist/contracts/tool.d.ts +20 -7
  51. package/dist/contracts/tool.d.ts.map +1 -1
  52. package/dist/contracts/tracer.d.ts +1 -1
  53. package/dist/contracts/tracer.d.ts.map +1 -1
  54. package/dist/contracts/tracer.js.map +1 -1
  55. package/dist/index.d.ts +11 -10
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +5 -3
  58. package/dist/index.js.map +1 -1
  59. package/dist/types/agent-event.d.ts +24 -10
  60. package/dist/types/agent-event.d.ts.map +1 -1
  61. package/dist/types/handoff.d.ts +9 -9
  62. package/dist/types/index.d.ts +5 -5
  63. package/dist/types/memory.d.ts +66 -23
  64. package/dist/types/memory.d.ts.map +1 -1
  65. package/dist/types/message.d.ts +2 -2
  66. package/dist/types/message.d.ts.map +1 -1
  67. package/dist/types/run.d.ts +57 -4
  68. package/dist/types/run.d.ts.map +1 -1
  69. package/dist/types/run.js +1 -1
  70. package/dist/types/run.js.map +1 -1
  71. package/dist/types/sensitivity.d.ts +3 -3
  72. package/dist/types/sensitivity.js.map +1 -1
  73. package/dist/types/stop-condition.d.ts +1 -1
  74. package/dist/types/stop-condition.js.map +1 -1
  75. package/dist/types/tool.d.ts +49 -24
  76. package/dist/types/tool.d.ts.map +1 -1
  77. package/dist/types/usage.d.ts +25 -1
  78. package/dist/types/usage.d.ts.map +1 -1
  79. package/dist/types/usage.js.map +1 -1
  80. package/dist/types/workflow-event.d.ts +8 -1
  81. package/dist/types/workflow-event.d.ts.map +1 -1
  82. package/dist/utils/assert-never.d.ts +1 -1
  83. package/dist/utils/assert-never.js +1 -1
  84. package/dist/utils/assert-never.js.map +1 -1
  85. package/dist/utils/hash.d.ts +9 -7
  86. package/dist/utils/hash.d.ts.map +1 -1
  87. package/dist/utils/hash.js +9 -7
  88. package/dist/utils/hash.js.map +1 -1
  89. package/dist/utils/validation.d.ts +1 -1
  90. package/dist/utils/validation.js +1 -1
  91. package/dist/utils/validation.js.map +1 -1
  92. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,102 @@
1
1
  # @graphorin/core
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Prompt-cache economics end-to-end (audit 2026-07-04 Wave C, cluster C1).
8
+
9
+ - `Usage` gains `cachedReadTokens` / `cacheWriteTokens` (subsets of `promptTokens`), mapped by the vercel adapter (v7 `inputTokenDetails`; reasoning split kept exclusive of `completionTokens`) and the OpenAI-compatible adapter (`prompt_tokens_details.cached_tokens`); the fields flow through step/run aggregation, `usageByModel`, run-state (de)serialization and `withCostTracking` (new `cachedReadPerMtok`/`cacheWritePerMtok` lookup rates, full-input-rate fallback).
10
+ - New opt-in `ProviderRequest.cachePolicy` / `AgentConfig.cachePolicy` (`{ breakpoints: 'auto', ttl? }`): the vercel adapter anchors Anthropic `cache_control` on the first and last conversation messages so the stable prefix is written once and read at the discounted rate every later step.
11
+ - `ModelPrice` gains `cacheWriteUsdPerToken`; `calculateCost` bills cache writes; the bundled pricing snapshot is regenerated (2026-07-04) with current Anthropic 4.x / OpenAI gpt-5 + gpt-4.1 + o3/o4-mini / Gemini 2.5 families (legacy ids retained), `lookupPrice` resolves dated ids via a date-suffix fallback, and a new snapshot-coverage release gate cross-checks the model-tier classifier against the snapshot with an explicit known-unpriced allowlist (post-cutoff models report null cost + WARN instead of invented numbers).
12
+ - Cache-friendly catalogue: handoff tools serialize BEFORE the growing promoted section (byte-stable prefix under `tool_search` promotions), and new `toolPromotion: 'run-boundary'` freezes the advertised catalogue for a whole run while still persisting discoveries.
13
+
14
+ - [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Agent harness quality (audit 2026-07-04 Wave C, cluster C3).
15
+
16
+ - Recoverable error envelope: `ToolError` gains `recoverable` + `recoveryHint` (retry_later / check_input / try_alternative / report_to_user), stamped from the kind at the executor's completion funnel and rendered to the model as a bracketed recovery line under the familiar `Error: <message>`.
17
+ - Transparent bounded tool retry: `rate_limited` outcomes from pure/read-only tools (or tools with an `idempotencyKey`) silently re-execute with exponential backoff up to 3 total attempts (`ToolRateLimitError.retryAfterMs` wins); tune via executor `retry` / `AgentConfig.toolRetry`.
18
+ - Verifier seam: `AgentConfig.verifiers` run deterministic checks on every terminal response, emit `verifier.result` events, feed failures back as a user message and continue up to `maxVerifierRounds` (default 1); throwing verifiers count as passed. Deliberately no evidence-free self-reflection step.
19
+ - ACI: empty successful tool output renders as an explicit `(tool ran successfully with no output)` marker.
20
+ - Deterministic replay: opt-in `recordProviderResponses` journals each step's raw model response onto `RunState.steps[].providerResponse`; new `createReplayProvider(state)` re-drives a run offline and fails loudly on divergence.
21
+
22
+ - [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Deterministic security adoptions (audit 2026-07-04 Wave C, cluster C6).
23
+
24
+ - Derived-taint propagation: opt-in `dataFlowPolicy.derivedTaint: 'strict'` fires the paraphrase-robust `derived-untrusted-to-sink` flow for every model-driven sink call once untrusted content entered the run (CaMeL-style control-flow integrity); the agent also records each tainted step's assistant text as `llm-derived` spans so model-echoed phrasing trips the verbatim probe.
25
+ - Taint into memory (cross-session MINJA leg): `ToolReturn` gains a widen-only `taint` override honoured through the executor record path; `fact_search` / `deep_recall` / `recall_episodes` attach it when any returned item is quarantined or foreign-provenance, re-arming the ledger at recall. `RunState.taintSummary` additionally carries one-way FNV-1a span-tile hashes (no plaintext), so a resumed run re-detects pre-suspend verbatim copies.
26
+ - MCP pinning completed: `toTools({ pinStore })` records fingerprints on first use and REJECTS drift by default when a store is present (rug-pull defense; `onPinMismatch: 'warn'` downgrades); tool-description injection hits at registration are counted (`mcp.tool-description.injection-flagged.total`).
27
+ - Signal-only heuristics + Unicode pre-pass: shared `normalizeForMatching` (NFKC + zero-width strip) applied in the guardrails injection catalogue and the memory quarantine heuristics; security.md repositions all pattern catalogues as best-effort signal, never a sole gate. `TaintLabel.sourceKind` widened to `string` for the new descriptive kinds.
28
+
29
+ - [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Trace-tree observability (audit 2026-07-04 Wave C, cluster C7; pairs periphery-04).
30
+
31
+ The agent loop now emits the previously-declared `agent.run` span per run and `agent.step` spans per step (parented under the run); `tool.execute` parents under the current step via the new optional `RunContext.span`; a `withTracing`-wrapped provider parents under the step via the new `ProviderRequest.parentSpan` (a live handle like `signal`). Attributes align to the OTel GenAI semantic conventions (`gen_ai.operation.name`, `gen_ai.agent.id/name`, `gen_ai.tool.name`, `gen_ai.request.model`, `gen_ai.usage.input_tokens/output_tokens`), parent-based sampling finally has parents to follow, and observability.md documents the real tree plus the memory-tier-spans-not-yet-parented limitation.
32
+
33
+ - [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable workflow orchestration (audit 2026-07-04 Wave D, cluster D1) - promotes the workflow engine to step-checkpoint durable execution and closes the confirmed `workflow-01..14` correctness floor.
34
+
35
+ - Feature floor: atomic checkpoint compare-and-set (`CheckpointStore.put({ expectedLatestId })` + `CheckpointConflictError`, honoured by both bundled stores), planned-order channel writes, merge-failure + boundary-abort + max-steps terminal checkpointing, all-false-`__start__` dead-end, ephemeral-value observability on `workflow.channel.update`, satisfied-pause retention on sibling failure, `maxConcurrentTasks` bounded task pool, `Dispatch` cross-realm brand, and hygiene (dead `visitedNodes`, six->seven stream modes, `'async'` source removed, resume durability override).
36
+ - New durable capabilities: per-node `timeoutMs` + `retry` (`nodeDefaults`), durable timers (`sleepUntil`/`sleepFor` + `workflow.tick`), durable promises (`awaitExternal` + `resolveAwakeable`), persisted approvals (`requestApproval` + `approve`), `WorkflowConfig.version` pinning (`workflow-version-mismatch`) + journal-divergence detection, and opt-in step journaling (`journalSteps`) with crash-recovery that replays completed tasks and re-runs only unfinished work.
37
+
38
+ - [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Sub-agent isolation & orchestration primitives (audit 2026-07-04 Wave D, cluster D2).
39
+
40
+ - Run-level `'read-only'` capability (single-writer constraint): `AgentConfig.capability` / `AgentCallOptions.capability` never advertise writer tools or handoffs and the executor deterministically blocks fabricated writer calls with the new `capability_blocked` `ToolErrorKind` (threaded through `executeBatch`/`executeOne`, HITL resume, and the code-mode bridge).
41
+ - `toTool({ contextFold })` returns a compact distilled child-run outcome instead of raw output; `toTool({ propagateTaint })` (default on) carries the child's coarse taint flags across the fold as a widen-only `ToolReturn.taint` override (`sourceKind: 'sub-agent'`) that re-arms the parent's data-flow ledger.
42
+
43
+ - [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Memory architecture evolution (audit 2026-07-04 Wave D, cluster D3) - opt-in; defaults byte-identical.
44
+
45
+ - Learned-context digest block (Letta sleep-time): a deep-phase pass rewrites the reserved `learned_context` working block from recent evidence via one budgeted LLM call (`consolidator.learnedContext`, off at every tier).
46
+ - Principal/owner dimension (`MemoryOwner` on facts/episodes/rules/insights, migration 026) stamped `'agent'` on synthesized writes, with a retrieval-time `owner` filter (`FactSearchOptions.owner`); default reads apply no filter.
47
+ - Retrieval-frequency reinforcement: `facts.access_count` (migration 027) + opt-in `SalienceWeights.accessReinforcement` (default 0 = inert).
48
+ - Runbook memory: `rules_fts` (migration 028) + `ProceduralMemory.search` returns whole validated procedures, with a gated `runbook_search` tool (`createMemory({ runbookSearch: true })`).
49
+
50
+ - [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Security hardening (audit 2026-07-04 Wave D, cluster D4) - architectural, deterministic layers.
51
+
52
+ - RFC-6962 Merkle transparency over the audit log (`@graphorin/security/audit`): tree head, inclusion + consistency proofs, and Ed25519-signed checkpoints, so anchoring a signed head out-of-band makes the trail tamper-resistant (a rewrite of a checkpointed prefix fails the consistency proof).
53
+ - Operator trust root for skills: `verifySkillSignature`/`installSkillFrom{Npm,Git}` gain `trustRoot` - a valid signature from a key absent from the root returns `valid: false` reason `'untrusted-key'`.
54
+ - Progent-style tool-argument policies + Rule-of-Two capability profiles (`@graphorin/security/policy`): `AgentConfig.toolPolicy` (forbid-before-allow, default-deny sensitive) + `ruleOfTwo` (drops a lethal-trifecta leg, forcing a read-only capability floor) enforced by a new `ExecutorOptions.argumentPolicy` hook (`capability_blocked`).
55
+ - Code-mode sandbox blocklist parity (`bridged-source.ts`) blocks the process-escape modules (child_process/vm/worker_threads/cluster/inspector) via the ESM resolve hook + a CJS `Module._load` patch.
56
+
57
+ - [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval SOTA graph wins (audit 2026-07-04 Wave D, cluster D5) - offline, opt-in; the billed/migration-heavy legs stay eval-gated.
58
+
59
+ - PPR-lite graded expansion (HippoRAG-style damped spreading activation): `SqliteGraphStore.expandActivation` + `FactSearchOptions.expandHops: 0|1|2` + `graphScoring: 'ppr'` score neighbours by `damping^hopDepth` instead of a flat 1.
60
+ - Graph + entity as first-class tunable fusion weights (`FusionWeights.graph` / `.entity`, were hardcoded neutral).
61
+ - Exact entity-match retriever: `SqliteGraphStore.factsForEntityName` + `FactSearchOptions.entityMatch` add a precise "facts about <entity>" candidate leg.
62
+ - `longmemeval` harness gains `--retrieval ppr` / `--retrieval entity`. Bitemporal event time, Matryoshka truncation, and cascade LLM reranking remain eval-gated.
63
+
64
+ - [#137](https://github.com/o-stepper/graphorin/pull/137) [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627) Thanks [@o-stepper](https://github.com/o-stepper)! - Context engineering trial (audit 2026-07-04 Wave D, cluster D6) - opt-in; tool surface unchanged by default.
65
+
66
+ - Structured plan tool (`update_plan`, TodoWrite-style full-replace checklist) journaled in the new `RunState.todos` (core `TodoItem`), surviving suspend/resume via the strict run-state (de)serializer.
67
+ - Attention recitation: the current plan is rendered into a compact `<plan>` block appended to each step's request copy - request-only and cache-layout-aware (rides the last prompt-cache anchor, never touches the shared buffer or persisted state).
68
+ - `AgentConfig.plan` opt-in. Progress files, resume-recap injection, and the degraded-mode ladder are documented as trial follow-ups.
69
+
70
+ ### Patch Changes
71
+
72
+ - [#134](https://github.com/o-stepper/graphorin/pull/134) [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6) Thanks [@o-stepper](https://github.com/o-stepper)! - Session hard-delete and the RP-6 retention sweep now purge the session's CONTENT (store-01): `session_messages` rows plus their FTS and per-embedder vector index entries, and episodes scoped to the session, all inside the existing cascade transaction. Previously no code path anywhere deleted `session_messages`, so a "deleted" conversation stayed permanently searchable via `memory.session.list/search` - a GDPR hazard for a privacy-positioned framework. The core `SessionStoreExt.deleteSession` contract is updated to document the content cascade.
73
+
74
+ - [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Provider adapters now match their real SDK / wire contracts (audit 2026-07-04 Wave B, cluster B1).
75
+
76
+ - vercel adapter: Graphorin tools convert to the AI SDK's name-keyed record with `jsonSchema()`-shaped input schemas; assistant `toolCalls` become `tool-call` content parts, `ToolMessage`s become `tool-result` messages, system-role messages hoist into the `system` option, and `toolChoice` maps onto the SDK spelling. Tool loops now run against the real `ai` peer (previously every tool conversation failed SDK validation). A real-SDK contract test suite (dev-only `ai` dependency) pins the shapes.
77
+ - Anthropic token counter posts Anthropic wire-shaped bodies (system hoist, `tool_use` / `tool_result` blocks, turn merging) instead of raw Graphorin messages that 400'd on any agent transcript; degradation to the tiktoken fallback now WARNs once.
78
+ - HTTP errors carry a canonical `errorKind` (shared `classifyHttpStatus` mapper: 429 rate-limit, 401/403 unauthorized, 5xx transient/capacity, context-length body sniff) plus captured `retry-after` / `x-ratelimit-*` headers; `withFallback` / `withRetry` consult them, so a 429 on the primary finally fails over and honours server-provided delays.
79
+ - llamacpp-node: the system prompt is no longer injected twice, per-request contexts/sequences are disposed after every stream (KV-cache leak), and aborted streams report `finishReason: 'aborted'`.
80
+ - OpenAI-compatible streaming sends `stream_options: { include_usage: true }` so vLLM / Together / OpenAI report real usage; `openAICompatibleAdapter` gained the `capabilities` / `timeoutMs` options its siblings had.
81
+ - `withCostTracking` bills separately-reported reasoning tokens at the output rate; classifiers recognise Bedrock cross-region ids (`us.anthropic.claude-...`) and the AI SDK's dotted provider ids.
82
+
83
+ - [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Retrieval and consolidation now honour the trust contracts they document (audit 2026-07-04 Wave B, cluster B3).
84
+
85
+ - memory-retrieval-01: default fact reads (FTS / vector / graph) behave as `asOf = now`, so superseded and validity-expired facts no longer surface as current - exactly what the `fact_supersede` tool promises. New `includeSuperseded: true` escape hatch (core `MemorySearchOptions`, memory `FactSearchOptions`, graph expand options) restores the full history for inspector / audit paths; `fact_search` and `deep_recall` outputs expose `validTo` / `supersededBy`.
86
+ - memory-retrieval-02: `deep_recall` passes `forceHard: true` (choosing the tool IS the hardness signal; the local heuristic gate rejected the tool's own documented examples and is English-only). Iterative results carry a new `graded` flag so ungraded single-shot passes stop claiming sufficiency as a verdict.
87
+ - memory-retrieval-03: a tagged search widens the fusion pool the same way decay does - the record-level tags filter runs after the topK cut, so tagged searches no longer silently return fewer than topK hits.
88
+ - memory-consolidation-01: the deep-phase dedup verdict now soft-forgets (replayable tombstone) instead of preferring the GDPR hard-delete `purge`, and a vanished conflicting fact skips the judge entirely (admit, no provider call) - a model verdict can no longer hard-delete the only surviving copy.
89
+ - memory-consolidation-07: the standard phase gains an embedder-independent exact-text duplicate guard (FTS + string equality, quarantine-aware), so replaying a partially-committed slice (DLQ / cursor retry) without an embedder no longer duplicates already-committed facts.
90
+
91
+ ## 0.5.0
92
+
93
+ First version published to the npm registry (with Sigstore build
94
+ provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
95
+ milestones and were never published. All `@graphorin/*` packages release
96
+ lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
97
+ in the repository-level
98
+ [CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
99
+
3
100
  ## 0.1.0
4
101
 
5
102
  ### Minor Changes
@@ -17,7 +114,7 @@
17
114
  `withSignal`, `assertNever`, `md5`, `xxhash`, …).
18
115
  - Workflow channel primitives under `@graphorin/core/channels`:
19
116
  `Directive`, `Dispatch`, `pause`, `LatestValue`, `Reducer`, `Stream`,
20
- `Barrier`, `Ephemeral`, `AnyValue`, `ListAggregate` Graphorin's own
117
+ `Barrier`, `Ephemeral`, `AnyValue`, `ListAggregate` - Graphorin's own
21
118
  vocabulary; names are part of the public API.
22
119
  - Typed no-op defaults `NOOP_TRACER` and `NOOP_LOGGER` so downstream
23
120
  packages can carry a non-null observability surface without taking the
package/README.md CHANGED
@@ -8,11 +8,11 @@ implementation (sandbox, secrets store, memory store, provider adapters,
8
8
  agent runtime, workflow engine, server, …) lives in a sibling package and
9
9
  depends on the interfaces declared here.
10
10
 
11
- - **Status:** v0.5.0 type and contract surface for the v0.1 release line.
12
- - **License:** [MIT](./LICENSE) © 2026 Oleksiy Stepurenko.
11
+ - **Status:** v0.6.0 - type and contract surface for the v0.1 release line.
12
+ - **License:** [MIT](./LICENSE) - © 2026 Oleksiy Stepurenko.
13
13
  - **Engines:** Node.js 22+ (ESM only).
14
14
  - **Runtime dependencies:** none.
15
- - **Peer dependency:** `zod` (`^3.23 || ^4`) used for schema-typed values
15
+ - **Peer dependency:** `zod` (`^3.23 || ^4`) - used for schema-typed values
16
16
  (`stateSchema`, `inputSchema`, `outputSchema`, `EvalScorer`, …). Bring
17
17
  whichever Zod version your application already uses.
18
18
 
@@ -52,8 +52,8 @@ sub-paths so that consumers can import only what they need:
52
52
 
53
53
  Every exported type is annotated with one of two TSDoc tags:
54
54
 
55
- - `@stable` covered by semver guarantees for the `v0.x` line.
56
- - `@experimental` may change between minor versions; a deprecation note
55
+ - `@stable` - covered by semver guarantees for the `v0.x` line.
56
+ - `@experimental` - may change between minor versions; a deprecation note
57
57
  in the `CHANGELOG.md` will accompany every removal.
58
58
 
59
59
  The vast majority of the surface ships as `@stable`; the deliberately
@@ -67,4 +67,4 @@ records are still being refined for v0.2.
67
67
 
68
68
  ---
69
69
 
70
- **Graphorin** · v0.5.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
70
+ **Graphorin** · v0.6.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
@@ -33,7 +33,7 @@ interface LatestValue<T = unknown> {
33
33
  readonly default?: T;
34
34
  }
35
35
  /**
36
- * Overwrite-on-write collisions are silently allowed (last-write-wins
36
+ * Overwrite-on-write - collisions are silently allowed (last-write-wins
37
37
  * semantics within a step).
38
38
  *
39
39
  * @stable
@@ -75,7 +75,7 @@ interface Stream<T = unknown> {
75
75
  readonly default?: ReadonlyArray<T>;
76
76
  }
77
77
  /**
78
- * Barrier completes when every writer in `from` has produced a value.
78
+ * Barrier - completes when every writer in `from` has produced a value.
79
79
  *
80
80
  * @stable
81
81
  */
@@ -85,7 +85,7 @@ interface Barrier<T = unknown> {
85
85
  readonly default?: T;
86
86
  }
87
87
  /**
88
- * Value scoped to a single execution step discarded when the step
88
+ * Value scoped to a single execution step - discarded when the step
89
89
  * ends.
90
90
  *
91
91
  * @stable
@@ -1 +1 @@
1
- {"version":3,"file":"channels.d.ts","names":[],"sources":["../../src/channels/channels.ts"],"sourcesContent":[],"mappings":";;AAYA;AAkBA;;;;;;;;;;AAKI,KAvBQ,WAAA,GAuBR,cAAA,GAAA,WAAA,GAAA,SAAA,GAAA,gBAAA,GAAA,QAAA,GAAA,SAAA,GAAA,WAAA;;;;;;AAUJ;AAWA;AAYA;;AAG0B,KAzCd,OAyCc,CAAA,IAAA,OAAA,CAAA,GAxCtB,WAwCsB,CAxCV,CAwCU,CAAA,GAvCtB,QAuCsB,CAvCb,CAuCa,CAAA,GAtCtB,OAsCsB,CAtCd,CAsCc,CAAA,GArCtB,aAqCsB,CArCR,CAqCQ,CAAA,GApCtB,MAoCsB,CApCf,CAoCe,CAAA,GAnCtB,OAmCsB,CAnCd,CAmCc,CAAA,GAlCtB,SAkCsB,CAlCZ,CAkCY,CAAA;;;;AAQ1B;AAWA;AAWA;AAYiB,UApEA,WAoES,CAAA,IAEJ,OAAA,CAAA,CAAA;EAQN,SAAA,IAAA,EAAW,cAAA;EAAgC,SAAA,OAAA,CAAA,EA5EtC,CA4EsC;;;;AAW3D;;;;AAAsE,UA9ErD,QA8EqD,CAAA,IAAA,OAAA,CAAA,CAAA;EAWtD,SAAA,IAAO,EAAA,WAAA;EACN,SAAA,OAAA,CAAA,EAxFI,CAwFJ;;;;;;;AAajB;;AAA6D,UA3F5C,OA2F4C,CAAA,IAAA,OAAA,CAAA,CAAA;EAAmC,SAAA,IAAA,EAAA,SAAA;EAAd,SAAA,OAAA,CAAA,EAzF7D,CAyF6D;EAAa,SAAA,MAAA,EAAA,CAAA,IAAA,EAxFrE,CAwFqE,EAAA,IAAA,EAxF5D,CAwF4D,EAAA,GAxFtD,CAwFsD;AAW/F;;;;;;AAiBgB,UA5GC,aA4GM,CAAA,IAAA,OAAA,CAAA,CAAA;EACf,SAAA,IAAA,EAAA,gBAAA;EACsB,SAAA,OAAA,CAAA,EA5GT,aA4GS,CA5GK,CA4GL,CAAA;;;;AAY9B;;;;AAAwE,UA/GvD,MA+GuD,CAAA,IAAA,OAAA,CAAA,CAAA;;;qBA5GnD,cAAc;;;;;;;UAQlB;;iBAEA;qBACI;;;;;;;;UASJ;;qBAEI;;;;;;;iBAQL;qBAA2C;IAAM,YAAY;;;;;;iBAW7D;qBAAwC;IAAM,SAAS;;;;;;iBAWvD,0BACC,SAAS,MAAM;qBACF;IAC3B,QAAQ;;;;;;iBAWK;qBAA6C,cAAc;IAAO,cAAc;;;;;;iBAWhF;;qBAEK,cAAc;IAC/B,OAAO;;;;;;iBAcK,iBACR;qBACsB;IAC3B,QAAQ;;;;;;iBAWK;qBAAyC;IAAM,UAAU"}
1
+ {"version":3,"file":"channels.d.ts","names":[],"sources":["../../src/channels/channels.ts"],"sourcesContent":[],"mappings":";;AAYA;AAkBA;;;;;;;;;;AAKI,KAvBQ,WAAA,GAuBR,cAAA,GAAA,WAAA,GAAA,SAAA,GAAA,gBAAA,GAAA,QAAA,GAAA,SAAA,GAAA,WAAA;;;;;;AAUJ;AAWA;AAYA;;AAG0B,KAzCd,OAyCc,CAAA,IAAA,OAAA,CAAA,GAxCtB,WAwCsB,CAxCV,CAwCU,CAAA,GAvCtB,QAuCsB,CAvCb,CAuCa,CAAA,GAtCtB,OAsCsB,CAtCd,CAsCc,CAAA,GArCtB,aAqCsB,CArCR,CAqCQ,CAAA,GApCtB,MAoCsB,CApCf,CAoCe,CAAA,GAnCtB,OAmCsB,CAnCd,CAmCc,CAAA,GAlCtB,SAkCsB,CAlCZ,CAkCY,CAAA;;;;AAQ1B;AAWA;AAWA;AAYiB,UApEA,WAoES,CAEL,IAAC,OAAA,CAAA,CAAA;EAQN,SAAA,IAAA,EAAW,cAAA;EAAgC,SAAA,OAAA,CAAA,EA5EtC,CA4EsC;;;;AAW3D;;;;AAAsE,UA9ErD,QA8EqD,CAAA,IAAA,OAAA,CAAA,CAAA;EAWtD,SAAA,IAAO,EAAA,WAAA;EACN,SAAA,OAAA,CAAA,EAxFI,CAwFJ;;;;;;;AAajB;;AAA6D,UA3F5C,OA2F4C,CAAA,IAAA,OAAA,CAAA,CAAA;EAAmC,SAAA,IAAA,EAAA,SAAA;EAAd,SAAA,OAAA,CAAA,EAzF7D,CAyF6D;EAAa,SAAA,MAAA,EAAA,CAAA,IAAA,EAxFrE,CAwFqE,EAAA,IAAA,EAxF5D,CAwF4D,EAAA,GAxFtD,CAwFsD;AAW/F;;;;;;AAiBgB,UA5GC,aA4GM,CAAA,IAAA,OAAA,CAAA,CAAA;EACf,SAAA,IAAA,EAAA,gBAAA;EACsB,SAAA,OAAA,CAAA,EA5GT,aA4GS,CA5GK,CA4GL,CAAA;;;;AAY9B;;;;AAAwE,UA/GvD,MA+GuD,CAAA,IAAA,OAAA,CAAA,CAAA;;;qBA5GnD,cAAc;;;;;;;UAQlB;;iBAEA;qBACI;;;;;;;;UASJ;;qBAEI;;;;;;;iBAQL;qBAA2C;IAAM,YAAY;;;;;;iBAW7D;qBAAwC;IAAM,SAAS;;;;;;iBAWvD,0BACC,SAAS,MAAM;qBACF;IAC3B,QAAQ;;;;;;iBAWK;qBAA6C,cAAc;IAAO,cAAc;;;;;;iBAWhF;;qBAEK,cAAc;IAC/B,OAAO;;;;;;iBAcK,iBACR;qBACsB;IAC3B,QAAQ;;;;;;iBAWK;qBAAyC;IAAM,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"channels.js","names":["out: { kind: 'stream'; unique?: boolean; default?: ReadonlyArray<T> }"],"sources":["../../src/channels/channels.ts"],"sourcesContent":["/**\n * Workflow channel kinds. Every state field declared on a workflow's\n * `stateSchema` is bound to a channel that decides the merge strategy\n * applied when multiple writers update the same field within a single\n * execution step.\n *\n * The names are **Graphorin's own design** and must not be aliased to\n * terms from other workflow libraries. A dedicated lint rule lands later\n * in the release line to enforce this.\n *\n * @stable\n */\nexport type ChannelKind =\n | 'latest-value'\n | 'any-value'\n | 'reducer'\n | 'list-aggregate'\n | 'stream'\n | 'barrier'\n | 'ephemeral';\n\n/**\n * Discriminated union of every channel descriptor.\n *\n * Channels are a *description* of the merge strategy, not a runtime\n * value: the engine reads the `kind` field plus optional auxiliary\n * fields (`reduce`, `from`, `unique`) to decide how to combine writes.\n *\n * @stable\n */\nexport type Channel<T = unknown> =\n | LatestValue<T>\n | AnyValue<T>\n | Reducer<T>\n | ListAggregate<T>\n | Stream<T>\n | Barrier<T>\n | Ephemeral<T>;\n\n/**\n * Overwrite-on-write. Multiple writes within the same execution step\n * raise `MultiWriteError` (use `AnyValue` if collisions are acceptable).\n *\n * @stable\n */\nexport interface LatestValue<T = unknown> {\n readonly kind: 'latest-value';\n readonly default?: T;\n}\n\n/**\n * Overwrite-on-write collisions are silently allowed (last-write-wins\n * semantics within a step).\n *\n * @stable\n */\nexport interface AnyValue<T = unknown> {\n readonly kind: 'any-value';\n readonly default?: T;\n}\n\n/**\n * Fold writes via a user-provided `reduce` function. The reducer is\n * invoked left-to-right over the writes collected within an execution\n * step.\n *\n * @stable\n */\nexport interface Reducer<T = unknown> {\n readonly kind: 'reducer';\n readonly default?: T;\n readonly reduce: (prev: T, next: T) => T;\n}\n\n/**\n * Specialization of `Reducer<T[]>` that appends each write to a list.\n *\n * @stable\n */\nexport interface ListAggregate<T = unknown> {\n readonly kind: 'list-aggregate';\n readonly default?: ReadonlyArray<T>;\n}\n\n/**\n * Append-only queue. Used for dynamic task creation via `Dispatch(...)`\n * and for application-defined event streams.\n *\n * @stable\n */\nexport interface Stream<T = unknown> {\n readonly kind: 'stream';\n readonly unique?: boolean;\n readonly default?: ReadonlyArray<T>;\n}\n\n/**\n * Barrier completes when every writer in `from` has produced a value.\n *\n * @stable\n */\nexport interface Barrier<T = unknown> {\n readonly kind: 'barrier';\n readonly from: ReadonlyArray<string>;\n readonly default?: T;\n}\n\n/**\n * Value scoped to a single execution step discarded when the step\n * ends.\n *\n * @stable\n */\nexport interface Ephemeral<T = unknown> {\n readonly kind: 'ephemeral';\n readonly default?: T;\n}\n\n/**\n * Construct a `LatestValue` channel.\n *\n * @stable\n */\nexport function latestValue<T>(opts?: { readonly default?: T }): LatestValue<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'latest-value', default: opts.default }\n : { kind: 'latest-value' };\n}\n\n/**\n * Construct an `AnyValue` channel.\n *\n * @stable\n */\nexport function anyValue<T>(opts?: { readonly default?: T }): AnyValue<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'any-value', default: opts.default }\n : { kind: 'any-value' };\n}\n\n/**\n * Construct a `Reducer` channel.\n *\n * @stable\n */\nexport function reducer<T>(\n reduce: (prev: T, next: T) => T,\n opts?: { readonly default?: T },\n): Reducer<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'reducer', reduce, default: opts.default }\n : { kind: 'reducer', reduce };\n}\n\n/**\n * Construct a `ListAggregate` channel.\n *\n * @stable\n */\nexport function listAggregate<T>(opts?: { readonly default?: ReadonlyArray<T> }): ListAggregate<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'list-aggregate', default: opts.default }\n : { kind: 'list-aggregate' };\n}\n\n/**\n * Construct a `Stream` channel.\n *\n * @stable\n */\nexport function stream<T>(opts?: {\n readonly unique?: boolean;\n readonly default?: ReadonlyArray<T>;\n}): Stream<T> {\n const out: { kind: 'stream'; unique?: boolean; default?: ReadonlyArray<T> } = {\n kind: 'stream',\n };\n if (opts?.unique !== undefined) out.unique = opts.unique;\n if (opts?.default !== undefined) out.default = opts.default;\n return out;\n}\n\n/**\n * Construct a `Barrier` channel.\n *\n * @stable\n */\nexport function barrier<T>(\n from: ReadonlyArray<string>,\n opts?: { readonly default?: T },\n): Barrier<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'barrier', from, default: opts.default }\n : { kind: 'barrier', from };\n}\n\n/**\n * Construct an `Ephemeral` channel.\n *\n * @stable\n */\nexport function ephemeral<T>(opts?: { readonly default?: T }): Ephemeral<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'ephemeral', default: opts.default }\n : { kind: 'ephemeral' };\n}\n"],"mappings":";;;;;;AA2HA,SAAgB,YAAe,MAAiD;AAC9E,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAgB,SAAS,KAAK;EAAS,GAC/C,EAAE,MAAM,gBAAgB;;;;;;;AAQ9B,SAAgB,SAAY,MAA8C;AACxE,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAa,SAAS,KAAK;EAAS,GAC5C,EAAE,MAAM,aAAa;;;;;;;AAQ3B,SAAgB,QACd,QACA,MACY;AACZ,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAW;EAAQ,SAAS,KAAK;EAAS,GAClD;EAAE,MAAM;EAAW;EAAQ;;;;;;;AAQjC,SAAgB,cAAiB,MAAkE;AACjG,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAkB,SAAS,KAAK;EAAS,GACjD,EAAE,MAAM,kBAAkB;;;;;;;AAQhC,SAAgB,OAAU,MAGZ;CACZ,MAAMA,MAAwE,EAC5E,MAAM,UACP;AACD,KAAI,MAAM,WAAW,OAAW,KAAI,SAAS,KAAK;AAClD,KAAI,MAAM,YAAY,OAAW,KAAI,UAAU,KAAK;AACpD,QAAO;;;;;;;AAQT,SAAgB,QACd,MACA,MACY;AACZ,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAW;EAAM,SAAS,KAAK;EAAS,GAChD;EAAE,MAAM;EAAW;EAAM;;;;;;;AAQ/B,SAAgB,UAAa,MAA+C;AAC1E,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAa,SAAS,KAAK;EAAS,GAC5C,EAAE,MAAM,aAAa"}
1
+ {"version":3,"file":"channels.js","names":["out: { kind: 'stream'; unique?: boolean; default?: ReadonlyArray<T> }"],"sources":["../../src/channels/channels.ts"],"sourcesContent":["/**\n * Workflow channel kinds. Every state field declared on a workflow's\n * `stateSchema` is bound to a channel that decides the merge strategy\n * applied when multiple writers update the same field within a single\n * execution step.\n *\n * The names are **Graphorin's own design** and must not be aliased to\n * terms from other workflow libraries. A dedicated lint rule lands later\n * in the release line to enforce this.\n *\n * @stable\n */\nexport type ChannelKind =\n | 'latest-value'\n | 'any-value'\n | 'reducer'\n | 'list-aggregate'\n | 'stream'\n | 'barrier'\n | 'ephemeral';\n\n/**\n * Discriminated union of every channel descriptor.\n *\n * Channels are a *description* of the merge strategy, not a runtime\n * value: the engine reads the `kind` field plus optional auxiliary\n * fields (`reduce`, `from`, `unique`) to decide how to combine writes.\n *\n * @stable\n */\nexport type Channel<T = unknown> =\n | LatestValue<T>\n | AnyValue<T>\n | Reducer<T>\n | ListAggregate<T>\n | Stream<T>\n | Barrier<T>\n | Ephemeral<T>;\n\n/**\n * Overwrite-on-write. Multiple writes within the same execution step\n * raise `MultiWriteError` (use `AnyValue` if collisions are acceptable).\n *\n * @stable\n */\nexport interface LatestValue<T = unknown> {\n readonly kind: 'latest-value';\n readonly default?: T;\n}\n\n/**\n * Overwrite-on-write - collisions are silently allowed (last-write-wins\n * semantics within a step).\n *\n * @stable\n */\nexport interface AnyValue<T = unknown> {\n readonly kind: 'any-value';\n readonly default?: T;\n}\n\n/**\n * Fold writes via a user-provided `reduce` function. The reducer is\n * invoked left-to-right over the writes collected within an execution\n * step.\n *\n * @stable\n */\nexport interface Reducer<T = unknown> {\n readonly kind: 'reducer';\n readonly default?: T;\n readonly reduce: (prev: T, next: T) => T;\n}\n\n/**\n * Specialization of `Reducer<T[]>` that appends each write to a list.\n *\n * @stable\n */\nexport interface ListAggregate<T = unknown> {\n readonly kind: 'list-aggregate';\n readonly default?: ReadonlyArray<T>;\n}\n\n/**\n * Append-only queue. Used for dynamic task creation via `Dispatch(...)`\n * and for application-defined event streams.\n *\n * @stable\n */\nexport interface Stream<T = unknown> {\n readonly kind: 'stream';\n readonly unique?: boolean;\n readonly default?: ReadonlyArray<T>;\n}\n\n/**\n * Barrier - completes when every writer in `from` has produced a value.\n *\n * @stable\n */\nexport interface Barrier<T = unknown> {\n readonly kind: 'barrier';\n readonly from: ReadonlyArray<string>;\n readonly default?: T;\n}\n\n/**\n * Value scoped to a single execution step - discarded when the step\n * ends.\n *\n * @stable\n */\nexport interface Ephemeral<T = unknown> {\n readonly kind: 'ephemeral';\n readonly default?: T;\n}\n\n/**\n * Construct a `LatestValue` channel.\n *\n * @stable\n */\nexport function latestValue<T>(opts?: { readonly default?: T }): LatestValue<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'latest-value', default: opts.default }\n : { kind: 'latest-value' };\n}\n\n/**\n * Construct an `AnyValue` channel.\n *\n * @stable\n */\nexport function anyValue<T>(opts?: { readonly default?: T }): AnyValue<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'any-value', default: opts.default }\n : { kind: 'any-value' };\n}\n\n/**\n * Construct a `Reducer` channel.\n *\n * @stable\n */\nexport function reducer<T>(\n reduce: (prev: T, next: T) => T,\n opts?: { readonly default?: T },\n): Reducer<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'reducer', reduce, default: opts.default }\n : { kind: 'reducer', reduce };\n}\n\n/**\n * Construct a `ListAggregate` channel.\n *\n * @stable\n */\nexport function listAggregate<T>(opts?: { readonly default?: ReadonlyArray<T> }): ListAggregate<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'list-aggregate', default: opts.default }\n : { kind: 'list-aggregate' };\n}\n\n/**\n * Construct a `Stream` channel.\n *\n * @stable\n */\nexport function stream<T>(opts?: {\n readonly unique?: boolean;\n readonly default?: ReadonlyArray<T>;\n}): Stream<T> {\n const out: { kind: 'stream'; unique?: boolean; default?: ReadonlyArray<T> } = {\n kind: 'stream',\n };\n if (opts?.unique !== undefined) out.unique = opts.unique;\n if (opts?.default !== undefined) out.default = opts.default;\n return out;\n}\n\n/**\n * Construct a `Barrier` channel.\n *\n * @stable\n */\nexport function barrier<T>(\n from: ReadonlyArray<string>,\n opts?: { readonly default?: T },\n): Barrier<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'barrier', from, default: opts.default }\n : { kind: 'barrier', from };\n}\n\n/**\n * Construct an `Ephemeral` channel.\n *\n * @stable\n */\nexport function ephemeral<T>(opts?: { readonly default?: T }): Ephemeral<T> {\n return opts !== undefined && 'default' in opts && opts.default !== undefined\n ? { kind: 'ephemeral', default: opts.default }\n : { kind: 'ephemeral' };\n}\n"],"mappings":";;;;;;AA2HA,SAAgB,YAAe,MAAiD;AAC9E,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAgB,SAAS,KAAK;EAAS,GAC/C,EAAE,MAAM,gBAAgB;;;;;;;AAQ9B,SAAgB,SAAY,MAA8C;AACxE,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAa,SAAS,KAAK;EAAS,GAC5C,EAAE,MAAM,aAAa;;;;;;;AAQ3B,SAAgB,QACd,QACA,MACY;AACZ,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAW;EAAQ,SAAS,KAAK;EAAS,GAClD;EAAE,MAAM;EAAW;EAAQ;;;;;;;AAQjC,SAAgB,cAAiB,MAAkE;AACjG,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAkB,SAAS,KAAK;EAAS,GACjD,EAAE,MAAM,kBAAkB;;;;;;;AAQhC,SAAgB,OAAU,MAGZ;CACZ,MAAMA,MAAwE,EAC5E,MAAM,UACP;AACD,KAAI,MAAM,WAAW,OAAW,KAAI,SAAS,KAAK;AAClD,KAAI,MAAM,YAAY,OAAW,KAAI,UAAU,KAAK;AACpD,QAAO;;;;;;;AAQT,SAAgB,QACd,MACA,MACY;AACZ,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAW;EAAM,SAAS,KAAK;EAAS,GAChD;EAAE,MAAM;EAAW;EAAM;;;;;;;AAQ/B,SAAgB,UAAa,MAA+C;AAC1E,QAAO,SAAS,UAAa,aAAa,QAAQ,KAAK,YAAY,SAC/D;EAAE,MAAM;EAAa,SAAS,KAAK;EAAS,GAC5C,EAAE,MAAM,aAAa"}
@@ -4,9 +4,14 @@
4
4
  * `Workflow.resume(threadId, directive?)` (or returned from a node's
5
5
  * `pause(...)` resolution) carrying any combination of:
6
6
  *
7
- * - `goto` jump to a named node, bypassing the edge graph.
8
- * - `resume` value supplied to the `pause(value)` call that suspended.
9
- * - `update` additional channel writes applied before the next step.
7
+ * - `goto` - jump to a named node, bypassing the edge graph.
8
+ * **Destructive (workflow-09):** the restored frontier is discarded -
9
+ * surviving dynamic tasks, completed-but-unwalked nodes, and every
10
+ * pending pause record (including already-delivered pause answers)
11
+ * are dropped in favour of the single goto task. Use it as an
12
+ * operator escape hatch, not routine control flow.
13
+ * - `resume` - value supplied to the `pause(value)` call that suspended.
14
+ * - `update` - additional channel writes applied before the next step.
10
15
  *
11
16
  * The shape is **Graphorin's own design** (the name `Directive` is part
12
17
  * of the public API).
@@ -1 +1 @@
1
- {"version":3,"file":"directive.d.ts","names":[],"sources":["../../src/channels/directive.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;;;;;AAiBA;;;;;cAjBa,oBAAoB;;oBAEb;oBACA;oBAEA,iBAAiB,SAAS;;;;;;;UAY7B,2BAA2B;;oBAExB;oBACA"}
1
+ {"version":3,"file":"directive.d.ts","names":[],"sources":["../../src/channels/directive.ts"],"sourcesContent":[],"mappings":";;AAmBA;;;;;;;;AAiBA;;;;;;;;;;cAjBa,oBAAoB;;oBAEb;oBACA;oBAEA,iBAAiB,SAAS;;;;;;;UAY7B,2BAA2B;;oBAExB;oBACA"}
@@ -4,9 +4,14 @@
4
4
  * `Workflow.resume(threadId, directive?)` (or returned from a node's
5
5
  * `pause(...)` resolution) carrying any combination of:
6
6
  *
7
- * - `goto` jump to a named node, bypassing the edge graph.
8
- * - `resume` value supplied to the `pause(value)` call that suspended.
9
- * - `update` additional channel writes applied before the next step.
7
+ * - `goto` - jump to a named node, bypassing the edge graph.
8
+ * **Destructive (workflow-09):** the restored frontier is discarded -
9
+ * surviving dynamic tasks, completed-but-unwalked nodes, and every
10
+ * pending pause record (including already-delivered pause answers)
11
+ * are dropped in favour of the single goto task. Use it as an
12
+ * operator escape hatch, not routine control flow.
13
+ * - `resume` - value supplied to the `pause(value)` call that suspended.
14
+ * - `update` - additional channel writes applied before the next step.
10
15
  *
11
16
  * The shape is **Graphorin's own design** (the name `Directive` is part
12
17
  * of the public API).
@@ -1 +1 @@
1
- {"version":3,"file":"directive.js","names":[],"sources":["../../src/channels/directive.ts"],"sourcesContent":["/**\n * Workflow control-flow primitive: a single value handed to\n * `Workflow.resume(threadId, directive?)` (or returned from a node's\n * `pause(...)` resolution) carrying any combination of:\n *\n * - `goto` jump to a named node, bypassing the edge graph.\n * - `resume` value supplied to the `pause(value)` call that suspended.\n * - `update` additional channel writes applied before the next step.\n *\n * The shape is **Graphorin's own design** (the name `Directive` is part\n * of the public API).\n *\n * @stable\n */\nexport class Directive<TUpdate = Record<string, unknown>, TResume = unknown> {\n readonly goto?: string;\n readonly resume?: TResume;\n readonly update?: TUpdate;\n\n constructor(opts: DirectiveOptions<TUpdate, TResume>) {\n if (opts.goto !== undefined) this.goto = opts.goto;\n if ('resume' in opts && opts.resume !== undefined) this.resume = opts.resume;\n if (opts.update !== undefined) this.update = opts.update;\n }\n}\n\n/**\n * Constructor parameters for `Directive`.\n *\n * @stable\n */\nexport interface DirectiveOptions<TUpdate = Record<string, unknown>, TResume = unknown> {\n readonly goto?: string;\n readonly resume?: TResume;\n readonly update?: TUpdate;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAa,YAAb,MAA6E;CAC3E,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,MAA0C;AACpD,MAAI,KAAK,SAAS,OAAW,MAAK,OAAO,KAAK;AAC9C,MAAI,YAAY,QAAQ,KAAK,WAAW,OAAW,MAAK,SAAS,KAAK;AACtE,MAAI,KAAK,WAAW,OAAW,MAAK,SAAS,KAAK"}
1
+ {"version":3,"file":"directive.js","names":[],"sources":["../../src/channels/directive.ts"],"sourcesContent":["/**\n * Workflow control-flow primitive: a single value handed to\n * `Workflow.resume(threadId, directive?)` (or returned from a node's\n * `pause(...)` resolution) carrying any combination of:\n *\n * - `goto` - jump to a named node, bypassing the edge graph.\n * **Destructive (workflow-09):** the restored frontier is discarded -\n * surviving dynamic tasks, completed-but-unwalked nodes, and every\n * pending pause record (including already-delivered pause answers)\n * are dropped in favour of the single goto task. Use it as an\n * operator escape hatch, not routine control flow.\n * - `resume` - value supplied to the `pause(value)` call that suspended.\n * - `update` - additional channel writes applied before the next step.\n *\n * The shape is **Graphorin's own design** (the name `Directive` is part\n * of the public API).\n *\n * @stable\n */\nexport class Directive<TUpdate = Record<string, unknown>, TResume = unknown> {\n readonly goto?: string;\n readonly resume?: TResume;\n readonly update?: TUpdate;\n\n constructor(opts: DirectiveOptions<TUpdate, TResume>) {\n if (opts.goto !== undefined) this.goto = opts.goto;\n if ('resume' in opts && opts.resume !== undefined) this.resume = opts.resume;\n if (opts.update !== undefined) this.update = opts.update;\n }\n}\n\n/**\n * Constructor parameters for `Directive`.\n *\n * @stable\n */\nexport interface DirectiveOptions<TUpdate = Record<string, unknown>, TResume = unknown> {\n readonly goto?: string;\n readonly resume?: TResume;\n readonly update?: TUpdate;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,YAAb,MAA6E;CAC3E,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,MAA0C;AACpD,MAAI,KAAK,SAAS,OAAW,MAAK,OAAO,KAAK;AAC9C,MAAI,YAAY,QAAQ,KAAK,WAAW,OAAW,MAAK,SAAS,KAAK;AACtE,MAAI,KAAK,WAAW,OAAW,MAAK,SAAS,KAAK"}
@@ -4,13 +4,21 @@
4
4
  * `Dispatch(nodeName, args)` values to schedule additional tasks in the
5
5
  * next execution step.
6
6
  *
7
- * The class is intentionally tiny the engine inspects only the public
7
+ * The class is intentionally tiny - the engine inspects only the public
8
8
  * `nodeName` and `args` fields. The shape is **Graphorin's own design**
9
9
  * (the name `Dispatch` is part of the public API).
10
10
  *
11
11
  * @stable
12
12
  */
13
13
  declare class Dispatch<TArgs = unknown> {
14
+ /**
15
+ * Cross-realm brand (workflow-13): the engine's structural fallback
16
+ * requires this marker so a plain state object that happens to carry
17
+ * `nodeName` + `args` keys is treated as channel WRITES, never
18
+ * silently swallowed as a dispatch. A plain own property (not a
19
+ * symbol) so it survives `structuredClone` across worker boundaries.
20
+ */
21
+ readonly __graphorinDispatch: true;
14
22
  readonly nodeName: string;
15
23
  readonly args: TArgs;
16
24
  constructor(nodeName: string, args: TArgs);
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","names":[],"sources":["../../src/channels/dispatch.ts"],"sourcesContent":[],"mappings":";;AAWA;AAeA;;;;;;;;;cAfa;;iBAEI;sCAEqB;;;;;;;iBAWtB,wCAAwC,QAAQ,SAAS"}
1
+ {"version":3,"file":"dispatch.d.ts","names":[],"sources":["../../src/channels/dispatch.ts"],"sourcesContent":[],"mappings":";;AAWA;AAuBA;;;;;;;;;cAvBa;;;;;;;;;;iBAUI;sCAEqB;;;;;;;iBAWtB,wCAAwC,QAAQ,SAAS"}
@@ -4,13 +4,21 @@
4
4
  * `Dispatch(nodeName, args)` values to schedule additional tasks in the
5
5
  * next execution step.
6
6
  *
7
- * The class is intentionally tiny the engine inspects only the public
7
+ * The class is intentionally tiny - the engine inspects only the public
8
8
  * `nodeName` and `args` fields. The shape is **Graphorin's own design**
9
9
  * (the name `Dispatch` is part of the public API).
10
10
  *
11
11
  * @stable
12
12
  */
13
13
  var Dispatch = class {
14
+ /**
15
+ * Cross-realm brand (workflow-13): the engine's structural fallback
16
+ * requires this marker so a plain state object that happens to carry
17
+ * `nodeName` + `args` keys is treated as channel WRITES, never
18
+ * silently swallowed as a dispatch. A plain own property (not a
19
+ * symbol) so it survives `structuredClone` across worker boundaries.
20
+ */
21
+ __graphorinDispatch = true;
14
22
  nodeName;
15
23
  args;
16
24
  constructor(nodeName, args) {
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.js","names":[],"sources":["../../src/channels/dispatch.ts"],"sourcesContent":["/**\n * Workflow dynamic-task primitive. A node returns one or more\n * `Dispatch(nodeName, args)` values to schedule additional tasks in the\n * next execution step.\n *\n * The class is intentionally tiny the engine inspects only the public\n * `nodeName` and `args` fields. The shape is **Graphorin's own design**\n * (the name `Dispatch` is part of the public API).\n *\n * @stable\n */\nexport class Dispatch<TArgs = unknown> {\n readonly nodeName: string;\n readonly args: TArgs;\n\n constructor(nodeName: string, args: TArgs) {\n this.nodeName = nodeName;\n this.args = args;\n }\n}\n\n/**\n * Convenience factory equivalent to `new Dispatch(nodeName, args)`.\n *\n * @stable\n */\nexport function dispatch<TArgs>(nodeName: string, args: TArgs): Dispatch<TArgs> {\n return new Dispatch(nodeName, args);\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,WAAb,MAAuC;CACrC,AAAS;CACT,AAAS;CAET,YAAY,UAAkB,MAAa;AACzC,OAAK,WAAW;AAChB,OAAK,OAAO;;;;;;;;AAShB,SAAgB,SAAgB,UAAkB,MAA8B;AAC9E,QAAO,IAAI,SAAS,UAAU,KAAK"}
1
+ {"version":3,"file":"dispatch.js","names":[],"sources":["../../src/channels/dispatch.ts"],"sourcesContent":["/**\n * Workflow dynamic-task primitive. A node returns one or more\n * `Dispatch(nodeName, args)` values to schedule additional tasks in the\n * next execution step.\n *\n * The class is intentionally tiny - the engine inspects only the public\n * `nodeName` and `args` fields. The shape is **Graphorin's own design**\n * (the name `Dispatch` is part of the public API).\n *\n * @stable\n */\nexport class Dispatch<TArgs = unknown> {\n /**\n * Cross-realm brand (workflow-13): the engine's structural fallback\n * requires this marker so a plain state object that happens to carry\n * `nodeName` + `args` keys is treated as channel WRITES, never\n * silently swallowed as a dispatch. A plain own property (not a\n * symbol) so it survives `structuredClone` across worker boundaries.\n */\n readonly __graphorinDispatch: true = true;\n readonly nodeName: string;\n readonly args: TArgs;\n\n constructor(nodeName: string, args: TArgs) {\n this.nodeName = nodeName;\n this.args = args;\n }\n}\n\n/**\n * Convenience factory equivalent to `new Dispatch(nodeName, args)`.\n *\n * @stable\n */\nexport function dispatch<TArgs>(nodeName: string, args: TArgs): Dispatch<TArgs> {\n return new Dispatch(nodeName, args);\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,WAAb,MAAuC;;;;;;;;CAQrC,AAAS,sBAA4B;CACrC,AAAS;CACT,AAAS;CAET,YAAY,UAAkB,MAAa;AACzC,OAAK,WAAW;AAChB,OAAK,OAAO;;;;;;;;AAShB,SAAgB,SAAgB,UAAkB,MAA8B;AAC9E,QAAO,IAAI,SAAS,UAAU,KAAK"}
@@ -0,0 +1,94 @@
1
+ //#region src/channels/durable.d.ts
2
+ /**
3
+ * Durable workflow primitives (D1) built on the `pause(...)` substrate:
4
+ *
5
+ * - {@link sleepUntil} / {@link sleepFor} - **durable timers**: the node
6
+ * suspends with a persisted wake-at timestamp; `workflow.tick(threadId)`
7
+ * (or any scheduler calling it) resumes the thread once the deadline
8
+ * passes. The timer survives process restarts because it lives in the
9
+ * checkpointed frontier, not in a `setTimeout`.
10
+ * - {@link awaitExternal} - **durable promises (awakeables)**: the node
11
+ * suspends under a caller-chosen name; an external system resolves it
12
+ * later via `workflow.resolveAwakeable(threadId, name, value)` and the
13
+ * node receives `value` as the call's return.
14
+ * - {@link requestApproval} - **first-class persisted approvals**: an
15
+ * awakeable specialized for human sign-off, resolved via
16
+ * `workflow.approve(threadId, name, decision)`.
17
+ *
18
+ * Each helper throws the same branded `PauseSignal` that `pause(...)`
19
+ * throws, carrying a reserved, JSON-safe pause value the engine
20
+ * recognises (`kind: 'graphorin.timer' | 'graphorin.awakeable' |
21
+ * 'graphorin.approval'`) to stamp `wakeAt` / `name` onto the persisted
22
+ * pause record. On resume the body re-executes from the top and the
23
+ * already-delivered values replay in order (WF-2), exactly like plain
24
+ * `pause(...)`.
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+ /** Reserved pause-value kind for durable timers (D1). */
29
+ declare const TIMER_PAUSE_KIND: "graphorin.timer";
30
+ /** Reserved pause-value kind for awakeables / durable promises (D1). */
31
+ declare const AWAKEABLE_PAUSE_KIND: "graphorin.awakeable";
32
+ /** Reserved pause-value kind for persisted approvals (D1). */
33
+ declare const APPROVAL_PAUSE_KIND: "graphorin.approval";
34
+ /** Pause value carried by a durable-timer suspension. */
35
+ interface TimerPauseValue {
36
+ readonly kind: typeof TIMER_PAUSE_KIND;
37
+ /** Epoch milliseconds at which the timer becomes due. */
38
+ readonly wakeAt: number;
39
+ }
40
+ /** Pause value carried by an awakeable suspension. */
41
+ interface AwakeablePauseValue {
42
+ readonly kind: typeof AWAKEABLE_PAUSE_KIND;
43
+ /** Caller-chosen name targeted by `workflow.resolveAwakeable(...)`. */
44
+ readonly name: string;
45
+ }
46
+ /** Pause value carried by a persisted-approval suspension. */
47
+ interface ApprovalPauseValue {
48
+ readonly kind: typeof APPROVAL_PAUSE_KIND;
49
+ /** Caller-chosen name targeted by `workflow.approve(...)`. */
50
+ readonly name: string;
51
+ /** Free-form payload surfaced to the approver (what is being approved). */
52
+ readonly payload?: unknown;
53
+ }
54
+ /** Type guard for {@link TimerPauseValue}. @stable */
55
+ declare function isTimerPauseValue(value: unknown): value is TimerPauseValue;
56
+ /** Type guard for {@link AwakeablePauseValue}. @stable */
57
+ declare function isAwakeablePauseValue(value: unknown): value is AwakeablePauseValue;
58
+ /** Type guard for {@link ApprovalPauseValue}. @stable */
59
+ declare function isApprovalPauseValue(value: unknown): value is ApprovalPauseValue;
60
+ /**
61
+ * Durably sleep until an absolute instant. Suspends the workflow thread
62
+ * with a persisted wake-at timestamp; `workflow.tick(threadId)` resumes
63
+ * it once due. Returns nothing on resume.
64
+ *
65
+ * @stable
66
+ */
67
+ declare function sleepUntil(at: string | number | Date): void;
68
+ /**
69
+ * Durably sleep for a relative duration (sugar over {@link sleepUntil}).
70
+ *
71
+ * @stable
72
+ */
73
+ declare function sleepFor(ms: number): void;
74
+ /**
75
+ * Suspend on a named durable promise. The thread stays suspended (and
76
+ * survives restarts) until an external caller resolves it via
77
+ * `workflow.resolveAwakeable(threadId, name, value)`; that `value` is
78
+ * returned here.
79
+ *
80
+ * @stable
81
+ */
82
+ declare function awaitExternal<TResume = unknown>(name: string): TResume;
83
+ /**
84
+ * Suspend on a named persisted approval. Resolved by
85
+ * `workflow.approve(threadId, name, decision)`; the decision is returned
86
+ * here. The optional payload is surfaced on the pending pause record so
87
+ * an approval UI can show what is being approved.
88
+ *
89
+ * @stable
90
+ */
91
+ declare function requestApproval<TDecision = unknown>(name: string, payload?: unknown): TDecision;
92
+ //#endregion
93
+ export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, ApprovalPauseValue, AwakeablePauseValue, TIMER_PAUSE_KIND, TimerPauseValue, awaitExternal, isApprovalPauseValue, isAwakeablePauseValue, isTimerPauseValue, requestApproval, sleepFor, sleepUntil };
94
+ //# sourceMappingURL=durable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable.d.ts","names":[],"sources":["../../src/channels/durable.ts"],"sourcesContent":[],"mappings":";;AA8BA;AAEA;AAEA;AAGA;AAOA;AAOA;AASA;AAUA;AAUA;AAgBA;AAeA;AAeA;AAeA;;;;;;;;;;;;;;cA/Ga;;cAEA;;cAEA;;UAGI,eAAA;wBACO;;;;;UAMP,mBAAA;wBACO;;;;;UAMP,kBAAA;wBACO;;;;;;;iBAQR,iBAAA,2BAA4C;;iBAU5C,qBAAA,2BAAgD;;iBAUhD,oBAAA,2BAA+C;;;;;;;;iBAgB/C,UAAA,uBAAiC;;;;;;iBAejC,QAAA;;;;;;;;;iBAeA,gDAAgD;;;;;;;;;iBAehD,uEAAuE"}
@@ -0,0 +1,106 @@
1
+ import { pause } from "./pause.js";
2
+
3
+ //#region src/channels/durable.ts
4
+ /**
5
+ * Durable workflow primitives (D1) built on the `pause(...)` substrate:
6
+ *
7
+ * - {@link sleepUntil} / {@link sleepFor} - **durable timers**: the node
8
+ * suspends with a persisted wake-at timestamp; `workflow.tick(threadId)`
9
+ * (or any scheduler calling it) resumes the thread once the deadline
10
+ * passes. The timer survives process restarts because it lives in the
11
+ * checkpointed frontier, not in a `setTimeout`.
12
+ * - {@link awaitExternal} - **durable promises (awakeables)**: the node
13
+ * suspends under a caller-chosen name; an external system resolves it
14
+ * later via `workflow.resolveAwakeable(threadId, name, value)` and the
15
+ * node receives `value` as the call's return.
16
+ * - {@link requestApproval} - **first-class persisted approvals**: an
17
+ * awakeable specialized for human sign-off, resolved via
18
+ * `workflow.approve(threadId, name, decision)`.
19
+ *
20
+ * Each helper throws the same branded `PauseSignal` that `pause(...)`
21
+ * throws, carrying a reserved, JSON-safe pause value the engine
22
+ * recognises (`kind: 'graphorin.timer' | 'graphorin.awakeable' |
23
+ * 'graphorin.approval'`) to stamp `wakeAt` / `name` onto the persisted
24
+ * pause record. On resume the body re-executes from the top and the
25
+ * already-delivered values replay in order (WF-2), exactly like plain
26
+ * `pause(...)`.
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ /** Reserved pause-value kind for durable timers (D1). */
31
+ const TIMER_PAUSE_KIND = "graphorin.timer";
32
+ /** Reserved pause-value kind for awakeables / durable promises (D1). */
33
+ const AWAKEABLE_PAUSE_KIND = "graphorin.awakeable";
34
+ /** Reserved pause-value kind for persisted approvals (D1). */
35
+ const APPROVAL_PAUSE_KIND = "graphorin.approval";
36
+ /** Type guard for {@link TimerPauseValue}. @stable */
37
+ function isTimerPauseValue(value) {
38
+ return typeof value === "object" && value !== null && value.kind === TIMER_PAUSE_KIND && typeof value.wakeAt === "number";
39
+ }
40
+ /** Type guard for {@link AwakeablePauseValue}. @stable */
41
+ function isAwakeablePauseValue(value) {
42
+ return typeof value === "object" && value !== null && value.kind === AWAKEABLE_PAUSE_KIND && typeof value.name === "string";
43
+ }
44
+ /** Type guard for {@link ApprovalPauseValue}. @stable */
45
+ function isApprovalPauseValue(value) {
46
+ return typeof value === "object" && value !== null && value.kind === APPROVAL_PAUSE_KIND && typeof value.name === "string";
47
+ }
48
+ /**
49
+ * Durably sleep until an absolute instant. Suspends the workflow thread
50
+ * with a persisted wake-at timestamp; `workflow.tick(threadId)` resumes
51
+ * it once due. Returns nothing on resume.
52
+ *
53
+ * @stable
54
+ */
55
+ function sleepUntil(at) {
56
+ const wakeAt = typeof at === "number" ? at : at instanceof Date ? at.getTime() : Date.parse(at);
57
+ if (!Number.isFinite(wakeAt)) throw new TypeError(`[graphorin/core] sleepUntil(...) got an unparseable instant: ${String(at)}`);
58
+ pause({
59
+ kind: TIMER_PAUSE_KIND,
60
+ wakeAt
61
+ });
62
+ }
63
+ /**
64
+ * Durably sleep for a relative duration (sugar over {@link sleepUntil}).
65
+ *
66
+ * @stable
67
+ */
68
+ function sleepFor(ms) {
69
+ if (!Number.isFinite(ms) || ms < 0) throw new TypeError(`[graphorin/core] sleepFor(...) needs a non-negative duration, got ${ms}`);
70
+ sleepUntil(Date.now() + ms);
71
+ }
72
+ /**
73
+ * Suspend on a named durable promise. The thread stays suspended (and
74
+ * survives restarts) until an external caller resolves it via
75
+ * `workflow.resolveAwakeable(threadId, name, value)`; that `value` is
76
+ * returned here.
77
+ *
78
+ * @stable
79
+ */
80
+ function awaitExternal(name) {
81
+ if (typeof name !== "string" || name.length === 0) throw new TypeError("[graphorin/core] awaitExternal(name) needs a non-empty name");
82
+ return pause({
83
+ kind: AWAKEABLE_PAUSE_KIND,
84
+ name
85
+ });
86
+ }
87
+ /**
88
+ * Suspend on a named persisted approval. Resolved by
89
+ * `workflow.approve(threadId, name, decision)`; the decision is returned
90
+ * here. The optional payload is surfaced on the pending pause record so
91
+ * an approval UI can show what is being approved.
92
+ *
93
+ * @stable
94
+ */
95
+ function requestApproval(name, payload) {
96
+ if (typeof name !== "string" || name.length === 0) throw new TypeError("[graphorin/core] requestApproval(name) needs a non-empty name");
97
+ return pause({
98
+ kind: APPROVAL_PAUSE_KIND,
99
+ name,
100
+ ...payload !== void 0 ? { payload } : {}
101
+ });
102
+ }
103
+
104
+ //#endregion
105
+ export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, TIMER_PAUSE_KIND, awaitExternal, isApprovalPauseValue, isAwakeablePauseValue, isTimerPauseValue, requestApproval, sleepFor, sleepUntil };
106
+ //# sourceMappingURL=durable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable.js","names":[],"sources":["../../src/channels/durable.ts"],"sourcesContent":["/**\n * Durable workflow primitives (D1) built on the `pause(...)` substrate:\n *\n * - {@link sleepUntil} / {@link sleepFor} - **durable timers**: the node\n * suspends with a persisted wake-at timestamp; `workflow.tick(threadId)`\n * (or any scheduler calling it) resumes the thread once the deadline\n * passes. The timer survives process restarts because it lives in the\n * checkpointed frontier, not in a `setTimeout`.\n * - {@link awaitExternal} - **durable promises (awakeables)**: the node\n * suspends under a caller-chosen name; an external system resolves it\n * later via `workflow.resolveAwakeable(threadId, name, value)` and the\n * node receives `value` as the call's return.\n * - {@link requestApproval} - **first-class persisted approvals**: an\n * awakeable specialized for human sign-off, resolved via\n * `workflow.approve(threadId, name, decision)`.\n *\n * Each helper throws the same branded `PauseSignal` that `pause(...)`\n * throws, carrying a reserved, JSON-safe pause value the engine\n * recognises (`kind: 'graphorin.timer' | 'graphorin.awakeable' |\n * 'graphorin.approval'`) to stamp `wakeAt` / `name` onto the persisted\n * pause record. On resume the body re-executes from the top and the\n * already-delivered values replay in order (WF-2), exactly like plain\n * `pause(...)`.\n *\n * @packageDocumentation\n */\n\nimport { pause } from './pause.js';\n\n/** Reserved pause-value kind for durable timers (D1). */\nexport const TIMER_PAUSE_KIND = 'graphorin.timer' as const;\n/** Reserved pause-value kind for awakeables / durable promises (D1). */\nexport const AWAKEABLE_PAUSE_KIND = 'graphorin.awakeable' as const;\n/** Reserved pause-value kind for persisted approvals (D1). */\nexport const APPROVAL_PAUSE_KIND = 'graphorin.approval' as const;\n\n/** Pause value carried by a durable-timer suspension. */\nexport interface TimerPauseValue {\n readonly kind: typeof TIMER_PAUSE_KIND;\n /** Epoch milliseconds at which the timer becomes due. */\n readonly wakeAt: number;\n}\n\n/** Pause value carried by an awakeable suspension. */\nexport interface AwakeablePauseValue {\n readonly kind: typeof AWAKEABLE_PAUSE_KIND;\n /** Caller-chosen name targeted by `workflow.resolveAwakeable(...)`. */\n readonly name: string;\n}\n\n/** Pause value carried by a persisted-approval suspension. */\nexport interface ApprovalPauseValue {\n readonly kind: typeof APPROVAL_PAUSE_KIND;\n /** Caller-chosen name targeted by `workflow.approve(...)`. */\n readonly name: string;\n /** Free-form payload surfaced to the approver (what is being approved). */\n readonly payload?: unknown;\n}\n\n/** Type guard for {@link TimerPauseValue}. @stable */\nexport function isTimerPauseValue(value: unknown): value is TimerPauseValue {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === TIMER_PAUSE_KIND &&\n typeof (value as { wakeAt?: unknown }).wakeAt === 'number'\n );\n}\n\n/** Type guard for {@link AwakeablePauseValue}. @stable */\nexport function isAwakeablePauseValue(value: unknown): value is AwakeablePauseValue {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === AWAKEABLE_PAUSE_KIND &&\n typeof (value as { name?: unknown }).name === 'string'\n );\n}\n\n/** Type guard for {@link ApprovalPauseValue}. @stable */\nexport function isApprovalPauseValue(value: unknown): value is ApprovalPauseValue {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === APPROVAL_PAUSE_KIND &&\n typeof (value as { name?: unknown }).name === 'string'\n );\n}\n\n/**\n * Durably sleep until an absolute instant. Suspends the workflow thread\n * with a persisted wake-at timestamp; `workflow.tick(threadId)` resumes\n * it once due. Returns nothing on resume.\n *\n * @stable\n */\nexport function sleepUntil(at: string | number | Date): void {\n const wakeAt = typeof at === 'number' ? at : at instanceof Date ? at.getTime() : Date.parse(at);\n if (!Number.isFinite(wakeAt)) {\n throw new TypeError(\n `[graphorin/core] sleepUntil(...) got an unparseable instant: ${String(at)}`,\n );\n }\n pause<TimerPauseValue, unknown>({ kind: TIMER_PAUSE_KIND, wakeAt });\n}\n\n/**\n * Durably sleep for a relative duration (sugar over {@link sleepUntil}).\n *\n * @stable\n */\nexport function sleepFor(ms: number): void {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new TypeError(`[graphorin/core] sleepFor(...) needs a non-negative duration, got ${ms}`);\n }\n sleepUntil(Date.now() + ms);\n}\n\n/**\n * Suspend on a named durable promise. The thread stays suspended (and\n * survives restarts) until an external caller resolves it via\n * `workflow.resolveAwakeable(threadId, name, value)`; that `value` is\n * returned here.\n *\n * @stable\n */\nexport function awaitExternal<TResume = unknown>(name: string): TResume {\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError('[graphorin/core] awaitExternal(name) needs a non-empty name');\n }\n return pause<AwakeablePauseValue, TResume>({ kind: AWAKEABLE_PAUSE_KIND, name });\n}\n\n/**\n * Suspend on a named persisted approval. Resolved by\n * `workflow.approve(threadId, name, decision)`; the decision is returned\n * here. The optional payload is surfaced on the pending pause record so\n * an approval UI can show what is being approved.\n *\n * @stable\n */\nexport function requestApproval<TDecision = unknown>(name: string, payload?: unknown): TDecision {\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError('[graphorin/core] requestApproval(name) needs a non-empty name');\n }\n return pause<ApprovalPauseValue, TDecision>({\n kind: APPROVAL_PAUSE_KIND,\n name,\n ...(payload !== undefined ? { payload } : {}),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,mBAAmB;;AAEhC,MAAa,uBAAuB;;AAEpC,MAAa,sBAAsB;;AA0BnC,SAAgB,kBAAkB,OAA0C;AAC1E,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,oBACvC,OAAQ,MAA+B,WAAW;;;AAKtD,SAAgB,sBAAsB,OAA8C;AAClF,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,wBACvC,OAAQ,MAA6B,SAAS;;;AAKlD,SAAgB,qBAAqB,OAA6C;AAChF,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,uBACvC,OAAQ,MAA6B,SAAS;;;;;;;;;AAWlD,SAAgB,WAAW,IAAkC;CAC3D,MAAM,SAAS,OAAO,OAAO,WAAW,KAAK,cAAc,OAAO,GAAG,SAAS,GAAG,KAAK,MAAM,GAAG;AAC/F,KAAI,CAAC,OAAO,SAAS,OAAO,CAC1B,OAAM,IAAI,UACR,gEAAgE,OAAO,GAAG,GAC3E;AAEH,OAAgC;EAAE,MAAM;EAAkB;EAAQ,CAAC;;;;;;;AAQrE,SAAgB,SAAS,IAAkB;AACzC,KAAI,CAAC,OAAO,SAAS,GAAG,IAAI,KAAK,EAC/B,OAAM,IAAI,UAAU,qEAAqE,KAAK;AAEhG,YAAW,KAAK,KAAK,GAAG,GAAG;;;;;;;;;;AAW7B,SAAgB,cAAiC,MAAuB;AACtE,KAAI,OAAO,SAAS,YAAY,KAAK,WAAW,EAC9C,OAAM,IAAI,UAAU,8DAA8D;AAEpF,QAAO,MAAoC;EAAE,MAAM;EAAsB;EAAM,CAAC;;;;;;;;;;AAWlF,SAAgB,gBAAqC,MAAc,SAA8B;AAC/F,KAAI,OAAO,SAAS,YAAY,KAAK,WAAW,EAC9C,OAAM,IAAI,UAAU,gEAAgE;AAEtF,QAAO,MAAqC;EAC1C,MAAM;EACN;EACA,GAAI,YAAY,SAAY,EAAE,SAAS,GAAG,EAAE;EAC7C,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { AnyValue, Barrier, Channel, ChannelKind, Ephemeral, LatestValue, ListAggregate, Reducer, Stream, anyValue, barrier, ephemeral, latestValue, listAggregate, reducer, stream } from "./channels.js";
2
2
  import { Directive, DirectiveOptions } from "./directive.js";
3
3
  import { Dispatch, dispatch } from "./dispatch.js";
4
+ import { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, ApprovalPauseValue, AwakeablePauseValue, TIMER_PAUSE_KIND, TimerPauseValue, awaitExternal, isApprovalPauseValue, isAwakeablePauseValue, isTimerPauseValue, requestApproval, sleepFor, sleepUntil } from "./durable.js";
4
5
  import { PAUSE_SIGNAL_BRAND, PauseResumeScope, PauseSignal, isPauseSignal, pause, runWithPauseResume } from "./pause.js";
5
- export { type AnyValue, type Barrier, type Channel, type ChannelKind, Directive, type DirectiveOptions, Dispatch, type Ephemeral, type LatestValue, type ListAggregate, PAUSE_SIGNAL_BRAND, type PauseResumeScope, PauseSignal, type Reducer, type Stream, anyValue, barrier, dispatch, ephemeral, isPauseSignal, latestValue, listAggregate, pause, reducer, runWithPauseResume, stream };
6
+ export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, type AnyValue, type ApprovalPauseValue, type AwakeablePauseValue, type Barrier, type Channel, type ChannelKind, Directive, type DirectiveOptions, Dispatch, type Ephemeral, type LatestValue, type ListAggregate, PAUSE_SIGNAL_BRAND, type PauseResumeScope, PauseSignal, type Reducer, type Stream, TIMER_PAUSE_KIND, type TimerPauseValue, anyValue, awaitExternal, barrier, dispatch, ephemeral, isApprovalPauseValue, isAwakeablePauseValue, isPauseSignal, isTimerPauseValue, latestValue, listAggregate, pause, reducer, requestApproval, runWithPauseResume, sleepFor, sleepUntil, stream };
@@ -2,5 +2,6 @@ import { anyValue, barrier, ephemeral, latestValue, listAggregate, reducer, stre
2
2
  import { Directive } from "./directive.js";
3
3
  import { Dispatch, dispatch } from "./dispatch.js";
4
4
  import { PAUSE_SIGNAL_BRAND, PauseSignal, isPauseSignal, pause, runWithPauseResume } from "./pause.js";
5
+ import { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, TIMER_PAUSE_KIND, awaitExternal, isApprovalPauseValue, isAwakeablePauseValue, isTimerPauseValue, requestApproval, sleepFor, sleepUntil } from "./durable.js";
5
6
 
6
- export { Directive, Dispatch, PAUSE_SIGNAL_BRAND, PauseSignal, anyValue, barrier, dispatch, ephemeral, isPauseSignal, latestValue, listAggregate, pause, reducer, runWithPauseResume, stream };
7
+ export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, Directive, Dispatch, PAUSE_SIGNAL_BRAND, PauseSignal, TIMER_PAUSE_KIND, anyValue, awaitExternal, barrier, dispatch, ephemeral, isApprovalPauseValue, isAwakeablePauseValue, isPauseSignal, isTimerPauseValue, latestValue, listAggregate, pause, reducer, requestApproval, runWithPauseResume, sleepFor, sleepUntil, stream };