@executablemd/testing 0.3.1 → 0.4.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 (94) hide show
  1. package/esm/{testing/npm/src/src → src}/assertions.js +15 -24
  2. package/esm/{testing/npm/src/src → src}/handlers.js +140 -10
  3. package/esm/{testing/npm/src/src → src}/vocabulary.js +3 -0
  4. package/package.json +3 -3
  5. package/types/src/assertions.d.ts +11 -1
  6. package/types/src/handlers.d.ts +1 -0
  7. package/esm/core/mod.js +0 -55
  8. package/esm/core/src/api.js +0 -31
  9. package/esm/core/src/collect.js +0 -24
  10. package/esm/core/src/component-api.js +0 -47
  11. package/esm/core/src/content-context.js +0 -23
  12. package/esm/core/src/deno-compiler.js +0 -51
  13. package/esm/core/src/errors.js +0 -13
  14. package/esm/core/src/eval-context.js +0 -13
  15. package/esm/core/src/eval-handler.js +0 -79
  16. package/esm/core/src/eval-interpolate.js +0 -63
  17. package/esm/core/src/eval-transform.js +0 -378
  18. package/esm/core/src/execute.js +0 -378
  19. package/esm/core/src/expand.js +0 -988
  20. package/esm/core/src/frontmatter.js +0 -98
  21. package/esm/core/src/heal.js +0 -29
  22. package/esm/core/src/interpolate.js +0 -39
  23. package/esm/core/src/modifiers/daemon.js +0 -60
  24. package/esm/core/src/modifiers/persist.js +0 -35
  25. package/esm/core/src/modifiers/timeout.js +0 -55
  26. package/esm/core/src/modifiers.js +0 -113
  27. package/esm/core/src/output/normalize.js +0 -33
  28. package/esm/core/src/output/terminal.js +0 -20
  29. package/esm/core/src/render.js +0 -30
  30. package/esm/core/src/replay-stream.js +0 -58
  31. package/esm/core/src/sample-api.js +0 -45
  32. package/esm/core/src/scanner.js +0 -632
  33. package/esm/core/src/temp-file-compiler.js +0 -68
  34. package/esm/core/src/types.js +0 -7
  35. package/esm/core/src/validate.js +0 -82
  36. package/esm/durable-streams/combinators.js +0 -227
  37. package/esm/durable-streams/context.js +0 -13
  38. package/esm/durable-streams/divergence.js +0 -56
  39. package/esm/durable-streams/each.js +0 -193
  40. package/esm/durable-streams/effect.js +0 -273
  41. package/esm/durable-streams/ephemeral.js +0 -80
  42. package/esm/durable-streams/errors.js +0 -92
  43. package/esm/durable-streams/http-stream.js +0 -207
  44. package/esm/durable-streams/mod.js +0 -34
  45. package/esm/durable-streams/operations.js +0 -88
  46. package/esm/durable-streams/replay-guard.js +0 -87
  47. package/esm/durable-streams/replay-index.js +0 -132
  48. package/esm/durable-streams/run.js +0 -137
  49. package/esm/durable-streams/serialize.js +0 -65
  50. package/esm/durable-streams/stream.js +0 -56
  51. package/esm/durable-streams/types.js +0 -10
  52. package/esm/runtime/apis.js +0 -237
  53. package/esm/runtime/find-free-port.js +0 -33
  54. package/esm/runtime/mod.js +0 -20
  55. /package/esm/{testing/npm/src/_dnt.polyfills.js → _dnt.polyfills.js} +0 -0
  56. /package/esm/{testing/npm/src/_dnt.shims.js → _dnt.shims.js} +0 -0
  57. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/almost_equals.js +0 -0
  58. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/array_includes.js +0 -0
  59. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/assert.js +0 -0
  60. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/assertion_error.js +0 -0
  61. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/equal.js +0 -0
  62. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/equals.js +0 -0
  63. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/exists.js +0 -0
  64. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/fail.js +0 -0
  65. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/false.js +0 -0
  66. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/greater.js +0 -0
  67. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/greater_or_equal.js +0 -0
  68. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/instance_of.js +0 -0
  69. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/is_error.js +0 -0
  70. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/less.js +0 -0
  71. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/less_or_equal.js +0 -0
  72. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/match.js +0 -0
  73. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/mod.js +0 -0
  74. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/not_equals.js +0 -0
  75. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/not_instance_of.js +0 -0
  76. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/not_match.js +0 -0
  77. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/not_strict_equals.js +0 -0
  78. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/object_match.js +0 -0
  79. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/rejects.js +0 -0
  80. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/strict_equals.js +0 -0
  81. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/string_includes.js +0 -0
  82. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/throws.js +0 -0
  83. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/unimplemented.js +0 -0
  84. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/assert/1.0.19/unreachable.js +0 -0
  85. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/internal/1.0.14/build_message.js +0 -0
  86. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/internal/1.0.14/diff.js +0 -0
  87. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/internal/1.0.14/diff_str.js +0 -0
  88. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/internal/1.0.14/format.js +0 -0
  89. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/internal/1.0.14/styles.js +0 -0
  90. /package/esm/{testing/npm/src/deps → deps}/jsr.io/@std/internal/1.0.14/types.js +0 -0
  91. /package/esm/{testing/npm/src/mod.js → mod.js} +0 -0
  92. /package/esm/{testing/npm/src/src → src}/journal.js +0 -0
  93. /package/esm/{testing/npm/src/src → src}/test-api.js +0 -0
  94. /package/esm/{testing/npm/src/src → src}/use-testing.js +0 -0
@@ -46,18 +46,12 @@ function requireRegExp(value) {
46
46
  }
47
47
  throw new AssertionError("match assertions require a RegExp through the expected prop — use expected={/pattern/}");
48
48
  }
49
- // ---------------------------------------------------------------------------
50
- // Live expression evaluation
51
- // ---------------------------------------------------------------------------
52
49
  const IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
53
- function evaluateExpression(expression, values) {
50
+ export function evaluateExpression(expression, values) {
54
51
  const names = Object.keys(values).filter((name) => IDENTIFIER_RE.test(name));
55
52
  const fn = new Function(...names, `return (${expression});`);
56
53
  return fn(...names.map((name) => values[name]));
57
54
  }
58
- // ---------------------------------------------------------------------------
59
- // Guarded value formatting — runs only AFTER the assertion outcome is fixed
60
- // ---------------------------------------------------------------------------
61
55
  function safeFormat(value) {
62
56
  try {
63
57
  if (typeof value === "string") {
@@ -81,9 +75,6 @@ function safeFormat(value) {
81
75
  }
82
76
  }
83
77
  }
84
- // ---------------------------------------------------------------------------
85
- // Diagnostic-carrying assertion error
86
- // ---------------------------------------------------------------------------
87
78
  /**
88
79
  * An assertion failure enriched with its Markdown diagnostic. Still an
89
80
  * `AssertionError`, so containment and classification treat it as the
@@ -100,9 +91,6 @@ export class AssertionDiagnostic extends AssertionError {
100
91
  this.cause = cause;
101
92
  }
102
93
  }
103
- // ---------------------------------------------------------------------------
104
- // expandAssertion
105
- // ---------------------------------------------------------------------------
106
94
  const KIND_PROPS = {
107
95
  "unary-truthy": { allowed: ["expr", "msg"], required: ["expr"] },
108
96
  "unary-exists": { allowed: ["actual", "msg"], required: ["actual"] },
@@ -111,9 +99,20 @@ const KIND_PROPS = {
111
99
  match: { allowed: ["actual", "expected", "msg"], required: ["actual", "expected"] },
112
100
  numeric: { allowed: ["actual", "expected", "msg"], required: ["actual", "expected"] },
113
101
  };
114
- function validationError(name, message) {
102
+ export function validationError(name, message) {
115
103
  return { type: "error", message: `<${name}> ${message}`, source: name };
116
104
  }
105
+ // Outside a test the throw would abort before the segment could render, so
106
+ // emit the visible diagnostic first.
107
+ export function* failVisiblyThenThrow(name, msg, detail, failure) {
108
+ const diagnostic = buildDiagnostic(name, "failed", msg, detail, failure);
109
+ const visible = (yield* testing) || (yield* verbose);
110
+ const inTestScope = yield* inTest;
111
+ if (visible && !inTestScope) {
112
+ yield* DocumentOutput.operations.output(diagnostic);
113
+ }
114
+ throw new AssertionDiagnostic(failure, diagnostic, detail);
115
+ }
117
116
  /**
118
117
  * Expand one assertion component: validate props, resolve raw values, run the
119
118
  * @std/assert function, then build the diagnostic. Returns diagnostic text on
@@ -215,15 +214,7 @@ export function* expandAssertion(assertion, invocation, ctx) {
215
214
  }
216
215
  }
217
216
  if (failure) {
218
- const diagnostic = buildDiagnostic(assertion.name, "failed", values.msg, detail, failure);
219
- const visible = (yield* testing) || (yield* verbose);
220
- const inTestScope = yield* inTest;
221
- if (visible && !inTestScope) {
222
- // Outside a test the throw below aborts expansion before the segment
223
- // could render — emit the diagnostic directly so it reaches the output.
224
- yield* DocumentOutput.operations.output(diagnostic);
225
- }
226
- throw new AssertionDiagnostic(failure, diagnostic, detail);
217
+ yield* failVisiblyThenThrow(assertion.name, values.msg, detail, failure);
227
218
  }
228
219
  const visible = (yield* testing) || (yield* verbose);
229
220
  if (!visible) {
@@ -231,7 +222,7 @@ export function* expandAssertion(assertion, invocation, ctx) {
231
222
  }
232
223
  return [{ type: "text", content: buildDiagnostic(assertion.name, "passed", values.msg, detail) }];
233
224
  }
234
- function buildDiagnostic(name, outcome, msg, detail, failure) {
225
+ export function buildDiagnostic(name, outcome, msg, detail, failure) {
235
226
  const icon = outcome === "passed" ? "✅" : "❌";
236
227
  const lines = [`> ${icon} **${name}** ${outcome}${msg ? ` — ${msg}` : ""}`];
237
228
  if (detail.actual !== undefined) {
@@ -9,13 +9,10 @@ import { ensure, scoped, spawn, suspend, withResolvers } from "effection";
9
9
  import { timebox } from "@effectionx/timebox";
10
10
  import { unbox, useEvalScope } from "@effectionx/scope-eval";
11
11
  import { AssertionError } from "../deps/jsr.io/@std/assert/1.0.19/mod.js";
12
- import { Component, env, evalScope } from "@executablemd/core";
13
- import { Test, boundary, inTest, record, testing } from "./test-api.js";
14
- import { AssertionDiagnostic, expandAssertion } from "./assertions.js";
12
+ import { Component, env, evalScope, validateBindingName } from "@executablemd/core";
13
+ import { Test, boundary, inTest, record, testing, verbose } from "./test-api.js";
14
+ import { AssertionDiagnostic, buildDiagnostic, evaluateExpression, expandAssertion, failVisiblyThenThrow, validationError, } from "./assertions.js";
15
15
  import { persistBoundaryOutcome, persistTestResult } from "./journal.js";
16
- // ---------------------------------------------------------------------------
17
- // Contained-failure carriers
18
- // ---------------------------------------------------------------------------
19
16
  /** An ErrorSegment raised anywhere inside a test body. */
20
17
  class RaisedSegmentError extends Error {
21
18
  name = "RaisedSegmentError";
@@ -25,6 +22,14 @@ class RaisedSegmentError extends Error {
25
22
  this.segment = segment;
26
23
  }
27
24
  }
25
+ class CapturedRaise extends Error {
26
+ name = "CapturedRaise";
27
+ segment;
28
+ constructor(segment) {
29
+ super(segment.message);
30
+ this.segment = segment;
31
+ }
32
+ }
28
33
  /** A failure while dismantling an established test scope or lease. */
29
34
  class TeardownError extends Error {
30
35
  name = "TeardownError";
@@ -188,11 +193,136 @@ export function createTestHandlers(options) {
188
193
  }
189
194
  return testOutput;
190
195
  }
191
- return { expandTesting, expandTest, expandAssertion };
196
+ function* expandAssertThrows(invocation, ctx) {
197
+ for (const propName of [
198
+ ...Object.keys(invocation.props),
199
+ ...Object.keys(invocation.expressions),
200
+ ]) {
201
+ if (propName !== "message" && propName !== "as") {
202
+ return [
203
+ validationError("AssertThrows", `does not accept a "${propName}" prop (allowed: message, as).`),
204
+ ];
205
+ }
206
+ }
207
+ if (!("message" in invocation.props) && !("message" in invocation.expressions)) {
208
+ return [validationError("AssertThrows", 'requires a "message" prop.')];
209
+ }
210
+ if ("as" in invocation.expressions) {
211
+ return [
212
+ validationError("AssertThrows", 'the "as" prop must be a string literal, not an expression.'),
213
+ ];
214
+ }
215
+ const currentEnv = yield* env;
216
+ const merged = {
217
+ ...(ctx.projectedEnv?.values ?? {}),
218
+ ...(currentEnv?.values ?? {}),
219
+ };
220
+ let matcher;
221
+ if ("message" in invocation.expressions) {
222
+ let evaluated;
223
+ try {
224
+ evaluated = evaluateExpression(invocation.expressions["message"], merged);
225
+ }
226
+ catch (error) {
227
+ return [
228
+ validationError("AssertThrows", `failed to evaluate the "message" expression: ${error instanceof Error ? error.message : String(error)}`),
229
+ ];
230
+ }
231
+ if (typeof evaluated === "string" || evaluated instanceof RegExp) {
232
+ matcher = evaluated;
233
+ }
234
+ else {
235
+ return [
236
+ validationError("AssertThrows", `the "message" expression must evaluate to a string or RegExp, got ${typeof evaluated}.`),
237
+ ];
238
+ }
239
+ }
240
+ else {
241
+ const literal = invocation.props["message"];
242
+ if (typeof literal !== "string") {
243
+ return [validationError("AssertThrows", 'the "message" prop must be a string.')];
244
+ }
245
+ matcher = literal;
246
+ }
247
+ let binding;
248
+ if ("as" in invocation.props) {
249
+ if (!currentEnv) {
250
+ return [
251
+ validationError("AssertThrows", 'binding with "as" requires an eval scope in context.'),
252
+ ];
253
+ }
254
+ const parsed = validateBindingName(invocation.props["as"]);
255
+ if (!parsed.ok) {
256
+ return [validationError("AssertThrows", `the "as" prop ${parsed.error}`)];
257
+ }
258
+ if (parsed.value === undefined) {
259
+ return [validationError("AssertThrows", 'the "as" prop must be a non-empty string.')];
260
+ }
261
+ binding = parsed.value;
262
+ }
263
+ // Install a scope-local raise interceptor and expand the body. Inside a
264
+ // <Test> the enclosing interceptor is installed in an outer scope, and
265
+ // outer middleware runs first (see @effectionx/context-api ordering), so
266
+ // it throws RaisedSegmentError before this hook is reached; outside a test
267
+ // this hook is the only interceptor and throws CapturedRaise. Catching
268
+ // both makes capture behave identically inside and outside <Test>. The
269
+ // first raised error stops expansion, so later children never execute.
270
+ let captured;
271
+ try {
272
+ yield* scoped(function* () {
273
+ yield* Component.around({
274
+ // deno-lint-ignore require-yield
275
+ *raise([segment]) {
276
+ throw new CapturedRaise(segment);
277
+ },
278
+ });
279
+ yield* ctx.expand(invocation.children);
280
+ });
281
+ }
282
+ catch (error) {
283
+ if (error instanceof CapturedRaise || error instanceof RaisedSegmentError) {
284
+ captured = error.segment;
285
+ }
286
+ else {
287
+ throw error;
288
+ }
289
+ }
290
+ if (!captured) {
291
+ yield* failAssertThrows(matcher, undefined);
292
+ }
293
+ else if (!matchesMessage(matcher, captured.message)) {
294
+ yield* failAssertThrows(matcher, captured.message);
295
+ }
296
+ if (binding !== undefined && currentEnv) {
297
+ currentEnv.values[binding] = captured;
298
+ }
299
+ const visible = (yield* testing) || (yield* verbose);
300
+ if (!visible) {
301
+ return [];
302
+ }
303
+ return [
304
+ {
305
+ type: "text",
306
+ content: buildDiagnostic("AssertThrows", "passed", describeMatcher(matcher), {}),
307
+ },
308
+ ];
309
+ }
310
+ return { expandTesting, expandTest, expandAssertion, expandAssertThrows };
311
+ }
312
+ function* failAssertThrows(matcher, actual) {
313
+ const expected = describeMatcher(matcher);
314
+ const failure = new AssertionError(actual === undefined
315
+ ? `AssertThrows: expected the body to raise an error matching ${expected}, but none was raised`
316
+ : `AssertThrows: raised error ${JSON.stringify(actual)} did not match ${expected}`);
317
+ yield* failVisiblyThenThrow("AssertThrows", undefined, { expected, actual: actual ?? "(none)" }, failure);
318
+ throw failure;
319
+ }
320
+ function matchesMessage(matcher, message) {
321
+ return matcher instanceof RegExp ? matcher.test(message) : message.includes(matcher);
322
+ }
323
+ function describeMatcher(matcher) {
324
+ return matcher instanceof RegExp ? String(matcher) : `substring ${JSON.stringify(matcher)}`;
192
325
  }
193
- // ---------------------------------------------------------------------------
194
- // Result classification and diagnostics
195
- // ---------------------------------------------------------------------------
196
326
  function formatLocation(invocation) {
197
327
  const position = invocation.position;
198
328
  if (!position) {
@@ -39,6 +39,9 @@ export function* installHandlers(handlers, options) {
39
39
  if (invocation.name === "Test") {
40
40
  return { segments: yield* handlers.expandTest(invocation, ctx) };
41
41
  }
42
+ if (invocation.name === "AssertThrows") {
43
+ return { segments: yield* handlers.expandAssertThrows(invocation, ctx) };
44
+ }
42
45
  const assertion = ASSERTIONS.get(invocation.name);
43
46
  if (assertion) {
44
47
  return { segments: yield* handlers.expandAssertion(assertion, invocation, ctx) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@executablemd/testing",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Testing vocabulary for executable.md documents: <Testing>, <Test>, and assertion components.",
5
5
  "homepage": "https://executable.md",
6
6
  "repository": {
@@ -26,8 +26,8 @@
26
26
  "@effectionx/context-api": "0.6.0",
27
27
  "@effectionx/scope-eval": "0.1.3",
28
28
  "@effectionx/timebox": "0.4.3",
29
- "@executablemd/core": "^0.3.1",
30
- "@executablemd/durable-streams": "^0.3.1",
29
+ "@executablemd/core": "^0.4.0",
30
+ "@executablemd/durable-streams": "^0.4.0",
31
31
  "effection": "4.1.0-alpha.7",
32
32
  "@std/assert": "npm:@jsr/std__assert@^1.0.17"
33
33
  },
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { AssertionError } from "../deps/jsr.io/@std/assert/1.0.19/mod.js";
15
15
  import type { Operation } from "effection";
16
- import type { ComponentInvocation, InvocationContext, Segment } from "@executablemd/core";
16
+ import type { ComponentInvocation, ErrorSegment, InvocationContext, Segment } from "@executablemd/core";
17
17
  type AssertionKind = "unary-truthy" | "unary-exists" | "binary-eq" | "string-includes" | "match" | "numeric";
18
18
  export interface AssertionEntry {
19
19
  name: string;
@@ -29,6 +29,7 @@ interface ResolvedValues {
29
29
  msg?: string;
30
30
  }
31
31
  export declare const ASSERTIONS: Map<string, AssertionEntry>;
32
+ export declare function evaluateExpression(expression: string, values: Record<string, unknown>): unknown;
32
33
  /**
33
34
  * An assertion failure enriched with its Markdown diagnostic. Still an
34
35
  * `AssertionError`, so containment and classification treat it as the
@@ -46,10 +47,19 @@ export declare class AssertionDiagnostic extends AssertionError {
46
47
  expected?: string;
47
48
  });
48
49
  }
50
+ export declare function validationError(name: string, message: string): ErrorSegment;
51
+ export declare function failVisiblyThenThrow(name: string, msg: string | undefined, detail: {
52
+ actual?: string;
53
+ expected?: string;
54
+ }, failure: Error): Operation<never>;
49
55
  /**
50
56
  * Expand one assertion component: validate props, resolve raw values, run the
51
57
  * @std/assert function, then build the diagnostic. Returns diagnostic text on
52
58
  * a visible pass; throws `AssertionDiagnostic` on failure.
53
59
  */
54
60
  export declare function expandAssertion(assertion: AssertionEntry, invocation: ComponentInvocation, ctx: InvocationContext): Operation<Segment[]>;
61
+ export declare function buildDiagnostic(name: string, outcome: "passed" | "failed", msg: string | undefined, detail: {
62
+ actual?: string;
63
+ expected?: string;
64
+ }, failure?: Error): string;
55
65
  export {};
@@ -12,6 +12,7 @@ export interface TestHandlers {
12
12
  expandTesting(invocation: ComponentInvocation, ctx: InvocationContext): Operation<Segment[]>;
13
13
  expandTest(invocation: ComponentInvocation, ctx: InvocationContext): Operation<Segment[]>;
14
14
  expandAssertion(assertion: AssertionEntry, invocation: ComponentInvocation, ctx: InvocationContext): Operation<Segment[]>;
15
+ expandAssertThrows(invocation: ComponentInvocation, ctx: InvocationContext): Operation<Segment[]>;
15
16
  }
16
17
  export declare function createTestHandlers(options: {
17
18
  timeoutMs: number;
package/esm/core/mod.js DELETED
@@ -1,55 +0,0 @@
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";
@@ -1,31 +0,0 @@
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
- });
@@ -1,24 +0,0 @@
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
- }
@@ -1,47 +0,0 @@
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;
@@ -1,23 +0,0 @@
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
- }
@@ -1,51 +0,0 @@
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
- }
@@ -1,13 +0,0 @@
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
- }
@@ -1,13 +0,0 @@
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
- }