@executablemd/testing 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/esm/core/mod.js +55 -0
- package/esm/core/src/api.js +31 -0
- package/esm/core/src/collect.js +24 -0
- package/esm/core/src/component-api.js +47 -0
- package/esm/core/src/content-context.js +23 -0
- package/esm/core/src/deno-compiler.js +51 -0
- package/esm/core/src/errors.js +13 -0
- package/esm/core/src/eval-context.js +13 -0
- package/esm/core/src/eval-handler.js +79 -0
- package/esm/core/src/eval-interpolate.js +63 -0
- package/esm/core/src/eval-transform.js +378 -0
- package/esm/core/src/execute.js +378 -0
- package/esm/core/src/expand.js +988 -0
- package/esm/core/src/frontmatter.js +98 -0
- package/esm/core/src/heal.js +29 -0
- package/esm/core/src/interpolate.js +39 -0
- package/esm/core/src/modifiers/daemon.js +60 -0
- package/esm/core/src/modifiers/persist.js +35 -0
- package/esm/core/src/modifiers/timeout.js +55 -0
- package/esm/core/src/modifiers.js +113 -0
- package/esm/core/src/output/normalize.js +33 -0
- package/esm/core/src/output/terminal.js +20 -0
- package/esm/core/src/render.js +30 -0
- package/esm/core/src/replay-stream.js +58 -0
- package/esm/core/src/sample-api.js +45 -0
- package/esm/core/src/scanner.js +632 -0
- package/esm/core/src/temp-file-compiler.js +68 -0
- package/esm/core/src/types.js +7 -0
- package/esm/core/src/validate.js +82 -0
- package/esm/durable-streams/combinators.js +227 -0
- package/esm/durable-streams/context.js +13 -0
- package/esm/durable-streams/divergence.js +56 -0
- package/esm/durable-streams/each.js +193 -0
- package/esm/durable-streams/effect.js +273 -0
- package/esm/durable-streams/ephemeral.js +80 -0
- package/esm/durable-streams/errors.js +92 -0
- package/esm/durable-streams/http-stream.js +207 -0
- package/esm/durable-streams/mod.js +34 -0
- package/esm/durable-streams/operations.js +88 -0
- package/esm/durable-streams/replay-guard.js +87 -0
- package/esm/durable-streams/replay-index.js +132 -0
- package/esm/durable-streams/run.js +137 -0
- package/esm/durable-streams/serialize.js +65 -0
- package/esm/durable-streams/stream.js +56 -0
- package/esm/durable-streams/types.js +10 -0
- package/esm/package.json +3 -0
- package/esm/runtime/apis.js +237 -0
- package/esm/runtime/find-free-port.js +33 -0
- package/esm/runtime/mod.js +20 -0
- package/esm/testing/npm/src/_dnt.polyfills.js +1 -0
- package/esm/testing/npm/src/_dnt.shims.js +57 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/equal.js +194 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/format.js +70 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/styles.js +207 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
- package/esm/testing/npm/src/mod.js +27 -0
- package/esm/testing/npm/src/src/assertions.js +250 -0
- package/esm/testing/npm/src/src/handlers.js +245 -0
- package/esm/testing/npm/src/src/journal.js +152 -0
- package/esm/testing/npm/src/src/test-api.js +33 -0
- package/esm/testing/npm/src/src/use-testing.js +79 -0
- package/esm/testing/npm/src/src/vocabulary.js +111 -0
- package/package.json +35 -0
- package/types/_dnt.polyfills.d.ts +6 -0
- package/types/_dnt.shims.d.ts +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/format.d.ts +28 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
- package/types/mod.d.ts +29 -0
- package/types/src/assertions.d.ts +55 -0
- package/types/src/handlers.d.ts +18 -0
- package/types/src/journal.d.ts +29 -0
- package/types/src/test-api.d.ts +54 -0
- package/types/src/use-testing.d.ts +25 -0
- package/types/src/vocabulary.d.ts +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Taras Mankovski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/esm/core/mod.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executable MDX — public API.
|
|
3
|
+
*
|
|
4
|
+
* Treats markdown documents as durable workflows: text is emitted immediately,
|
|
5
|
+
* component references are resolved and expanded recursively, and code blocks
|
|
6
|
+
* marked as executable are run via durableExec.
|
|
7
|
+
*/
|
|
8
|
+
export { ephemeral } from "@executablemd/durable-streams";
|
|
9
|
+
export { healSegment } from "./src/heal.ts";
|
|
10
|
+
export { combine } from "@effectionx/middleware";
|
|
11
|
+
export { useCodeBlock } from "./src/modifiers.ts";
|
|
12
|
+
export { Component, importComponent, applyModifiers, raise, env, evalScope, expandInvocation, codeBlock, persistent, content, } from "./src/component-api.ts";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Rendering
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export { renderSegments } from "./src/render.ts";
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Replay-safe stream
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export { createReplayStream } from "./src/replay-stream.ts";
|
|
21
|
+
export { compileBlock } from "./src/eval-context.ts";
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// executable.md Globals (for generated eval modules)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export { useContent } from "./src/content-context.ts";
|
|
26
|
+
export { Sample } from "./src/sample-api.ts";
|
|
27
|
+
export { evalFactory } from "./src/eval-handler.ts";
|
|
28
|
+
export { persistFactory } from "./src/modifiers/persist.ts";
|
|
29
|
+
export { timeoutFactory, parseDuration } from "./src/modifiers/timeout.ts";
|
|
30
|
+
export { daemonFactory } from "./src/modifiers/daemon.ts";
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Eval binding interpolation
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
export { interpolateEvalBindings } from "./src/eval-interpolate.ts";
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Port allocation
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
export { findFreePort } from "@executablemd/runtime";
|
|
39
|
+
export { transformBlock, serializeExports, isJson } from "./src/eval-transform.ts";
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Output Api & middleware
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
export { DocumentOutput } from "./src/api.ts";
|
|
44
|
+
export { useNormalizedOutput } from "./src/output/normalize.ts";
|
|
45
|
+
export { useTerminalOutput } from "./src/output/terminal.ts";
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Document runner
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
export { execute, Execution } from "./src/execute.ts";
|
|
50
|
+
export { useDenoCompiler } from "./src/deno-compiler.ts";
|
|
51
|
+
export { useTempFileCompiler } from "./src/temp-file-compiler.ts";
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Stream consumption
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export { collect } from "./src/collect.ts";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Output Api — Effection Api for streaming document output (spec §9).
|
|
3
|
+
*
|
|
4
|
+
* A single Api with one operation: `output`. The core handler is a no-op.
|
|
5
|
+
* Behavior comes from middleware installed via `yield* DocumentOutput.around(...)`
|
|
6
|
+
* and channel delivery.
|
|
7
|
+
*
|
|
8
|
+
* Call sites use `DocumentOutput.operations.output(text)` inside `yield* ephemeral(...)`.
|
|
9
|
+
*/
|
|
10
|
+
import { createApi } from "@effectionx/context-api";
|
|
11
|
+
/**
|
|
12
|
+
* The Document Output Api instance.
|
|
13
|
+
*
|
|
14
|
+
* Usage in middleware:
|
|
15
|
+
* ```js
|
|
16
|
+
* yield* DocumentOutput.around({
|
|
17
|
+
* *output([text], next) {
|
|
18
|
+
* const transformed = transform(text);
|
|
19
|
+
* yield* next(transformed);
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Usage in the emission loop:
|
|
25
|
+
* ```js
|
|
26
|
+
* yield* ephemeral(DocumentOutput.operations.output(text));
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const DocumentOutput = createApi("DocumentOutput", {
|
|
30
|
+
*output(_text) { },
|
|
31
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collect — wait for a document execution to complete and return the output.
|
|
3
|
+
*
|
|
4
|
+
* Convenience wrapper that unwraps the completion `Result<string>`:
|
|
5
|
+
* the output string on `Ok`, a throw on `Err`.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const output = yield* collect(yield* execute(options));
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Wait for a document execution to complete and return the full output.
|
|
13
|
+
* Throws the failure when the execution completed with `Err`.
|
|
14
|
+
*
|
|
15
|
+
* @param execution - A `DocumentExecution` as returned by `execute`.
|
|
16
|
+
* @returns The full output string.
|
|
17
|
+
*/
|
|
18
|
+
export function* collect(execution) {
|
|
19
|
+
const result = yield* execution;
|
|
20
|
+
if (!result.ok) {
|
|
21
|
+
throw result.error;
|
|
22
|
+
}
|
|
23
|
+
return result.value;
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Api — contextual operations for component expansion.
|
|
3
|
+
*
|
|
4
|
+
* One public Api replaces the former dependency container (ExpansionContext)
|
|
5
|
+
* and raw Effection context keys. Context-dependent behavior is installed as
|
|
6
|
+
* scope-local middleware via `Component.around(...)`:
|
|
7
|
+
*
|
|
8
|
+
* - Runtime implementations (document import, modifier execution, component
|
|
9
|
+
* state) install at `{ at: "min" }`. Middleware installed in a nested scope
|
|
10
|
+
* runs before inherited middleware, so a component that installs its own
|
|
11
|
+
* `env` shadows its ancestors without leaking into siblings — install
|
|
12
|
+
* inside `scoped()` for automatic removal.
|
|
13
|
+
* - Caller instrumentation and overrides wrap at the default `"max"`.
|
|
14
|
+
*/
|
|
15
|
+
import { createApi } from "@effectionx/context-api";
|
|
16
|
+
export const Component = createApi("Component", {
|
|
17
|
+
// deno-lint-ignore require-yield
|
|
18
|
+
*importComponent(name) {
|
|
19
|
+
throw new Error(`Component.importComponent("${name}") has no provider. Install one with ` +
|
|
20
|
+
`Component.around({ importComponent }, { at: "min" }) before expansion.`);
|
|
21
|
+
},
|
|
22
|
+
// deno-lint-ignore require-yield
|
|
23
|
+
*applyModifiers(_modifiers, block) {
|
|
24
|
+
throw new Error(`Component.applyModifiers() has no provider for block "${block.blockId}". Install one ` +
|
|
25
|
+
`with Component.around({ applyModifiers }, { at: "min" }) before expansion.`);
|
|
26
|
+
},
|
|
27
|
+
// deno-lint-ignore require-yield
|
|
28
|
+
*raise(error) {
|
|
29
|
+
return error;
|
|
30
|
+
},
|
|
31
|
+
env: undefined,
|
|
32
|
+
evalScope: undefined,
|
|
33
|
+
// deno-lint-ignore require-yield
|
|
34
|
+
*expandInvocation() {
|
|
35
|
+
return undefined;
|
|
36
|
+
},
|
|
37
|
+
// deno-lint-ignore require-yield
|
|
38
|
+
*codeBlock() {
|
|
39
|
+
throw new Error("Component.codeBlock() has no provider: no code block is executing in this scope.");
|
|
40
|
+
},
|
|
41
|
+
persistent: false,
|
|
42
|
+
// deno-lint-ignore require-yield
|
|
43
|
+
*content(_slot) {
|
|
44
|
+
throw new Error("Component.content() has no provider: not inside a function component invocation.");
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
export const { importComponent, applyModifiers, raise, env, evalScope, expandInvocation, codeBlock, persistent, content, } = Component.operations;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useContent() — the function component equivalent of `<Content />` in
|
|
3
|
+
* markdown components. Ergonomic alias for the Component `content()`
|
|
4
|
+
* operation; the expansion engine installs the slot-rendering middleware
|
|
5
|
+
* around each function component invocation.
|
|
6
|
+
*/
|
|
7
|
+
import { content } from "./component-api.ts";
|
|
8
|
+
/**
|
|
9
|
+
* Render children content from the invoking component.
|
|
10
|
+
*
|
|
11
|
+
* @param slotName - Optional slot name. If provided, renders only the
|
|
12
|
+
* content assigned to that slot (matching `<Content slot="name" />`).
|
|
13
|
+
* If omitted, renders the default slot.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const body = yield* useContent();
|
|
18
|
+
* const header = yield* useContent("header");
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function useContent(slotName) {
|
|
22
|
+
return content(slotName);
|
|
23
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deno-specific eval block compiler middleware.
|
|
3
|
+
*
|
|
4
|
+
* Compiles eval block source into generator functions via data: URIs.
|
|
5
|
+
* Standard imports (Effection, executable.md APIs) are captured in the middleware
|
|
6
|
+
* closure — they are not part of the Compiler API interface.
|
|
7
|
+
*
|
|
8
|
+
* Install via `yield* useDenoCompiler()` inside a document execution
|
|
9
|
+
* scope before any eval blocks are processed.
|
|
10
|
+
*/
|
|
11
|
+
import { call } from "effection";
|
|
12
|
+
import { API } from "@executablemd/runtime";
|
|
13
|
+
// STANDARD_IMPORTS below resolve at runtime from generated eval modules;
|
|
14
|
+
// without these static anchors, `deno compile --exclude-unused-npm` prunes
|
|
15
|
+
// the packages from the binary and every eval block using them fails.
|
|
16
|
+
import "@effectionx/converge";
|
|
17
|
+
import "@effectionx/fetch";
|
|
18
|
+
/**
|
|
19
|
+
* Standard import statements prepended to every generated eval module.
|
|
20
|
+
* Captured in the middleware closure — not exposed on the public API.
|
|
21
|
+
*/
|
|
22
|
+
const STANDARD_IMPORTS = [
|
|
23
|
+
'import { sleep, spawn, call, resource, useScope, createChannel, each, suspend, createSignal } from "effection";',
|
|
24
|
+
'import { when } from "@effectionx/converge";',
|
|
25
|
+
'import { fetch } from "@effectionx/fetch";',
|
|
26
|
+
'import { useContent, Sample } from "@executablemd/core";',
|
|
27
|
+
'import { findFreePort } from "@executablemd/runtime";',
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Install the Deno data: URI compiler as middleware on the current scope.
|
|
31
|
+
*
|
|
32
|
+
* Must be called inside an Effection scope (e.g., inside `execute`'s
|
|
33
|
+
* spawned task) before any eval blocks execute.
|
|
34
|
+
*/
|
|
35
|
+
export function* useDenoCompiler() {
|
|
36
|
+
yield* API.Compiler.around({
|
|
37
|
+
*compile([source, options], next) {
|
|
38
|
+
void next; // terminal middleware — does not delegate
|
|
39
|
+
const userImports = options?.imports ?? [];
|
|
40
|
+
const allImports = [...STANDARD_IMPORTS, ...userImports];
|
|
41
|
+
const importLines = allImports.join("\n");
|
|
42
|
+
const moduleSource = [importLines, `export default function*(env) {`, source, `}`].join("\n");
|
|
43
|
+
const dataUri = `data:application/typescript,${encodeURIComponent(moduleSource)}`;
|
|
44
|
+
const mod = yield* call(() => import(dataUri));
|
|
45
|
+
if (typeof mod.default !== "function") {
|
|
46
|
+
throw new Error(`useDenoCompiler: expected default export to be a generator function, got ${typeof mod.default}`);
|
|
47
|
+
}
|
|
48
|
+
return mod.default;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by suppressed-documentation raise middleware (spec §6.9). Generic
|
|
3
|
+
* catches in the engine rethrow it instead of converting it into an
|
|
4
|
+
* ErrorSegment, so documentation fail-fast is never swallowed.
|
|
5
|
+
*/
|
|
6
|
+
export class DocumentationError extends Error {
|
|
7
|
+
segment;
|
|
8
|
+
constructor(segment) {
|
|
9
|
+
super(segment.message);
|
|
10
|
+
this.name = "DocumentationError";
|
|
11
|
+
this.segment = segment;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval block compilation (spec §5).
|
|
3
|
+
*/
|
|
4
|
+
import { compile as runtimeCompile } from "@executablemd/runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Compile transformed source code into a generator function.
|
|
7
|
+
*
|
|
8
|
+
* Delegates to `@executablemd/runtime` so platform-specific
|
|
9
|
+
* compilation can be provided via API.Compiler middleware.
|
|
10
|
+
*/
|
|
11
|
+
export function compileBlock(transformedBodyCode, userImports) {
|
|
12
|
+
return runtimeCompile(transformedBodyCode, { imports: userImports });
|
|
13
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createDurableOperation, ephemeral } from "@executablemd/durable-streams";
|
|
2
|
+
import { unbox } from "@effectionx/scope-eval";
|
|
3
|
+
import { useCodeBlock } from "./modifiers.ts";
|
|
4
|
+
import { env, evalScope, persistent } from "./component-api.ts";
|
|
5
|
+
import { compileBlock } from "./eval-context.ts";
|
|
6
|
+
import { transformBlock, serializeExports } from "./eval-transform.ts";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// evalFactory — terminal modifier (spec §6.2)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const evalFactory = (_params) => (_args, _next) => (function* () {
|
|
11
|
+
const ctx = yield* useCodeBlock();
|
|
12
|
+
const evalEnv = yield* ephemeral(env);
|
|
13
|
+
if (!evalEnv) {
|
|
14
|
+
throw new Error(`eval block "${ctx.blockId}" requires a binding environment; none is in scope.`);
|
|
15
|
+
}
|
|
16
|
+
const persist = yield* ephemeral(persistent);
|
|
17
|
+
// Inject output() function into env so eval blocks can produce
|
|
18
|
+
// rendered output. The function is a plain synchronous call:
|
|
19
|
+
// output("some text")
|
|
20
|
+
// The mutable ref is block-local; serializeExports silently
|
|
21
|
+
// omits non-JSON values (functions), so output won't pollute
|
|
22
|
+
// the journal. The output text itself is journaled alongside
|
|
23
|
+
// exports as __output.
|
|
24
|
+
const outputRef = { text: "" };
|
|
25
|
+
evalEnv.values.output = (text) => {
|
|
26
|
+
outputRef.text = String(text);
|
|
27
|
+
};
|
|
28
|
+
const transformed = transformBlock(ctx.content, ctx.blockId, Object.keys(evalEnv.values));
|
|
29
|
+
const bindings = serializeExports(evalEnv.values, transformed.imports);
|
|
30
|
+
const result = (yield createDurableOperation({
|
|
31
|
+
type: "eval",
|
|
32
|
+
name: `eval:${ctx.blockId}`,
|
|
33
|
+
...(ctx.language ? { language: ctx.language } : {}),
|
|
34
|
+
}, function* () {
|
|
35
|
+
// Merge incoming bindings snapshot into env before execution
|
|
36
|
+
Object.assign(evalEnv.values, bindings);
|
|
37
|
+
// Compile the eval block via data: URI module import.
|
|
38
|
+
// compileBlock is async (returns Operation) — it generates a
|
|
39
|
+
// TypeScript module and imports it via data: URI.
|
|
40
|
+
const fn = yield* compileBlock(transformed.code, transformed.userImports ?? []);
|
|
41
|
+
if (persist) {
|
|
42
|
+
// Persist mode: run the compiled block inside the eval scope
|
|
43
|
+
// so spawned resources are retained in the persistent EvalScope.
|
|
44
|
+
const scope = yield* evalScope;
|
|
45
|
+
if (!scope) {
|
|
46
|
+
throw new Error(`persist eval block "${ctx.blockId}" requires a component eval scope; none is in scope.`);
|
|
47
|
+
}
|
|
48
|
+
const blockResult = yield* scope.eval(() => fn(evalEnv.values));
|
|
49
|
+
const returnValue = unbox(blockResult);
|
|
50
|
+
if (!outputRef.text && returnValue != null) {
|
|
51
|
+
outputRef.text = String(returnValue);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Normal mode: run the compiled block in the current scope.
|
|
56
|
+
// Resources are torn down when this operation completes.
|
|
57
|
+
const returnValue = yield* fn(evalEnv.values);
|
|
58
|
+
if (!outputRef.text && returnValue != null) {
|
|
59
|
+
outputRef.text = String(returnValue);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const exports = serializeExports(evalEnv.values, transformed.exports);
|
|
63
|
+
if (outputRef.text) {
|
|
64
|
+
exports.__output = outputRef.text;
|
|
65
|
+
}
|
|
66
|
+
return { value: exports };
|
|
67
|
+
}));
|
|
68
|
+
if (result.value && typeof result.value === "object") {
|
|
69
|
+
const restored = result.value;
|
|
70
|
+
// Extract __output before merging into env
|
|
71
|
+
if (typeof restored.__output === "string") {
|
|
72
|
+
outputRef.text = restored.__output;
|
|
73
|
+
}
|
|
74
|
+
// Remove __output from exports before assigning to env
|
|
75
|
+
const { __output: _, ...exports } = restored;
|
|
76
|
+
Object.assign(evalEnv.values, exports);
|
|
77
|
+
}
|
|
78
|
+
return { output: outputRef.text, exitCode: 0, stderr: "" };
|
|
79
|
+
})();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval binding interpolation (spec §6.6).
|
|
3
|
+
*
|
|
4
|
+
* Substitutes `{name}` and `{name.path.chain}` references in content
|
|
5
|
+
* with values from the eval binding environment (`env.values`). This
|
|
6
|
+
* runs in the expansion engine for both code block content and text
|
|
7
|
+
* segments.
|
|
8
|
+
*
|
|
9
|
+
* References use JavaScript identifier syntax with optional dot paths:
|
|
10
|
+
* /\{([a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*)\}/g
|
|
11
|
+
*
|
|
12
|
+
* The first segment of a dotted path must be a key in `env.values`.
|
|
13
|
+
* Subsequent segments traverse nested properties. If any intermediate
|
|
14
|
+
* value is null/undefined, the reference is left verbatim.
|
|
15
|
+
*
|
|
16
|
+
* No collision with `{meta.key}` / `{props.key}`: those are consumed
|
|
17
|
+
* by the `interpolate()` pass which runs first. By the time this
|
|
18
|
+
* function runs, namespaced references are already resolved.
|
|
19
|
+
*
|
|
20
|
+
* If `env.values` has no key matching the root reference, it is left
|
|
21
|
+
* verbatim. Non-string values are converted via `String()`.
|
|
22
|
+
*
|
|
23
|
+
* Escaped braces (`\{name}`) are preserved as literal `{name}` in the
|
|
24
|
+
* output — the backslash is consumed. This is consistent with
|
|
25
|
+
* `interpolate()` which handles `\{meta.key}` the same way.
|
|
26
|
+
*/
|
|
27
|
+
const BARE_BINDING_RE = /\{([a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*)\}/g;
|
|
28
|
+
/**
|
|
29
|
+
* Unicode private-use placeholder for escaped opening braces.
|
|
30
|
+
* Used to protect `\{` from interpolation, then restored as `{`.
|
|
31
|
+
*/
|
|
32
|
+
const ESCAPED_BRACE_PLACEHOLDER = "\uE000";
|
|
33
|
+
/**
|
|
34
|
+
* Replace bare `{name}` references with values from the binding environment.
|
|
35
|
+
* Respects `\{` escaping — `\{name}` is left as literal `{name}`.
|
|
36
|
+
*
|
|
37
|
+
* @param content - The content to interpolate
|
|
38
|
+
* @param bindings - The current eval binding environment (env.values)
|
|
39
|
+
* @returns Content with resolved bindings substituted
|
|
40
|
+
*/
|
|
41
|
+
export function interpolateEvalBindings(content, bindings) {
|
|
42
|
+
// Protect escaped braces: \{ → placeholder
|
|
43
|
+
const escaped = content.replaceAll("\\{", ESCAPED_BRACE_PLACEHOLDER);
|
|
44
|
+
// Run interpolation on the protected content.
|
|
45
|
+
// Supports dotted paths: {pr.meta.number} traverses bindings.pr.meta.number
|
|
46
|
+
const interpolated = escaped.replace(BARE_BINDING_RE, (match, key) => {
|
|
47
|
+
const parts = key.split(".");
|
|
48
|
+
if (!(parts[0] in bindings))
|
|
49
|
+
return match;
|
|
50
|
+
let value = bindings;
|
|
51
|
+
for (let i = 0; i < parts.length; i++) {
|
|
52
|
+
if (value == null || typeof value !== "object")
|
|
53
|
+
return match;
|
|
54
|
+
const obj = value;
|
|
55
|
+
if (i < parts.length - 1 && !(parts[i] in obj))
|
|
56
|
+
return match;
|
|
57
|
+
value = obj[parts[i]];
|
|
58
|
+
}
|
|
59
|
+
return String(value);
|
|
60
|
+
});
|
|
61
|
+
// Restore escaped braces: placeholder → literal {
|
|
62
|
+
return interpolated.replaceAll(ESCAPED_BRACE_PLACEHOLDER, "{");
|
|
63
|
+
}
|