@graphorin/core 0.6.0 → 0.7.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.
- package/CHANGELOG.md +88 -0
- package/README.md +14 -8
- package/dist/channels/channels.d.ts.map +1 -1
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2 -2
- package/dist/channels/pause.d.ts +47 -2
- package/dist/channels/pause.d.ts.map +1 -1
- package/dist/channels/pause.js +62 -2
- package/dist/channels/pause.js.map +1 -1
- package/dist/contracts/checkpoint-store.d.ts +97 -1
- package/dist/contracts/checkpoint-store.d.ts.map +1 -1
- package/dist/contracts/checkpoint-store.js.map +1 -1
- package/dist/contracts/index.d.ts +5 -5
- package/dist/contracts/index.js +2 -1
- package/dist/contracts/memory-store.d.ts +59 -2
- package/dist/contracts/memory-store.d.ts.map +1 -1
- package/dist/contracts/provider.d.ts +20 -6
- package/dist/contracts/provider.d.ts.map +1 -1
- package/dist/contracts/session-store.d.ts +10 -2
- package/dist/contracts/session-store.d.ts.map +1 -1
- package/dist/contracts/tool.d.ts +75 -1
- package/dist/contracts/tool.d.ts.map +1 -1
- package/dist/contracts/tool.js +57 -0
- package/dist/contracts/tool.js.map +1 -0
- package/dist/contracts/tracer.d.ts +53 -5
- package/dist/contracts/tracer.d.ts.map +1 -1
- package/dist/contracts/tracer.js.map +1 -1
- package/dist/index.d.ts +14 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/types/agent-event-wire.d.ts +74 -0
- package/dist/types/agent-event-wire.d.ts.map +1 -0
- package/dist/types/agent-event-wire.js +130 -0
- package/dist/types/agent-event-wire.js.map +1 -0
- package/dist/types/agent-event.d.ts +48 -6
- package/dist/types/agent-event.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +2 -1
- package/dist/types/memory.d.ts +11 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/run.d.ts +86 -4
- package/dist/types/run.d.ts.map +1 -1
- package/dist/types/run.js.map +1 -1
- package/dist/types/tool.d.ts +10 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/usage.d.ts +11 -1
- package/dist/types/usage.d.ts.map +1 -1
- package/dist/types/usage.js.map +1 -1
- package/dist/utils/binary-json.d.ts +165 -0
- package/dist/utils/binary-json.d.ts.map +1 -0
- package/dist/utils/binary-json.js +240 -0
- package/dist/utils/binary-json.js.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/validation.d.ts +10 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +1 -1
- package/dist/utils/validation.js.map +1 -1
- package/package.json +9 -7
- package/src/channels/channels.ts +206 -0
- package/src/channels/directive.ts +41 -0
- package/src/channels/dispatch.ts +37 -0
- package/src/channels/durable.ts +151 -0
- package/src/channels/index.ts +62 -0
- package/src/channels/pause.ts +216 -0
- package/src/contracts/auth-token-store.ts +42 -0
- package/src/contracts/checkpoint-store.ts +256 -0
- package/src/contracts/embedder.ts +42 -0
- package/src/contracts/eval-scorer.ts +44 -0
- package/src/contracts/index.ts +112 -0
- package/src/contracts/local-provider-trust.ts +33 -0
- package/src/contracts/logger.ts +61 -0
- package/src/contracts/memory-store.ts +187 -0
- package/src/contracts/oauth-server-store.ts +78 -0
- package/src/contracts/preferred-model.ts +56 -0
- package/src/contracts/provider.ts +316 -0
- package/src/contracts/reasoning-retention.ts +52 -0
- package/src/contracts/redaction-validator.ts +56 -0
- package/src/contracts/sandbox.ts +70 -0
- package/src/contracts/secret-ref.ts +22 -0
- package/src/contracts/secret-value.ts +117 -0
- package/src/contracts/secrets-store.ts +90 -0
- package/src/contracts/session-store.ts +163 -0
- package/src/contracts/token-counter.ts +23 -0
- package/src/contracts/tool.ts +397 -0
- package/src/contracts/tracer.ts +219 -0
- package/src/contracts/trigger-store.ts +40 -0
- package/src/index.ts +23 -0
- package/src/types/agent-event-wire.ts +193 -0
- package/src/types/agent-event.ts +579 -0
- package/src/types/handoff.ts +111 -0
- package/src/types/index.ts +148 -0
- package/src/types/memory.ts +427 -0
- package/src/types/message.ts +174 -0
- package/src/types/run.ts +312 -0
- package/src/types/sensitivity.ts +35 -0
- package/src/types/session-scope.ts +18 -0
- package/src/types/stop-condition.ts +108 -0
- package/src/types/tool-call.ts +24 -0
- package/src/types/tool.ts +324 -0
- package/src/types/usage.ts +120 -0
- package/src/types/workflow-event.ts +132 -0
- package/src/utils/assert-never.ts +24 -0
- package/src/utils/async-context.ts +55 -0
- package/src/utils/binary-json.ts +425 -0
- package/src/utils/hash.ts +122 -0
- package/src/utils/index.ts +57 -0
- package/src/utils/streams.ts +233 -0
- package/src/utils/validation.ts +82 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binary-json.js","names":["B64_LOOKUP: ReadonlyMap<string, number>"],"sources":["../../src/utils/binary-json.ts"],"sourcesContent":["/**\n * JSON-safe projection of binary-bearing message content (W-004).\n *\n * `Message` and `ToolResult.contentParts` carry `Uint8Array | URL`\n * payloads that a naive `JSON.stringify` silently corrupts: a\n * `Uint8Array` turns into an object with numeric keys and a `URL`\n * into `{}`. The wire types below replace those fields with explicit\n * {@link EncodedBytes} / {@link EncodedUrl} envelopes so a run state\n * (or any message transcript) survives `JSON.parse(JSON.stringify(x))`\n * byte-for-byte.\n *\n * The codec is runtime-neutral by design: base64 is hand-rolled (no\n * `Buffer`, no `btoa`) so the module works in browsers, workers and\n * edge runtimes exactly as it does under Node.\n *\n * @packageDocumentation\n */\n\nimport type {\n AssistantMessage,\n AudioContent,\n FileContent,\n ImageContent,\n Message,\n MessageContent,\n SystemMessage,\n ToolMessage,\n UserMessage,\n} from '../types/message.js';\nimport type { PendingSubRun, RunState, RunStep } from '../types/run.js';\nimport type { CompletedToolCall, ToolError, ToolResult } from '../types/tool.js';\n\n/**\n * Base64-encoded binary payload as it appears on the wire.\n *\n * @stable\n */\nexport interface EncodedBytes {\n readonly enc: 'base64';\n readonly data: string;\n}\n\n/**\n * URL reference as it appears on the wire (`URL` instances do not\n * survive `JSON.stringify`).\n *\n * @stable\n */\nexport interface EncodedUrl {\n readonly enc: 'url';\n readonly href: string;\n}\n\n/**\n * Wire form of a `Uint8Array | URL` binary field.\n *\n * @stable\n */\nexport type EncodedBinary = EncodedBytes | EncodedUrl;\n\n/** Wire twin of {@link ImageContent}. @stable */\nexport interface WireImageContent extends Omit<ImageContent, 'image'> {\n readonly image: EncodedBinary;\n}\n\n/** Wire twin of {@link AudioContent}. @stable */\nexport interface WireAudioContent extends Omit<AudioContent, 'audio'> {\n readonly audio: EncodedBinary;\n}\n\n/** Wire twin of {@link FileContent}. @stable */\nexport interface WireFileContent extends Omit<FileContent, 'file'> {\n readonly file: EncodedBinary;\n}\n\n/**\n * JSON-safe twin of {@link MessageContent}: binary-bearing variants\n * carry {@link EncodedBinary} envelopes, text/reasoning variants pass\n * through untouched.\n *\n * @stable\n */\nexport type WireMessageContent =\n | Exclude<MessageContent, ImageContent | AudioContent | FileContent>\n | WireImageContent\n | WireAudioContent\n | WireFileContent;\n\n/** Wire twin of {@link UserMessage}. @stable */\nexport interface WireUserMessage extends Omit<UserMessage, 'content'> {\n readonly content: string | readonly WireMessageContent[];\n}\n\n/** Wire twin of {@link AssistantMessage}. @stable */\nexport interface WireAssistantMessage extends Omit<AssistantMessage, 'content'> {\n readonly content: string | readonly WireMessageContent[];\n}\n\n/** Wire twin of {@link ToolMessage}. @stable */\nexport interface WireToolMessage extends Omit<ToolMessage, 'content'> {\n readonly content: string | readonly WireMessageContent[];\n}\n\n/**\n * JSON-safe twin of {@link Message}. System messages are plain strings\n * and pass through unchanged.\n *\n * @stable\n */\nexport type WireMessage = SystemMessage | WireUserMessage | WireAssistantMessage | WireToolMessage;\n\n/** Wire twin of {@link ToolResult}: `contentParts` are encoded. @stable */\nexport type WireToolResult<TOutput = unknown> = Omit<ToolResult<TOutput>, 'contentParts'> & {\n readonly contentParts?: readonly WireMessageContent[];\n};\n\n/** Wire twin of `ToolOutcome`. @stable */\nexport type WireToolOutcome<TOutput = unknown> = WireToolResult<TOutput> | ToolError;\n\n/** Wire twin of {@link CompletedToolCall}. @stable */\nexport type WireCompletedToolCall<TOutput = unknown> = Omit<\n CompletedToolCall<TOutput>,\n 'outcome'\n> & {\n readonly outcome: WireToolOutcome<TOutput>;\n};\n\n/** Wire twin of {@link RunStep}. @stable */\nexport type WireRunStep = Omit<RunStep, 'toolCalls'> & {\n readonly toolCalls: readonly WireCompletedToolCall[];\n};\n\n/** Wire twin of {@link PendingSubRun}: the child state recurses. @stable */\nexport type WirePendingSubRun = Omit<PendingSubRun, 'state'> & {\n readonly state: WireRunState;\n};\n\n/**\n * JSON-safe twin of {@link RunState}: `messages`, every\n * `steps[].toolCalls[].outcome.contentParts`, and each parked\n * `pendingSubRuns[].state` (recursively, W-001) are projected through\n * the binary codec. Everything else is structurally identical.\n *\n * `pendingApprovals[].args` and `ToolResult.output` are model-produced\n * JSON and are assumed JSON-safe already - the projection does not\n * walk them.\n *\n * @stable\n */\nexport type WireRunState = Omit<RunState, 'messages' | 'steps' | 'pendingSubRuns'> & {\n readonly messages: readonly WireMessage[];\n readonly steps: readonly WireRunStep[];\n readonly pendingSubRuns?: readonly WirePendingSubRun[];\n};\n\nconst B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\nconst B64_LOOKUP: ReadonlyMap<string, number> = new Map(\n [...B64_ALPHABET].map((ch, i) => [ch, i] as const),\n);\n\n/**\n * Encode bytes as standard (padded) base64 without relying on `Buffer`\n * or `btoa`.\n *\n * @stable\n */\nexport function bytesToBase64(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i] as number;\n const b1 = i + 1 < bytes.length ? (bytes[i + 1] as number) : undefined;\n const b2 = i + 2 < bytes.length ? (bytes[i + 2] as number) : undefined;\n out += B64_ALPHABET[b0 >> 2];\n out += B64_ALPHABET[((b0 & 0x03) << 4) | ((b1 ?? 0) >> 4)];\n out += b1 === undefined ? '=' : B64_ALPHABET[((b1 & 0x0f) << 2) | ((b2 ?? 0) >> 6)];\n out += b2 === undefined ? '=' : B64_ALPHABET[b2 & 0x3f];\n }\n return out;\n}\n\n/**\n * Decode standard base64 (padding optional). Throws on characters\n * outside the base64 alphabet.\n *\n * @stable\n */\nexport function base64ToBytes(data: string): Uint8Array {\n // Strip trailing padding with a loop: /=+$/ backtracks polynomially\n // on '='-heavy adversarial input (CodeQL js/polynomial-redos).\n let end = data.length;\n while (end > 0 && data.charCodeAt(end - 1) === 0x3d /* '=' */) end -= 1;\n const stripped = data.slice(0, end);\n const out = new Uint8Array(Math.floor((stripped.length * 6) / 8));\n let offset = 0;\n let buffer = 0;\n let bits = 0;\n for (const ch of stripped) {\n const value = B64_LOOKUP.get(ch);\n if (value === undefined) {\n throw new Error(`base64ToBytes: invalid base64 character ${JSON.stringify(ch)}`);\n }\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[offset] = (buffer >> bits) & 0xff;\n offset += 1;\n }\n }\n return out;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction encodeBinary(value: Uint8Array | URL | EncodedBinary): EncodedBinary {\n if (value instanceof Uint8Array) return { enc: 'base64', data: bytesToBase64(value) };\n if (value instanceof URL) return { enc: 'url', href: value.href };\n // Already wire-encoded (idempotent re-projection).\n return value;\n}\n\n/**\n * Repair the exact corruption `JSON.stringify(Uint8Array)` produces: an\n * object whose keys are the dense indices `0..n-1` with byte values.\n * Applied ONLY to known binary fields of legacy (schema <= 1.1)\n * payloads; anything else is returned as-is.\n */\nfunction repairNumericKeyBytes(value: Record<string, unknown>): Uint8Array | undefined {\n const keys = Object.keys(value);\n if (keys.length === 0) return undefined;\n const out = new Uint8Array(keys.length);\n for (let i = 0; i < keys.length; i += 1) {\n const raw = value[String(i)];\n if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 0 || raw > 255) {\n return undefined;\n }\n out[i] = raw;\n }\n return out;\n}\n\nfunction decodeBinary(value: unknown): Uint8Array | URL | unknown {\n if (value instanceof Uint8Array || value instanceof URL) return value;\n if (!isRecord(value)) return value;\n if (value.enc === 'base64' && typeof value.data === 'string') {\n return base64ToBytes(value.data);\n }\n if (value.enc === 'url' && typeof value.href === 'string') {\n return new URL(value.href);\n }\n // Legacy repair path: schema 1.0/1.1 checkpoints stringified the raw\n // Uint8Array into a numeric-key object. A corrupted URL serialized to\n // `{}` and is unrecoverable - it stays as-is.\n const repaired = repairNumericKeyBytes(value);\n return repaired ?? value;\n}\n\nfunction encodeContentPart(part: MessageContent | WireMessageContent): WireMessageContent {\n switch (part.type) {\n case 'image':\n return { ...part, image: encodeBinary(part.image) };\n case 'audio':\n return { ...part, audio: encodeBinary(part.audio) };\n case 'file':\n return { ...part, file: encodeBinary(part.file) };\n default:\n return part;\n }\n}\n\nfunction decodeContentPart(part: WireMessageContent | MessageContent): MessageContent {\n switch (part.type) {\n case 'image':\n return { ...part, image: decodeBinary(part.image) as Uint8Array | URL };\n case 'audio':\n return { ...part, audio: decodeBinary(part.audio) as Uint8Array | URL };\n case 'file':\n return { ...part, file: decodeBinary(part.file) as Uint8Array | URL };\n default:\n return part;\n }\n}\n\n/**\n * Project multimodal content parts into their JSON-safe wire form.\n * Text and reasoning parts pass through untouched.\n *\n * @stable\n */\nexport function toJsonSafeContentParts(\n parts: readonly MessageContent[],\n): readonly WireMessageContent[] {\n return parts.map(encodeContentPart);\n}\n\n/**\n * Inverse of {@link toJsonSafeContentParts}. Accepts legacy corrupted\n * payloads (numeric-key byte objects) and repairs them best-effort.\n *\n * @stable\n */\nexport function fromJsonSafeContentParts(\n parts: readonly WireMessageContent[],\n): readonly MessageContent[] {\n return parts.map(decodeContentPart);\n}\n\n/**\n * Project a {@link Message} into its JSON-safe wire form. Idempotent:\n * projecting an already-wire message returns an equivalent value.\n *\n * @stable\n */\nexport function toJsonSafeMessage(message: Message | WireMessage): WireMessage {\n if (message.role === 'system' || typeof message.content === 'string') {\n return message as WireMessage;\n }\n return { ...message, content: message.content.map(encodeContentPart) } as WireMessage;\n}\n\n/**\n * Inverse of {@link toJsonSafeMessage}.\n *\n * @stable\n */\nexport function fromJsonSafeMessage(message: WireMessage | Message): Message {\n if (message.role === 'system' || typeof message.content === 'string') {\n return message as Message;\n }\n return { ...message, content: message.content.map(decodeContentPart) } as Message;\n}\n\nfunction encodeOutcome(outcome: CompletedToolCall['outcome']): WireToolOutcome {\n // Defensive: tolerate loose historical/fixture shapes where the\n // outcome is missing or not an object.\n if (isRecord(outcome) && Array.isArray(outcome.contentParts)) {\n return {\n ...outcome,\n contentParts: toJsonSafeContentParts(outcome.contentParts as readonly MessageContent[]),\n } as WireToolOutcome;\n }\n return outcome as WireToolOutcome;\n}\n\nfunction decodeOutcome(outcome: WireToolOutcome): CompletedToolCall['outcome'] {\n if (isRecord(outcome) && Array.isArray(outcome.contentParts)) {\n return {\n ...outcome,\n contentParts: fromJsonSafeContentParts(outcome.contentParts as readonly WireMessageContent[]),\n } as CompletedToolCall['outcome'];\n }\n return outcome as CompletedToolCall['outcome'];\n}\n\nfunction encodeStep(step: RunStep | WireRunStep): WireRunStep {\n if (step.toolCalls.length === 0) return step as WireRunStep;\n return {\n ...step,\n toolCalls: step.toolCalls.map((tc) => ({\n ...tc,\n outcome: encodeOutcome(tc.outcome as CompletedToolCall['outcome']),\n })),\n };\n}\n\nfunction decodeStep(step: WireRunStep): RunStep {\n if (step.toolCalls.length === 0) return step as unknown as RunStep;\n return {\n ...step,\n toolCalls: step.toolCalls.map((tc) => ({ ...tc, outcome: decodeOutcome(tc.outcome) })),\n } as unknown as RunStep;\n}\n\n/**\n * Project a full {@link RunState} into its JSON-safe {@link WireRunState}\n * twin: `messages` and `steps[].toolCalls[].outcome.contentParts` go\n * through the binary codec, everything else is copied structurally.\n *\n * @stable\n */\nexport function toJsonSafeRunState(state: RunState | WireRunState): WireRunState {\n const subRuns = state.pendingSubRuns;\n return {\n ...state,\n messages: state.messages.map(toJsonSafeMessage),\n steps: state.steps.map(encodeStep),\n // W-001: parked child states carry binary-bearing messages of their\n // own - project them recursively (idempotent, like every codec leg).\n ...(subRuns !== undefined && subRuns.length > 0\n ? {\n pendingSubRuns: subRuns.map((sub) => ({\n ...sub,\n state: toJsonSafeRunState(sub.state),\n })),\n }\n : {}),\n } as WireRunState;\n}\n\n/**\n * Inverse of {@link toJsonSafeRunState}. Best-effort: legacy corrupted\n * binary fields (numeric-key byte objects from schema <= 1.1 payloads)\n * are repaired to `Uint8Array`; unrecoverable shapes are left as-is.\n *\n * @stable\n */\nexport function fromJsonSafeRunState(state: WireRunState): RunState {\n const subRuns = state.pendingSubRuns;\n return {\n ...state,\n messages: state.messages.map(fromJsonSafeMessage),\n steps: state.steps.map(decodeStep),\n ...(subRuns !== undefined && subRuns.length > 0\n ? {\n pendingSubRuns: subRuns.map((sub) => ({\n ...sub,\n state: fromJsonSafeRunState(sub.state),\n })),\n }\n : {}),\n } as RunState;\n}\n"],"mappings":"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"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ValidationResult, ZodLikeError, ZodLikeSafeParseResult, ZodLikeSchema, validate, validateOrThrow } from "./validation.js";
|
|
2
|
+
import { EncodedBinary, EncodedBytes, EncodedUrl, WireAssistantMessage, WireAudioContent, WireCompletedToolCall, WireFileContent, WireImageContent, WireMessage, WireMessageContent, WireRunState, WireRunStep, WireToolMessage, WireToolOutcome, WireToolResult, WireUserMessage, base64ToBytes, bytesToBase64, fromJsonSafeContentParts, fromJsonSafeMessage, fromJsonSafeRunState, toJsonSafeContentParts, toJsonSafeMessage, toJsonSafeRunState } from "./binary-json.js";
|
|
2
3
|
import { assertNever } from "./assert-never.js";
|
|
3
4
|
import { AsyncContext, createAsyncContext } from "./async-context.js";
|
|
4
5
|
import { md5, xxhash } from "./hash.js";
|
|
5
6
|
import { collect, filter, mapStream, merge, take, takeWhile, withSignal } from "./streams.js";
|
|
6
|
-
export { type AsyncContext, type ValidationResult, type ZodLikeError, type ZodLikeSafeParseResult, type ZodLikeSchema, assertNever, collect, createAsyncContext, filter, mapStream, md5, merge, take, takeWhile, validate, validateOrThrow, withSignal, xxhash };
|
|
7
|
+
export { type AsyncContext, type EncodedBinary, type EncodedBytes, type EncodedUrl, type ValidationResult, type WireAssistantMessage, type WireAudioContent, type WireCompletedToolCall, type WireFileContent, type WireImageContent, type WireMessage, type WireMessageContent, type WireRunState, type WireRunStep, type WireToolMessage, type WireToolOutcome, type WireToolResult, type WireUserMessage, type ZodLikeError, type ZodLikeSafeParseResult, type ZodLikeSchema, assertNever, base64ToBytes, bytesToBase64, collect, createAsyncContext, filter, fromJsonSafeContentParts, fromJsonSafeMessage, fromJsonSafeRunState, mapStream, md5, merge, take, takeWhile, toJsonSafeContentParts, toJsonSafeMessage, toJsonSafeRunState, validate, validateOrThrow, withSignal, xxhash };
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { base64ToBytes, bytesToBase64, fromJsonSafeContentParts, fromJsonSafeMessage, fromJsonSafeRunState, toJsonSafeContentParts, toJsonSafeMessage, toJsonSafeRunState } from "./binary-json.js";
|
|
1
2
|
import { assertNever } from "./assert-never.js";
|
|
2
3
|
import { createAsyncContext } from "./async-context.js";
|
|
3
4
|
import { md5, xxhash } from "./hash.js";
|
|
4
5
|
import { collect, filter, mapStream, merge, take, takeWhile, withSignal } from "./streams.js";
|
|
5
6
|
import { validate, validateOrThrow } from "./validation.js";
|
|
6
7
|
|
|
7
|
-
export { assertNever, collect, createAsyncContext, filter, mapStream, md5, merge, take, takeWhile, validate, validateOrThrow, withSignal, xxhash };
|
|
8
|
+
export { assertNever, base64ToBytes, bytesToBase64, collect, createAsyncContext, filter, fromJsonSafeContentParts, fromJsonSafeMessage, fromJsonSafeRunState, mapStream, md5, merge, take, takeWhile, toJsonSafeContentParts, toJsonSafeMessage, toJsonSafeRunState, validate, validateOrThrow, withSignal, xxhash };
|
|
@@ -28,7 +28,16 @@ interface ZodLikeError<_TInput = unknown> {
|
|
|
28
28
|
readonly name: string;
|
|
29
29
|
readonly message: string;
|
|
30
30
|
readonly issues: ReadonlyArray<{
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* W-013: `PropertyKey`, not `string | number` - zod 4 bases
|
|
33
|
+
* `$ZodIssue.path` on `PropertyKey`, and this shim must be a
|
|
34
|
+
* SUPERSET of both supported peer majors or the canonical
|
|
35
|
+
* `tool({ inputSchema: z.object({...}) })` fails to typecheck for
|
|
36
|
+
* every zod@4 consumer. Type-level widening of a produced position
|
|
37
|
+
* (breaking for downstream code assigning elements to
|
|
38
|
+
* `string | number`).
|
|
39
|
+
*/
|
|
40
|
+
readonly path: ReadonlyArray<PropertyKey>;
|
|
32
41
|
readonly message: string;
|
|
33
42
|
}>;
|
|
34
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","names":[],"sources":["../../src/utils/validation.ts"],"sourcesContent":[],"mappings":";;AASA;;;;;;;;AASY,UATK,aASiB,CAAA,UAAA,OAAA,EAAA,SAAA,OAAA,CAAA,CAAA;EACW,KAAA,CAAA,IAAA,EAAA,OAAA,CAAA,EATrB,OASqB;EACe,SAAA,CAAA,IAAA,EAAA,OAAA,CAAA,EAThC,sBASgC,CATT,OASS,EATA,MASA,CAAA;EAAb;EAAY,SAAA,OAAA,CAAA,EAPtC,OAOsC;EAG1C,SAAA,MAAY,CAAA,EATT,MASS
|
|
1
|
+
{"version":3,"file":"validation.d.ts","names":[],"sources":["../../src/utils/validation.ts"],"sourcesContent":[],"mappings":";;AASA;;;;;;;;AASY,UATK,aASiB,CAAA,UAAA,OAAA,EAAA,SAAA,OAAA,CAAA,CAAA;EACW,KAAA,CAAA,IAAA,EAAA,OAAA,CAAA,EATrB,OASqB;EACe,SAAA,CAAA,IAAA,EAAA,OAAA,CAAA,EAThC,sBASgC,CATT,OASS,EATA,MASA,CAAA;EAAb;EAAY,SAAA,OAAA,CAAA,EAPtC,OAOsC;EAG1C,SAAA,MAAY,CAAA,EATT,MASS;;;AAGV,KARP,sBAQO,CAAA,OAAA,EAAA,MAAA,CAAA,GAAA;EAAa,SAAA,OAAA,EAAA,IAAA;EAsBpB,SAAA,IAAA,EA7BiC,OA6BjB;AAW5B,CAAA,GAAgB;EAAkC,SAAA,OAAA,EAAA,KAAA;EAAd,SAAA,KAAA,EAvCW,YAuCX,CAvCwB,MAuCxB,CAAA;CAAmD;;AAAD,UApCrE,YAoCqE,CAAA,UAAA,OAAA,CAAA,CAAA;EAatE,SAAA,IAAA,EAAA,MAAe;EAA0B,SAAA,OAAA,EAAA,MAAA;EAAd,SAAA,MAAA,EA9CxB,aA8CwB,CAAA;IAAiD;;;;;;;;;mBApCzE,cAAc;;;;;;;;;;;KAYrB;;kBAC6B;;;kBACC;;;;;;;;;iBAS1B,oBAAoB,cAAc,oBAAoB,iBAAiB;;;;;;;;iBAavE,2BAA2B,cAAc,mCAAmC"}
|
package/dist/utils/validation.js
CHANGED
|
@@ -27,7 +27,7 @@ function validate(schema, data) {
|
|
|
27
27
|
function validateOrThrow(schema, data, what) {
|
|
28
28
|
const r = validate(schema, data);
|
|
29
29
|
if (r.ok) return r.value;
|
|
30
|
-
const summary = r.error.issues.map((i) => `${i.path.join(".") || "."}: ${i.message}`).join("; ");
|
|
30
|
+
const summary = r.error.issues.map((i) => `${i.path.map(String).join(".") || "."}: ${i.message}`).join("; ");
|
|
31
31
|
throw new TypeError(`graphorin: validation failed${what ? ` for ${what}` : ""}: ${summary}`);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","names":[],"sources":["../../src/utils/validation.ts"],"sourcesContent":["/**\n * Type-only Zod compatibility shim. We declare a structural type for the\n * subset of `zod` we depend on so that `@graphorin/core` can be type-\n * checked without importing zod directly. Consumers that do `import {\n * z } from 'zod'` get the real types via the user's `zod` install (the\n * peer dependency).\n *\n * @stable\n */\nexport interface ZodLikeSchema<TOutput = unknown, TInput = unknown> {\n parse(data: unknown): TOutput;\n safeParse(data: unknown): ZodLikeSafeParseResult<TOutput, TInput>;\n /** Internal phantom used by Zod for inference. We don't dereference it. */\n readonly _output?: TOutput;\n readonly _input?: TInput;\n}\n\n/** @stable */\nexport type ZodLikeSafeParseResult<TOutput, TInput> =\n | { readonly success: true; readonly data: TOutput }\n | { readonly success: false; readonly error: ZodLikeError<TInput> };\n\n/** @stable */\nexport interface ZodLikeError<_TInput = unknown> {\n readonly name: string;\n readonly message: string;\n readonly issues: ReadonlyArray<{\n
|
|
1
|
+
{"version":3,"file":"validation.js","names":[],"sources":["../../src/utils/validation.ts"],"sourcesContent":["/**\n * Type-only Zod compatibility shim. We declare a structural type for the\n * subset of `zod` we depend on so that `@graphorin/core` can be type-\n * checked without importing zod directly. Consumers that do `import {\n * z } from 'zod'` get the real types via the user's `zod` install (the\n * peer dependency).\n *\n * @stable\n */\nexport interface ZodLikeSchema<TOutput = unknown, TInput = unknown> {\n parse(data: unknown): TOutput;\n safeParse(data: unknown): ZodLikeSafeParseResult<TOutput, TInput>;\n /** Internal phantom used by Zod for inference. We don't dereference it. */\n readonly _output?: TOutput;\n readonly _input?: TInput;\n}\n\n/** @stable */\nexport type ZodLikeSafeParseResult<TOutput, TInput> =\n | { readonly success: true; readonly data: TOutput }\n | { readonly success: false; readonly error: ZodLikeError<TInput> };\n\n/** @stable */\nexport interface ZodLikeError<_TInput = unknown> {\n readonly name: string;\n readonly message: string;\n readonly issues: ReadonlyArray<{\n /**\n * W-013: `PropertyKey`, not `string | number` - zod 4 bases\n * `$ZodIssue.path` on `PropertyKey`, and this shim must be a\n * SUPERSET of both supported peer majors or the canonical\n * `tool({ inputSchema: z.object({...}) })` fails to typecheck for\n * every zod@4 consumer. Type-level widening of a produced position\n * (breaking for downstream code assigning elements to\n * `string | number`).\n */\n readonly path: ReadonlyArray<PropertyKey>;\n readonly message: string;\n }>;\n}\n\n/**\n * Validate `data` against `schema` and return a `Result` instead of\n * throwing. Use this in code paths where you want explicit\n * pattern-matching over success / failure.\n *\n * @stable\n */\nexport type ValidationResult<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error: ZodLikeError };\n\n/**\n * Synchronous validation wrapper. Does **not** swallow errors thrown by\n * the schema's transformations - only normalizes the success / failure\n * signal.\n *\n * @stable\n */\nexport function validate<T>(schema: ZodLikeSchema<T>, data: unknown): ValidationResult<T> {\n const result = schema.safeParse(data);\n if (result.success) return { ok: true, value: result.data };\n return { ok: false, error: result.error };\n}\n\n/**\n * Throwing variant of `validate(...)` that surfaces a `TypeError` carrying\n * a stable, parser-style message. Useful at module-boundary entry points\n * where a thrown error is the natural failure mode.\n *\n * @stable\n */\nexport function validateOrThrow<T>(schema: ZodLikeSchema<T>, data: unknown, what?: string): T {\n const r = validate(schema, data);\n if (r.ok) return r.value;\n // W-013: `Array.prototype.join` THROWS on symbol elements - map to\n // String first (zod 4 paths are PropertyKey-based).\n const summary = r.error.issues\n .map((i) => `${i.path.map(String).join('.') || '.'}: ${i.message}`)\n .join('; ');\n throw new TypeError(`graphorin: validation failed${what ? ` for ${what}` : ''}: ${summary}`);\n}\n"],"mappings":";;;;;;;;AA2DA,SAAgB,SAAY,QAA0B,MAAoC;CACxF,MAAM,SAAS,OAAO,UAAU,KAAK;AACrC,KAAI,OAAO,QAAS,QAAO;EAAE,IAAI;EAAM,OAAO,OAAO;EAAM;AAC3D,QAAO;EAAE,IAAI;EAAO,OAAO,OAAO;EAAO;;;;;;;;;AAU3C,SAAgB,gBAAmB,QAA0B,MAAe,MAAkB;CAC5F,MAAM,IAAI,SAAS,QAAQ,KAAK;AAChC,KAAI,EAAE,GAAI,QAAO,EAAE;CAGnB,MAAM,UAAU,EAAE,MAAM,OACrB,KAAK,MAAM,GAAG,EAAE,KAAK,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,IAAI,IAAI,EAAE,UAAU,CAClE,KAAK,KAAK;AACb,OAAM,IAAI,UAAU,+BAA+B,OAAO,QAAQ,SAAS,GAAG,IAAI,UAAU"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphorin/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Core types, contracts, and utilities for the Graphorin framework. Dependency-free root package every other @graphorin/* package depends on.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Oleksiy Stepurenko",
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
"contracts"
|
|
23
23
|
],
|
|
24
24
|
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
25
26
|
"engines": {
|
|
26
|
-
"node": ">=22.
|
|
27
|
+
"node": ">=22.12.0"
|
|
27
28
|
},
|
|
28
29
|
"main": "./dist/index.js",
|
|
29
30
|
"module": "./dist/index.js",
|
|
@@ -31,28 +32,29 @@
|
|
|
31
32
|
"exports": {
|
|
32
33
|
".": {
|
|
33
34
|
"types": "./dist/index.d.ts",
|
|
34
|
-
"
|
|
35
|
+
"default": "./dist/index.js"
|
|
35
36
|
},
|
|
36
37
|
"./types": {
|
|
37
38
|
"types": "./dist/types/index.d.ts",
|
|
38
|
-
"
|
|
39
|
+
"default": "./dist/types/index.js"
|
|
39
40
|
},
|
|
40
41
|
"./contracts": {
|
|
41
42
|
"types": "./dist/contracts/index.d.ts",
|
|
42
|
-
"
|
|
43
|
+
"default": "./dist/contracts/index.js"
|
|
43
44
|
},
|
|
44
45
|
"./utils": {
|
|
45
46
|
"types": "./dist/utils/index.d.ts",
|
|
46
|
-
"
|
|
47
|
+
"default": "./dist/utils/index.js"
|
|
47
48
|
},
|
|
48
49
|
"./channels": {
|
|
49
50
|
"types": "./dist/channels/index.d.ts",
|
|
50
|
-
"
|
|
51
|
+
"default": "./dist/channels/index.js"
|
|
51
52
|
},
|
|
52
53
|
"./package.json": "./package.json"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
56
|
"dist",
|
|
57
|
+
"src",
|
|
56
58
|
"README.md",
|
|
57
59
|
"CHANGELOG.md",
|
|
58
60
|
"LICENSE"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow channel kinds. Every state field declared on a workflow's
|
|
3
|
+
* `stateSchema` is bound to a channel that decides the merge strategy
|
|
4
|
+
* applied when multiple writers update the same field within a single
|
|
5
|
+
* execution step.
|
|
6
|
+
*
|
|
7
|
+
* The names are **Graphorin's own design** and must not be aliased to
|
|
8
|
+
* terms from other workflow libraries. A dedicated lint rule lands later
|
|
9
|
+
* in the release line to enforce this.
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
export type ChannelKind =
|
|
14
|
+
| 'latest-value'
|
|
15
|
+
| 'any-value'
|
|
16
|
+
| 'reducer'
|
|
17
|
+
| 'list-aggregate'
|
|
18
|
+
| 'stream'
|
|
19
|
+
| 'barrier'
|
|
20
|
+
| 'ephemeral';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Discriminated union of every channel descriptor.
|
|
24
|
+
*
|
|
25
|
+
* Channels are a *description* of the merge strategy, not a runtime
|
|
26
|
+
* value: the engine reads the `kind` field plus optional auxiliary
|
|
27
|
+
* fields (`reduce`, `from`, `unique`) to decide how to combine writes.
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
export type Channel<T = unknown> =
|
|
32
|
+
| LatestValue<T>
|
|
33
|
+
| AnyValue<T>
|
|
34
|
+
| Reducer<T>
|
|
35
|
+
| ListAggregate<T>
|
|
36
|
+
| Stream<T>
|
|
37
|
+
| Barrier<T>
|
|
38
|
+
| Ephemeral<T>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Overwrite-on-write. Multiple writes within the same execution step
|
|
42
|
+
* raise `MultiWriteError` (use `AnyValue` if collisions are acceptable).
|
|
43
|
+
*
|
|
44
|
+
* @stable
|
|
45
|
+
*/
|
|
46
|
+
export interface LatestValue<T = unknown> {
|
|
47
|
+
readonly kind: 'latest-value';
|
|
48
|
+
readonly default?: T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Overwrite-on-write - collisions are silently allowed (last-write-wins
|
|
53
|
+
* semantics within a step).
|
|
54
|
+
*
|
|
55
|
+
* @stable
|
|
56
|
+
*/
|
|
57
|
+
export interface AnyValue<T = unknown> {
|
|
58
|
+
readonly kind: 'any-value';
|
|
59
|
+
readonly default?: T;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fold writes via a user-provided `reduce` function. The reducer is
|
|
64
|
+
* invoked left-to-right over the writes collected within an execution
|
|
65
|
+
* step.
|
|
66
|
+
*
|
|
67
|
+
* @stable
|
|
68
|
+
*/
|
|
69
|
+
export interface Reducer<T = unknown> {
|
|
70
|
+
readonly kind: 'reducer';
|
|
71
|
+
readonly default?: T;
|
|
72
|
+
readonly reduce: (prev: T, next: T) => T;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Specialization of `Reducer<T[]>` that appends each write to a list.
|
|
77
|
+
*
|
|
78
|
+
* @stable
|
|
79
|
+
*/
|
|
80
|
+
export interface ListAggregate<T = unknown> {
|
|
81
|
+
readonly kind: 'list-aggregate';
|
|
82
|
+
readonly default?: ReadonlyArray<T>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Append-only queue. Used for dynamic task creation via `Dispatch(...)`
|
|
87
|
+
* and for application-defined event streams.
|
|
88
|
+
*
|
|
89
|
+
* @stable
|
|
90
|
+
*/
|
|
91
|
+
export interface Stream<T = unknown> {
|
|
92
|
+
readonly kind: 'stream';
|
|
93
|
+
readonly unique?: boolean;
|
|
94
|
+
readonly default?: ReadonlyArray<T>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Barrier - completes when every writer in `from` has produced a value.
|
|
99
|
+
*
|
|
100
|
+
* @stable
|
|
101
|
+
*/
|
|
102
|
+
export interface Barrier<T = unknown> {
|
|
103
|
+
readonly kind: 'barrier';
|
|
104
|
+
readonly from: ReadonlyArray<string>;
|
|
105
|
+
readonly default?: T;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Value scoped to a single execution step - discarded when the step
|
|
110
|
+
* ends.
|
|
111
|
+
*
|
|
112
|
+
* @stable
|
|
113
|
+
*/
|
|
114
|
+
export interface Ephemeral<T = unknown> {
|
|
115
|
+
readonly kind: 'ephemeral';
|
|
116
|
+
readonly default?: T;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Construct a `LatestValue` channel.
|
|
121
|
+
*
|
|
122
|
+
* @stable
|
|
123
|
+
*/
|
|
124
|
+
export function latestValue<T>(opts?: { readonly default?: T }): LatestValue<T> {
|
|
125
|
+
return opts !== undefined && 'default' in opts && opts.default !== undefined
|
|
126
|
+
? { kind: 'latest-value', default: opts.default }
|
|
127
|
+
: { kind: 'latest-value' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Construct an `AnyValue` channel.
|
|
132
|
+
*
|
|
133
|
+
* @stable
|
|
134
|
+
*/
|
|
135
|
+
export function anyValue<T>(opts?: { readonly default?: T }): AnyValue<T> {
|
|
136
|
+
return opts !== undefined && 'default' in opts && opts.default !== undefined
|
|
137
|
+
? { kind: 'any-value', default: opts.default }
|
|
138
|
+
: { kind: 'any-value' };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Construct a `Reducer` channel.
|
|
143
|
+
*
|
|
144
|
+
* @stable
|
|
145
|
+
*/
|
|
146
|
+
export function reducer<T>(
|
|
147
|
+
reduce: (prev: T, next: T) => T,
|
|
148
|
+
opts?: { readonly default?: T },
|
|
149
|
+
): Reducer<T> {
|
|
150
|
+
return opts !== undefined && 'default' in opts && opts.default !== undefined
|
|
151
|
+
? { kind: 'reducer', reduce, default: opts.default }
|
|
152
|
+
: { kind: 'reducer', reduce };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Construct a `ListAggregate` channel.
|
|
157
|
+
*
|
|
158
|
+
* @stable
|
|
159
|
+
*/
|
|
160
|
+
export function listAggregate<T>(opts?: { readonly default?: ReadonlyArray<T> }): ListAggregate<T> {
|
|
161
|
+
return opts !== undefined && 'default' in opts && opts.default !== undefined
|
|
162
|
+
? { kind: 'list-aggregate', default: opts.default }
|
|
163
|
+
: { kind: 'list-aggregate' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Construct a `Stream` channel.
|
|
168
|
+
*
|
|
169
|
+
* @stable
|
|
170
|
+
*/
|
|
171
|
+
export function stream<T>(opts?: {
|
|
172
|
+
readonly unique?: boolean;
|
|
173
|
+
readonly default?: ReadonlyArray<T>;
|
|
174
|
+
}): Stream<T> {
|
|
175
|
+
const out: { kind: 'stream'; unique?: boolean; default?: ReadonlyArray<T> } = {
|
|
176
|
+
kind: 'stream',
|
|
177
|
+
};
|
|
178
|
+
if (opts?.unique !== undefined) out.unique = opts.unique;
|
|
179
|
+
if (opts?.default !== undefined) out.default = opts.default;
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Construct a `Barrier` channel.
|
|
185
|
+
*
|
|
186
|
+
* @stable
|
|
187
|
+
*/
|
|
188
|
+
export function barrier<T>(
|
|
189
|
+
from: ReadonlyArray<string>,
|
|
190
|
+
opts?: { readonly default?: T },
|
|
191
|
+
): Barrier<T> {
|
|
192
|
+
return opts !== undefined && 'default' in opts && opts.default !== undefined
|
|
193
|
+
? { kind: 'barrier', from, default: opts.default }
|
|
194
|
+
: { kind: 'barrier', from };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Construct an `Ephemeral` channel.
|
|
199
|
+
*
|
|
200
|
+
* @stable
|
|
201
|
+
*/
|
|
202
|
+
export function ephemeral<T>(opts?: { readonly default?: T }): Ephemeral<T> {
|
|
203
|
+
return opts !== undefined && 'default' in opts && opts.default !== undefined
|
|
204
|
+
? { kind: 'ephemeral', default: opts.default }
|
|
205
|
+
: { kind: 'ephemeral' };
|
|
206
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow control-flow primitive: a single value handed to
|
|
3
|
+
* `Workflow.resume(threadId, directive?)` (or returned from a node's
|
|
4
|
+
* `pause(...)` resolution) carrying any combination of:
|
|
5
|
+
*
|
|
6
|
+
* - `goto` - jump to a named node, bypassing the edge graph.
|
|
7
|
+
* **Destructive (workflow-09):** the restored frontier is discarded -
|
|
8
|
+
* surviving dynamic tasks, completed-but-unwalked nodes, and every
|
|
9
|
+
* pending pause record (including already-delivered pause answers)
|
|
10
|
+
* are dropped in favour of the single goto task. Use it as an
|
|
11
|
+
* operator escape hatch, not routine control flow.
|
|
12
|
+
* - `resume` - value supplied to the `pause(value)` call that suspended.
|
|
13
|
+
* - `update` - additional channel writes applied before the next step.
|
|
14
|
+
*
|
|
15
|
+
* The shape is **Graphorin's own design** (the name `Directive` is part
|
|
16
|
+
* of the public API).
|
|
17
|
+
*
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
export class Directive<TUpdate = Record<string, unknown>, TResume = unknown> {
|
|
21
|
+
readonly goto?: string;
|
|
22
|
+
readonly resume?: TResume;
|
|
23
|
+
readonly update?: TUpdate;
|
|
24
|
+
|
|
25
|
+
constructor(opts: DirectiveOptions<TUpdate, TResume>) {
|
|
26
|
+
if (opts.goto !== undefined) this.goto = opts.goto;
|
|
27
|
+
if ('resume' in opts && opts.resume !== undefined) this.resume = opts.resume;
|
|
28
|
+
if (opts.update !== undefined) this.update = opts.update;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Constructor parameters for `Directive`.
|
|
34
|
+
*
|
|
35
|
+
* @stable
|
|
36
|
+
*/
|
|
37
|
+
export interface DirectiveOptions<TUpdate = Record<string, unknown>, TResume = unknown> {
|
|
38
|
+
readonly goto?: string;
|
|
39
|
+
readonly resume?: TResume;
|
|
40
|
+
readonly update?: TUpdate;
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow dynamic-task primitive. A node returns one or more
|
|
3
|
+
* `Dispatch(nodeName, args)` values to schedule additional tasks in the
|
|
4
|
+
* next execution step.
|
|
5
|
+
*
|
|
6
|
+
* The class is intentionally tiny - the engine inspects only the public
|
|
7
|
+
* `nodeName` and `args` fields. The shape is **Graphorin's own design**
|
|
8
|
+
* (the name `Dispatch` is part of the public API).
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
export class Dispatch<TArgs = unknown> {
|
|
13
|
+
/**
|
|
14
|
+
* Cross-realm brand (workflow-13): the engine's structural fallback
|
|
15
|
+
* requires this marker so a plain state object that happens to carry
|
|
16
|
+
* `nodeName` + `args` keys is treated as channel WRITES, never
|
|
17
|
+
* silently swallowed as a dispatch. A plain own property (not a
|
|
18
|
+
* symbol) so it survives `structuredClone` across worker boundaries.
|
|
19
|
+
*/
|
|
20
|
+
readonly __graphorinDispatch: true = true;
|
|
21
|
+
readonly nodeName: string;
|
|
22
|
+
readonly args: TArgs;
|
|
23
|
+
|
|
24
|
+
constructor(nodeName: string, args: TArgs) {
|
|
25
|
+
this.nodeName = nodeName;
|
|
26
|
+
this.args = args;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convenience factory equivalent to `new Dispatch(nodeName, args)`.
|
|
32
|
+
*
|
|
33
|
+
* @stable
|
|
34
|
+
*/
|
|
35
|
+
export function dispatch<TArgs>(nodeName: string, args: TArgs): Dispatch<TArgs> {
|
|
36
|
+
return new Dispatch(nodeName, args);
|
|
37
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable workflow primitives (D1) built on the `pause(...)` substrate:
|
|
3
|
+
*
|
|
4
|
+
* - {@link sleepUntil} / {@link sleepFor} - **durable timers**: the node
|
|
5
|
+
* suspends with a persisted wake-at timestamp; `workflow.tick(threadId)`
|
|
6
|
+
* (or any scheduler calling it) resumes the thread once the deadline
|
|
7
|
+
* passes. The timer survives process restarts because it lives in the
|
|
8
|
+
* checkpointed frontier, not in a `setTimeout`.
|
|
9
|
+
* - {@link awaitExternal} - **durable promises (awakeables)**: the node
|
|
10
|
+
* suspends under a caller-chosen name; an external system resolves it
|
|
11
|
+
* later via `workflow.resolveAwakeable(threadId, name, value)` and the
|
|
12
|
+
* node receives `value` as the call's return.
|
|
13
|
+
* - {@link requestApproval} - **first-class persisted approvals**: an
|
|
14
|
+
* awakeable specialized for human sign-off, resolved via
|
|
15
|
+
* `workflow.approve(threadId, name, decision)`.
|
|
16
|
+
*
|
|
17
|
+
* Each helper throws the same branded `PauseSignal` that `pause(...)`
|
|
18
|
+
* throws, carrying a reserved, JSON-safe pause value the engine
|
|
19
|
+
* recognises (`kind: 'graphorin.timer' | 'graphorin.awakeable' |
|
|
20
|
+
* 'graphorin.approval'`) to stamp `wakeAt` / `name` onto the persisted
|
|
21
|
+
* pause record. On resume the body re-executes from the top and the
|
|
22
|
+
* already-delivered values replay in order (WF-2), exactly like plain
|
|
23
|
+
* `pause(...)`.
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { pause } from './pause.js';
|
|
29
|
+
|
|
30
|
+
/** Reserved pause-value kind for durable timers (D1). */
|
|
31
|
+
export const TIMER_PAUSE_KIND = 'graphorin.timer' as const;
|
|
32
|
+
/** Reserved pause-value kind for awakeables / durable promises (D1). */
|
|
33
|
+
export const AWAKEABLE_PAUSE_KIND = 'graphorin.awakeable' as const;
|
|
34
|
+
/** Reserved pause-value kind for persisted approvals (D1). */
|
|
35
|
+
export const APPROVAL_PAUSE_KIND = 'graphorin.approval' as const;
|
|
36
|
+
|
|
37
|
+
/** Pause value carried by a durable-timer suspension. */
|
|
38
|
+
export interface TimerPauseValue {
|
|
39
|
+
readonly kind: typeof TIMER_PAUSE_KIND;
|
|
40
|
+
/** Epoch milliseconds at which the timer becomes due. */
|
|
41
|
+
readonly wakeAt: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Pause value carried by an awakeable suspension. */
|
|
45
|
+
export interface AwakeablePauseValue {
|
|
46
|
+
readonly kind: typeof AWAKEABLE_PAUSE_KIND;
|
|
47
|
+
/** Caller-chosen name targeted by `workflow.resolveAwakeable(...)`. */
|
|
48
|
+
readonly name: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Pause value carried by a persisted-approval suspension. */
|
|
52
|
+
export interface ApprovalPauseValue {
|
|
53
|
+
readonly kind: typeof APPROVAL_PAUSE_KIND;
|
|
54
|
+
/** Caller-chosen name targeted by `workflow.approve(...)`. */
|
|
55
|
+
readonly name: string;
|
|
56
|
+
/** Free-form payload surfaced to the approver (what is being approved). */
|
|
57
|
+
readonly payload?: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Type guard for {@link TimerPauseValue}. @stable */
|
|
61
|
+
export function isTimerPauseValue(value: unknown): value is TimerPauseValue {
|
|
62
|
+
return (
|
|
63
|
+
typeof value === 'object' &&
|
|
64
|
+
value !== null &&
|
|
65
|
+
(value as { kind?: unknown }).kind === TIMER_PAUSE_KIND &&
|
|
66
|
+
typeof (value as { wakeAt?: unknown }).wakeAt === 'number'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Type guard for {@link AwakeablePauseValue}. @stable */
|
|
71
|
+
export function isAwakeablePauseValue(value: unknown): value is AwakeablePauseValue {
|
|
72
|
+
return (
|
|
73
|
+
typeof value === 'object' &&
|
|
74
|
+
value !== null &&
|
|
75
|
+
(value as { kind?: unknown }).kind === AWAKEABLE_PAUSE_KIND &&
|
|
76
|
+
typeof (value as { name?: unknown }).name === 'string'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Type guard for {@link ApprovalPauseValue}. @stable */
|
|
81
|
+
export function isApprovalPauseValue(value: unknown): value is ApprovalPauseValue {
|
|
82
|
+
return (
|
|
83
|
+
typeof value === 'object' &&
|
|
84
|
+
value !== null &&
|
|
85
|
+
(value as { kind?: unknown }).kind === APPROVAL_PAUSE_KIND &&
|
|
86
|
+
typeof (value as { name?: unknown }).name === 'string'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Durably sleep until an absolute instant. Suspends the workflow thread
|
|
92
|
+
* with a persisted wake-at timestamp; `workflow.tick(threadId)` resumes
|
|
93
|
+
* it once due. Returns nothing on resume.
|
|
94
|
+
*
|
|
95
|
+
* @stable
|
|
96
|
+
*/
|
|
97
|
+
export function sleepUntil(at: string | number | Date): void {
|
|
98
|
+
const wakeAt = typeof at === 'number' ? at : at instanceof Date ? at.getTime() : Date.parse(at);
|
|
99
|
+
if (!Number.isFinite(wakeAt)) {
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
`[graphorin/core] sleepUntil(...) got an unparseable instant: ${String(at)}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
pause<TimerPauseValue, unknown>({ kind: TIMER_PAUSE_KIND, wakeAt });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Durably sleep for a relative duration (sugar over {@link sleepUntil}).
|
|
109
|
+
*
|
|
110
|
+
* @stable
|
|
111
|
+
*/
|
|
112
|
+
export function sleepFor(ms: number): void {
|
|
113
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
114
|
+
throw new TypeError(`[graphorin/core] sleepFor(...) needs a non-negative duration, got ${ms}`);
|
|
115
|
+
}
|
|
116
|
+
sleepUntil(Date.now() + ms);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Suspend on a named durable promise. The thread stays suspended (and
|
|
121
|
+
* survives restarts) until an external caller resolves it via
|
|
122
|
+
* `workflow.resolveAwakeable(threadId, name, value)`; that `value` is
|
|
123
|
+
* returned here.
|
|
124
|
+
*
|
|
125
|
+
* @stable
|
|
126
|
+
*/
|
|
127
|
+
export function awaitExternal<TResume = unknown>(name: string): TResume {
|
|
128
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
129
|
+
throw new TypeError('[graphorin/core] awaitExternal(name) needs a non-empty name');
|
|
130
|
+
}
|
|
131
|
+
return pause<AwakeablePauseValue, TResume>({ kind: AWAKEABLE_PAUSE_KIND, name });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Suspend on a named persisted approval. Resolved by
|
|
136
|
+
* `workflow.approve(threadId, name, decision)`; the decision is returned
|
|
137
|
+
* here. The optional payload is surfaced on the pending pause record so
|
|
138
|
+
* an approval UI can show what is being approved.
|
|
139
|
+
*
|
|
140
|
+
* @stable
|
|
141
|
+
*/
|
|
142
|
+
export function requestApproval<TDecision = unknown>(name: string, payload?: unknown): TDecision {
|
|
143
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
144
|
+
throw new TypeError('[graphorin/core] requestApproval(name) needs a non-empty name');
|
|
145
|
+
}
|
|
146
|
+
return pause<ApprovalPauseValue, TDecision>({
|
|
147
|
+
kind: APPROVAL_PAUSE_KIND,
|
|
148
|
+
name,
|
|
149
|
+
...(payload !== undefined ? { payload } : {}),
|
|
150
|
+
});
|
|
151
|
+
}
|