@mdsnai/sdk 0.1.0 → 0.2.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 (78) hide show
  1. package/README.md +40 -0
  2. package/dist/core/document/markdown.js +2 -4
  3. package/dist/core/document/page-definition.js +1 -2
  4. package/dist/core/index.d.ts +2 -1
  5. package/dist/core/index.js +9 -1
  6. package/dist/core/model/block.d.ts +4 -8
  7. package/dist/core/model/block.js +6 -0
  8. package/dist/core/model/document.d.ts +0 -2
  9. package/dist/core/model/index.d.ts +1 -2
  10. package/dist/core/model/input.d.ts +1 -2
  11. package/dist/core/protocol/mdsn.d.ts +0 -2
  12. package/dist/core/protocol/mdsn.js +3 -17
  13. package/dist/core/protocol/statements.d.ts +3 -8
  14. package/dist/core/protocol/statements.js +46 -71
  15. package/dist/core/protocol/validation.d.ts +2 -3
  16. package/dist/core/protocol/validation.js +21 -11
  17. package/dist/core/utils/html.d.ts +6 -0
  18. package/dist/core/utils/html.js +28 -0
  19. package/dist/core/utils/index.d.ts +2 -0
  20. package/dist/core/utils/index.js +12 -0
  21. package/dist/core/utils/logger.d.ts +12 -0
  22. package/dist/core/utils/logger.js +45 -0
  23. package/dist/framework/create-framework-app.d.ts +1 -0
  24. package/dist/framework/create-framework-app.js +1 -0
  25. package/dist/framework/hosted-app.d.ts +21 -0
  26. package/dist/framework/hosted-app.js +123 -33
  27. package/dist/framework/index.d.ts +2 -0
  28. package/dist/framework/index.js +3 -1
  29. package/dist/framework/site-app.d.ts +1 -0
  30. package/dist/framework/site-app.js +1 -0
  31. package/dist/index.d.ts +5 -5
  32. package/dist/index.js +18 -1
  33. package/dist/server/action-context.d.ts +11 -0
  34. package/dist/server/action-context.js +26 -0
  35. package/dist/server/action-host.d.ts +2 -3
  36. package/dist/server/action-host.js +4 -2
  37. package/dist/server/action-inputs.d.ts +3 -0
  38. package/dist/server/action-inputs.js +178 -0
  39. package/dist/server/action-runtime.d.ts +3 -3
  40. package/dist/server/action-runtime.js +2 -21
  41. package/dist/server/action.d.ts +1 -9
  42. package/dist/server/action.js +5 -1
  43. package/dist/server/build.js +4 -0
  44. package/dist/server/error-fragments.d.ts +46 -0
  45. package/dist/server/error-fragments.js +77 -0
  46. package/dist/server/index.d.ts +9 -2
  47. package/dist/server/index.js +17 -1
  48. package/dist/server/init.js +13 -13
  49. package/dist/server/markdown.d.ts +2 -6
  50. package/dist/server/markdown.js +11 -10
  51. package/dist/server/server.d.ts +2 -1
  52. package/dist/server/server.js +17 -8
  53. package/dist/server/session.d.ts +40 -0
  54. package/dist/server/session.js +220 -0
  55. package/dist/web/block-runtime.js +15 -17
  56. package/dist/web/fragment-render.d.ts +0 -2
  57. package/dist/web/fragment-render.js +0 -1
  58. package/dist/web/i18n.d.ts +0 -2
  59. package/dist/web/i18n.js +0 -4
  60. package/dist/web/index.d.ts +1 -1
  61. package/dist/web/index.js +2 -1
  62. package/dist/web/page-bootstrap.js +0 -1
  63. package/dist/web/page-client-runtime.d.ts +2 -13
  64. package/dist/web/page-client-runtime.js +1 -16
  65. package/dist/web/page-client-script.d.ts +0 -1
  66. package/dist/web/page-client-script.js +172 -160
  67. package/dist/web/page-html.js +4 -11
  68. package/dist/web/page-render.d.ts +1 -1
  69. package/dist/web/page-render.js +13 -21
  70. package/package.json +1 -1
  71. package/dist/core/action/execution.d.ts +0 -4
  72. package/dist/core/action/execution.js +0 -57
  73. package/dist/core/action/index.d.ts +0 -2
  74. package/dist/core/action/index.js +0 -7
  75. package/dist/core/action/types.d.ts +0 -19
  76. package/dist/core/action/types.js +0 -2
  77. package/dist/core/model/schema.d.ts +0 -4
  78. package/dist/core/model/schema.js +0 -2
package/README.md CHANGED
@@ -4,6 +4,37 @@
4
4
 
5
5
  MDSN keeps page content and page interaction in the same source by combining a Markdown body with an executable `mdsn` block.
6
6
 
7
+ ## Start With One Package
8
+
9
+ For most projects, start with:
10
+
11
+ ```ts
12
+ import { createFrameworkApp, defineConfig } from "@mdsnai/sdk";
13
+ ```
14
+
15
+ ```ts
16
+ import {
17
+ createHostedApp,
18
+ createActionContextFromRequest,
19
+ defineActions,
20
+ renderHostedPage,
21
+ renderMarkdownFragment,
22
+ renderMarkdownValue,
23
+ } from "@mdsnai/sdk";
24
+ ```
25
+
26
+ ```ts
27
+ import { parsePage, parseFragment } from "@mdsnai/sdk";
28
+ ```
29
+
30
+ Use the root entry point for the common paths:
31
+
32
+ - build a site with the built-in framework
33
+ - host MDSN pages and actions inside your own server
34
+ - render the UI yourself with React or Vue
35
+
36
+ Move to `@mdsnai/sdk/framework`, `@mdsnai/sdk/server`, `@mdsnai/sdk/web`, or `@mdsnai/sdk/core` only when you want stricter boundaries or narrower imports.
37
+
7
38
  ## Why MDSN
8
39
 
9
40
  Plain Markdown is good for content, but weak at expressing interaction.
@@ -12,6 +43,14 @@ Once a page needs inputs, actions, partial updates, or navigation, that structur
12
43
 
13
44
  MDSN makes that interaction layer explicit while keeping the page source readable for humans, AI agents, and agentic AI systems.
14
45
 
46
+ In MDSN, page content is not just presentation. It is also shared prompt context for AI agents.
47
+
48
+ That means the same Markdown source can carry:
49
+
50
+ - content for humans to read
51
+ - state and task context for AI agents to interpret
52
+ - explicit interaction structure for both sides to continue from
53
+
15
54
  ## What This Package Includes
16
55
 
17
56
  - the MDSN parser and core model
@@ -63,5 +102,6 @@ The starter generates a minimal runnable site with:
63
102
  - [Getting Started](https://docs.mdsn.ai/docs/getting-started)
64
103
  - [Framework Development](https://docs.mdsn.ai/docs/site-development)
65
104
  - [Server Development](https://docs.mdsn.ai/docs/server-development)
105
+ - [HTTP Content Negotiation and Shared Interaction](https://docs.mdsn.ai/docs/shared-interaction)
66
106
  - [Action Reference](https://docs.mdsn.ai/docs/action-reference)
67
107
  - [SDK Reference](https://docs.mdsn.ai/docs/sdk-reference)
@@ -2,9 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractExecutableMdsnBlocks = extractExecutableMdsnBlocks;
4
4
  exports.extractBlockAnchors = extractBlockAnchors;
5
- function trimTrailingBlankLines(value) {
6
- return value.replace(/\n+$/u, "\n");
7
- }
5
+ const utils_1 = require("../utils");
8
6
  function extractExecutableMdsnBlocks(markdown) {
9
7
  const lines = markdown.split(/\r?\n/);
10
8
  const keptLines = [];
@@ -49,7 +47,7 @@ function extractExecutableMdsnBlocks(markdown) {
49
47
  index += 1;
50
48
  }
51
49
  return {
52
- markdownWithoutMdsn: trimTrailingBlankLines(keptLines.join("\n")),
50
+ markdownWithoutMdsn: (0, utils_1.trimTrailingBlankLines)(keptLines.join("\n")),
53
51
  blocks,
54
52
  };
55
53
  }
@@ -13,11 +13,10 @@ function parsePageDefinition(raw) {
13
13
  }
14
14
  const parsed = (0, mdsn_1.parseMdsnBlocks)(blocks);
15
15
  const blockAnchors = (0, markdown_1.extractBlockAnchors)(markdownWithoutMdsn).map((name) => ({ name }));
16
- (0, validation_1.validateDocumentStructure)(parsed.schemas, parsed.blocks, blockAnchors);
16
+ (0, validation_1.validateDocumentStructure)(parsed.blocks, blockAnchors);
17
17
  return {
18
18
  frontmatter,
19
19
  markdown: markdownWithoutMdsn,
20
- schemas: parsed.schemas,
21
20
  blocks: parsed.blocks,
22
21
  blockAnchors,
23
22
  };
@@ -1,3 +1,4 @@
1
1
  export { parsePageDefinition } from "./document/page-definition";
2
2
  export type { FrontmatterData } from "./model/document";
3
- export type { BlockAnchorDefinition, BlockDefinition, DocumentDefinition, InputDefinition, InputType, ReadDefinition, RedirectDefinition, SchemaDefinition, WriteDefinition, } from "./model";
3
+ export type { BlockAnchorDefinition, BlockDefinition, DocumentDefinition, InputDefinition, InputType, ReadDefinition, WriteDefinition, } from "./model";
4
+ export { escapeHtml, escapeRegExp, validateInputLength, trimTrailingBlankLines, MAX_INPUT_LENGTH, MAX_IDENTIFIER_LENGTH, createLogger, type Logger, type LogLevel, type CreateLoggerOptions, } from "./utils";
@@ -1,5 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parsePageDefinition = void 0;
3
+ exports.createLogger = exports.MAX_IDENTIFIER_LENGTH = exports.MAX_INPUT_LENGTH = exports.trimTrailingBlankLines = exports.validateInputLength = exports.escapeRegExp = exports.escapeHtml = exports.parsePageDefinition = void 0;
4
4
  var page_definition_1 = require("./document/page-definition");
5
5
  Object.defineProperty(exports, "parsePageDefinition", { enumerable: true, get: function () { return page_definition_1.parsePageDefinition; } });
6
+ var utils_1 = require("./utils");
7
+ Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return utils_1.escapeHtml; } });
8
+ Object.defineProperty(exports, "escapeRegExp", { enumerable: true, get: function () { return utils_1.escapeRegExp; } });
9
+ Object.defineProperty(exports, "validateInputLength", { enumerable: true, get: function () { return utils_1.validateInputLength; } });
10
+ Object.defineProperty(exports, "trimTrailingBlankLines", { enumerable: true, get: function () { return utils_1.trimTrailingBlankLines; } });
11
+ Object.defineProperty(exports, "MAX_INPUT_LENGTH", { enumerable: true, get: function () { return utils_1.MAX_INPUT_LENGTH; } });
12
+ Object.defineProperty(exports, "MAX_IDENTIFIER_LENGTH", { enumerable: true, get: function () { return utils_1.MAX_IDENTIFIER_LENGTH; } });
13
+ Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return utils_1.createLogger; } });
@@ -1,10 +1,13 @@
1
1
  import type { InputDefinition } from "./input";
2
+ export declare const STREAM_ACCEPT = "text/event-stream";
3
+ export declare function isStreamAccept(accept?: string): boolean;
2
4
  export interface ReadDefinition {
3
5
  id: string;
4
6
  block: string;
5
- name: string;
7
+ name?: string;
6
8
  target: string;
7
9
  inputs: string[];
10
+ accept?: string;
8
11
  order: number;
9
12
  }
10
13
  export interface WriteDefinition {
@@ -15,16 +18,9 @@ export interface WriteDefinition {
15
18
  inputs: string[];
16
19
  order: number;
17
20
  }
18
- export interface RedirectDefinition {
19
- id: string;
20
- block: string;
21
- target: string;
22
- order: number;
23
- }
24
21
  export interface BlockDefinition {
25
22
  name: string;
26
23
  inputs: InputDefinition[];
27
24
  reads: ReadDefinition[];
28
25
  writes: WriteDefinition[];
29
- redirects: RedirectDefinition[];
30
26
  }
@@ -1,2 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STREAM_ACCEPT = void 0;
4
+ exports.isStreamAccept = isStreamAccept;
5
+ exports.STREAM_ACCEPT = "text/event-stream";
6
+ function isStreamAccept(accept) {
7
+ return String(accept ?? "").toLowerCase() === exports.STREAM_ACCEPT;
8
+ }
@@ -1,5 +1,4 @@
1
1
  import type { BlockDefinition } from "./block";
2
- import type { SchemaDefinition } from "./schema";
3
2
  export type FrontmatterData = Record<string, unknown>;
4
3
  export interface BlockAnchorDefinition {
5
4
  name: string;
@@ -7,7 +6,6 @@ export interface BlockAnchorDefinition {
7
6
  export interface DocumentDefinition {
8
7
  frontmatter: FrontmatterData;
9
8
  markdown: string;
10
- schemas: SchemaDefinition[];
11
9
  blocks: BlockDefinition[];
12
10
  blockAnchors: BlockAnchorDefinition[];
13
11
  }
@@ -1,5 +1,4 @@
1
1
  export type { BlockAnchorDefinition, DocumentDefinition, FrontmatterData, } from "./document";
2
- export type { BlockDefinition, ReadDefinition, RedirectDefinition, WriteDefinition, } from "./block";
2
+ export type { BlockDefinition, ReadDefinition, WriteDefinition, } from "./block";
3
3
  export type { InputDefinition, InputType, } from "./input";
4
- export type { SchemaDefinition } from "./schema";
5
4
  export type { MarkdownFragmentDefinition } from "./fragment";
@@ -1,4 +1,4 @@
1
- export type InputType = "text" | "number" | "boolean" | "choice" | "file" | "json";
1
+ export type InputType = "text" | "number" | "boolean" | "choice" | "asset";
2
2
  export interface InputDefinition {
3
3
  id: string;
4
4
  block: string;
@@ -7,5 +7,4 @@ export interface InputDefinition {
7
7
  required: boolean;
8
8
  secret: boolean;
9
9
  options?: string[];
10
- schema?: string;
11
10
  }
@@ -1,6 +1,4 @@
1
1
  import type { BlockDefinition } from "../model/block";
2
- import type { SchemaDefinition } from "../model/schema";
3
2
  export declare function parseMdsnBlocks(blocks: string[]): {
4
- schemas: SchemaDefinition[];
5
3
  blocks: BlockDefinition[];
6
4
  };
@@ -8,11 +8,9 @@ function createBlock(name) {
8
8
  inputs: [],
9
9
  reads: [],
10
10
  writes: [],
11
- redirects: [],
12
11
  };
13
12
  }
14
13
  function parseMdsnBlocks(blocks) {
15
- const schemas = [];
16
14
  const documentBlocks = [];
17
15
  let currentBlock = null;
18
16
  for (const blockText of blocks) {
@@ -25,12 +23,6 @@ function parseMdsnBlocks(blocks) {
25
23
  continue;
26
24
  }
27
25
  if (!currentBlock) {
28
- if (line.startsWith("schema ")) {
29
- const parsed = (0, statements_1.parseSchemaBlock)(lines, index);
30
- schemas.push(parsed.schema);
31
- index = parsed.nextIndex;
32
- continue;
33
- }
34
26
  if (line.startsWith("block ")) {
35
27
  currentBlock = createBlock((0, statements_1.parseBlockHeaderLine)(line));
36
28
  documentBlocks.push(currentBlock);
@@ -47,26 +39,21 @@ function parseMdsnBlocks(blocks) {
47
39
  index += 1;
48
40
  continue;
49
41
  }
50
- if (line.startsWith("input ")) {
42
+ if (line.startsWith("INPUT ")) {
51
43
  currentBlock.inputs.push((0, statements_1.parseInputLine)(line, currentBlock.name));
52
44
  index += 1;
53
45
  continue;
54
46
  }
55
- if (line.startsWith("read ")) {
47
+ if (line.startsWith("GET ")) {
56
48
  currentBlock.reads.push((0, statements_1.parseReadOrWriteLine)(line, "read", currentBlock.name, (0, statements_1.getNextOperationOrder)(currentBlock)));
57
49
  index += 1;
58
50
  continue;
59
51
  }
60
- if (line.startsWith("write ")) {
52
+ if (line.startsWith("POST ")) {
61
53
  currentBlock.writes.push((0, statements_1.parseReadOrWriteLine)(line, "write", currentBlock.name, (0, statements_1.getNextOperationOrder)(currentBlock)));
62
54
  index += 1;
63
55
  continue;
64
56
  }
65
- if (line.startsWith("redirect ")) {
66
- currentBlock.redirects.push((0, statements_1.parseRedirectLine)(line, currentBlock.name, (0, statements_1.getNextOperationOrder)(currentBlock)));
67
- index += 1;
68
- continue;
69
- }
70
57
  throw new Error(`Unsupported MDSN statement: ${line}`);
71
58
  }
72
59
  }
@@ -74,7 +61,6 @@ function parseMdsnBlocks(blocks) {
74
61
  throw new Error(`Unterminated block declaration: ${currentBlock.name}`);
75
62
  }
76
63
  return {
77
- schemas,
78
64
  blocks: documentBlocks,
79
65
  };
80
66
  }
@@ -1,12 +1,7 @@
1
- import type { BlockDefinition, ReadDefinition, RedirectDefinition, WriteDefinition } from "../model/block";
1
+ import { type BlockDefinition, type ReadDefinition, type WriteDefinition } from "../model/block";
2
2
  import type { InputDefinition } from "../model/input";
3
- import type { SchemaDefinition } from "../model/schema";
4
3
  export declare function parseBlockHeaderLine(line: string): string;
5
4
  export declare function parseInputLine(line: string, blockName: string): InputDefinition;
6
- export declare function parseReadOrWriteLine(line: string, kind: "read" | "write", blockName: string, index: number): ReadDefinition | WriteDefinition;
7
- export declare function parseRedirectLine(line: string, blockName: string, index: number): RedirectDefinition;
8
- export declare function parseSchemaBlock(lines: string[], startIndex: number): {
9
- schema: SchemaDefinition;
10
- nextIndex: number;
11
- };
5
+ export declare function parseReadOrWriteLine(line: string, kind: "read", blockName: string, index: number): ReadDefinition;
6
+ export declare function parseReadOrWriteLine(line: string, kind: "write", blockName: string, index: number): WriteDefinition;
12
7
  export declare function getNextOperationOrder(block: BlockDefinition): number;
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseBlockHeaderLine = parseBlockHeaderLine;
4
4
  exports.parseInputLine = parseInputLine;
5
5
  exports.parseReadOrWriteLine = parseReadOrWriteLine;
6
- exports.parseRedirectLine = parseRedirectLine;
7
- exports.parseSchemaBlock = parseSchemaBlock;
8
6
  exports.getNextOperationOrder = getNextOperationOrder;
7
+ const block_1 = require("../model/block");
9
8
  function isIdentifier(value) {
10
9
  return /^[a-zA-Z_][\w-]*$/.test(value);
11
10
  }
@@ -28,13 +27,6 @@ function parseStringArrayLiteral(raw) {
28
27
  }
29
28
  return parsed;
30
29
  }
31
- function parseSchemaLiteral(raw) {
32
- const parsed = JSON.parse(raw);
33
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
34
- throw new Error("Schema body must be a JSON object");
35
- }
36
- return parsed;
37
- }
38
30
  function parseBlockHeaderLine(line) {
39
31
  const match = line.trim().match(/^block\s+([a-zA-Z_][\w-]*)\s*\{$/);
40
32
  if (!match) {
@@ -43,46 +35,71 @@ function parseBlockHeaderLine(line) {
43
35
  return match[1];
44
36
  }
45
37
  function parseInputLine(line, blockName) {
46
- const match = line
47
- .trim()
48
- .match(/^input\s+([a-zA-Z_][\w-]*)(!)?:\s*(text|number|boolean|choice|file|json)(?:\s+(secret))?(?:\s+(\[.*\]))?(?:\s+([a-zA-Z_][\w-]*))?$/);
38
+ const match = line.trim().match(/^INPUT\s+(text|number|boolean|choice|asset)(?:\s+(.*?))?\s*->\s*([a-zA-Z_][\w-]*)$/);
49
39
  if (!match) {
50
40
  throw new Error(`Invalid input declaration: ${line}`);
51
41
  }
52
- const [, name, requiredMarker, type, secretMarker, optionsLiteral, schemaName] = match;
42
+ const [, type, trailing, name] = match;
43
+ const tail = (trailing ?? "").trim();
44
+ const required = /\brequired\b/u.test(tail);
45
+ const secret = /\bsecret\b/u.test(tail);
46
+ const optionsMatch = tail.match(/(\[.*\])/u);
47
+ const optionsLiteral = optionsMatch?.[1];
48
+ const normalizedTail = tail
49
+ .replace(/\brequired\b/gu, "")
50
+ .replace(/\bsecret\b/gu, "")
51
+ .replace(/(\[.*\])/u, "")
52
+ .trim();
53
+ if (normalizedTail) {
54
+ throw new Error(`Invalid input declaration: ${line}`);
55
+ }
53
56
  const input = {
54
57
  id: `${blockName}::input::${name}`,
55
58
  block: blockName,
56
59
  name,
57
60
  type: type,
58
- required: requiredMarker === "!",
59
- secret: secretMarker === "secret",
61
+ required,
62
+ secret,
60
63
  };
61
64
  if (optionsLiteral) {
62
65
  input.options = parseStringArrayLiteral(optionsLiteral);
63
66
  }
64
- if (schemaName) {
65
- input.schema = schemaName;
66
- }
67
67
  if (input.type === "choice" && (!input.options || input.options.length === 0)) {
68
68
  throw new Error(`Choice input ${name} in block ${blockName} must define options`);
69
69
  }
70
- if (input.type === "json" && !input.schema) {
71
- throw new Error(`JSON input ${name} in block ${blockName} must define schema`);
70
+ if (input.type !== "choice" && input.options) {
71
+ throw new Error(`Only choice input ${name} in block ${blockName} can define options`);
72
72
  }
73
73
  return input;
74
74
  }
75
75
  function parseReadOrWriteLine(line, kind, blockName, index) {
76
- const match = line
77
- .trim()
78
- .match(/^(read|write)\s+([a-zA-Z_][\w-]*)\s*:\s*"([^"]+)"(?:\s*\(([^)]*)\))?$/);
76
+ if (kind === "read") {
77
+ const match = line.trim().match(/^GET\s+"([^"]+)"(?:\s*\(([^)]*)\))?(?:\s+accept:"([^"]+)")?(?:\s*->\s*([a-zA-Z_][\w-]*))?$/);
78
+ if (!match) {
79
+ throw new Error(`Invalid ${kind} declaration: ${line}`);
80
+ }
81
+ const [, target, rawInputs, accept, name] = match;
82
+ if (!name && !(0, block_1.isStreamAccept)(accept)) {
83
+ throw new Error(`Invalid ${kind} declaration: ${line}`);
84
+ }
85
+ if ((0, block_1.isStreamAccept)(accept) && name) {
86
+ throw new Error(`Invalid ${kind} declaration: ${line}`);
87
+ }
88
+ return {
89
+ id: `${blockName}::${kind}::${index}`,
90
+ block: blockName,
91
+ name,
92
+ target,
93
+ inputs: parseIdentifierList(rawInputs ?? ""),
94
+ accept,
95
+ order: index,
96
+ };
97
+ }
98
+ const match = line.trim().match(/^POST\s+"([^"]+)"\s*\(([^)]*)\)\s*->\s*([a-zA-Z_][\w-]*)$/);
79
99
  if (!match) {
80
100
  throw new Error(`Invalid ${kind} declaration: ${line}`);
81
101
  }
82
- const [, parsedKind, name, target, rawInputs] = match;
83
- if (parsedKind !== kind) {
84
- throw new Error(`Expected ${kind} declaration: ${line}`);
85
- }
102
+ const [, target, rawInputs, name] = match;
86
103
  const base = {
87
104
  id: `${blockName}::${kind}::${index}`,
88
105
  block: blockName,
@@ -91,50 +108,8 @@ function parseReadOrWriteLine(line, kind, blockName, index) {
91
108
  inputs: parseIdentifierList(rawInputs ?? ""),
92
109
  order: index,
93
110
  };
94
- return kind === "read" ? base : base;
95
- }
96
- function parseRedirectLine(line, blockName, index) {
97
- const match = line.trim().match(/^redirect\s+"([^"]+)"$/);
98
- if (!match) {
99
- throw new Error(`Invalid redirect declaration: ${line}`);
100
- }
101
- return {
102
- id: `${blockName}::redirect::${index}`,
103
- block: blockName,
104
- target: match[1],
105
- order: index,
106
- };
107
- }
108
- function parseSchemaBlock(lines, startIndex) {
109
- const firstLine = lines[startIndex].trim();
110
- const match = firstLine.match(/^schema\s+([a-zA-Z_][\w-]*)\s*(\{.*)?$/);
111
- if (!match) {
112
- throw new Error(`Invalid schema declaration: ${firstLine}`);
113
- }
114
- const name = match[1];
115
- let literal = (match[2] ?? "").trim();
116
- let braceDepth = (literal.match(/\{/g) ?? []).length - (literal.match(/\}/g) ?? []).length;
117
- let index = startIndex;
118
- while (braceDepth > 0) {
119
- index += 1;
120
- if (index >= lines.length) {
121
- throw new Error(`Unterminated schema declaration: ${name}`);
122
- }
123
- literal += "\n" + lines[index];
124
- braceDepth += (lines[index].match(/\{/g) ?? []).length;
125
- braceDepth -= (lines[index].match(/\}/g) ?? []).length;
126
- }
127
- if (!literal.startsWith("{")) {
128
- throw new Error(`schema ${name} must start with {`);
129
- }
130
- return {
131
- schema: {
132
- name,
133
- shape: parseSchemaLiteral(literal),
134
- },
135
- nextIndex: index + 1,
136
- };
111
+ return base;
137
112
  }
138
113
  function getNextOperationOrder(block) {
139
- return block.reads.length + block.writes.length + block.redirects.length;
114
+ return block.reads.length + block.writes.length;
140
115
  }
@@ -1,4 +1,3 @@
1
- import type { BlockDefinition } from "../model/block";
1
+ import { type BlockDefinition } from "../model/block";
2
2
  import type { BlockAnchorDefinition } from "../model/document";
3
- import type { SchemaDefinition } from "../model/schema";
4
- export declare function validateDocumentStructure(schemas: SchemaDefinition[], blocks: BlockDefinition[], blockAnchors: BlockAnchorDefinition[]): void;
3
+ export declare function validateDocumentStructure(blocks: BlockDefinition[], blockAnchors: BlockAnchorDefinition[]): void;
@@ -1,14 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateDocumentStructure = validateDocumentStructure;
4
- function validateDocumentStructure(schemas, blocks, blockAnchors) {
5
- const schemaNames = new Set();
6
- for (const schema of schemas) {
7
- if (schemaNames.has(schema.name)) {
8
- throw new Error(`Duplicate schema name: ${schema.name}`);
9
- }
10
- schemaNames.add(schema.name);
11
- }
4
+ const block_1 = require("../model/block");
5
+ function validateDocumentStructure(blocks, blockAnchors) {
12
6
  const blockNames = new Set();
13
7
  for (const block of blocks) {
14
8
  if (blockNames.has(block.name)) {
@@ -41,11 +35,27 @@ function validateDocumentStructure(schemas, blocks, blockAnchors) {
41
35
  throw new Error(`Duplicate input name in block ${block.name}: ${input.name}`);
42
36
  }
43
37
  inputNames.add(input.name);
44
- if (input.schema && !schemaNames.has(input.schema)) {
45
- throw new Error(`Unknown schema ${input.schema} referenced by input ${input.name}`);
38
+ }
39
+ for (const operation of block.reads) {
40
+ if (!operation.name && !(0, block_1.isStreamAccept)(operation.accept)) {
41
+ throw new Error(`Read operations must declare a name unless they accept text/event-stream: ${operation.id}`);
42
+ }
43
+ if ((0, block_1.isStreamAccept)(operation.accept) && operation.name) {
44
+ throw new Error(`Stream read operations must not declare a name: ${operation.id}`);
45
+ }
46
+ if (operation.name) {
47
+ if (operationNames.has(operation.name)) {
48
+ throw new Error(`Duplicate operation name in block ${block.name}: ${operation.name}`);
49
+ }
50
+ operationNames.add(operation.name);
51
+ }
52
+ for (const inputName of operation.inputs) {
53
+ if (!inputNames.has(inputName)) {
54
+ throw new Error(`Unknown input ${inputName} referenced by ${operation.id}`);
55
+ }
46
56
  }
47
57
  }
48
- for (const operation of [...block.reads, ...block.writes]) {
58
+ for (const operation of block.writes) {
49
59
  if (operationNames.has(operation.name)) {
50
60
  throw new Error(`Duplicate operation name in block ${block.name}: ${operation.name}`);
51
61
  }
@@ -0,0 +1,6 @@
1
+ export declare function escapeHtml(value: string): string;
2
+ export declare function escapeRegExp(value: string): string;
3
+ export declare const MAX_INPUT_LENGTH = 10000;
4
+ export declare const MAX_IDENTIFIER_LENGTH = 256;
5
+ export declare function validateInputLength(value: string, maxLength?: number): void;
6
+ export declare function trimTrailingBlankLines(value: string): string;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_IDENTIFIER_LENGTH = exports.MAX_INPUT_LENGTH = void 0;
4
+ exports.escapeHtml = escapeHtml;
5
+ exports.escapeRegExp = escapeRegExp;
6
+ exports.validateInputLength = validateInputLength;
7
+ exports.trimTrailingBlankLines = trimTrailingBlankLines;
8
+ function escapeHtml(value) {
9
+ return value
10
+ .replace(/&/g, "&amp;")
11
+ .replace(/</g, "&lt;")
12
+ .replace(/>/g, "&gt;")
13
+ .replace(/"/g, "&quot;")
14
+ .replace(/'/g, "&#39;");
15
+ }
16
+ function escapeRegExp(value) {
17
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
19
+ exports.MAX_INPUT_LENGTH = 10000;
20
+ exports.MAX_IDENTIFIER_LENGTH = 256;
21
+ function validateInputLength(value, maxLength = exports.MAX_INPUT_LENGTH) {
22
+ if (value.length > maxLength) {
23
+ throw new Error(`Input exceeds maximum length of ${maxLength} characters`);
24
+ }
25
+ }
26
+ function trimTrailingBlankLines(value) {
27
+ return value.replace(/\n+$/u, "\n");
28
+ }
@@ -0,0 +1,2 @@
1
+ export { escapeHtml, escapeRegExp, validateInputLength, trimTrailingBlankLines, MAX_INPUT_LENGTH, MAX_IDENTIFIER_LENGTH, } from "./html";
2
+ export { createLogger, type Logger, type LogLevel, type CreateLoggerOptions } from "./logger";
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLogger = exports.MAX_IDENTIFIER_LENGTH = exports.MAX_INPUT_LENGTH = exports.trimTrailingBlankLines = exports.validateInputLength = exports.escapeRegExp = exports.escapeHtml = void 0;
4
+ var html_1 = require("./html");
5
+ Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return html_1.escapeHtml; } });
6
+ Object.defineProperty(exports, "escapeRegExp", { enumerable: true, get: function () { return html_1.escapeRegExp; } });
7
+ Object.defineProperty(exports, "validateInputLength", { enumerable: true, get: function () { return html_1.validateInputLength; } });
8
+ Object.defineProperty(exports, "trimTrailingBlankLines", { enumerable: true, get: function () { return html_1.trimTrailingBlankLines; } });
9
+ Object.defineProperty(exports, "MAX_INPUT_LENGTH", { enumerable: true, get: function () { return html_1.MAX_INPUT_LENGTH; } });
10
+ Object.defineProperty(exports, "MAX_IDENTIFIER_LENGTH", { enumerable: true, get: function () { return html_1.MAX_IDENTIFIER_LENGTH; } });
11
+ var logger_1 = require("./logger");
12
+ Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_1.createLogger; } });
@@ -0,0 +1,12 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+ export interface Logger {
3
+ debug(message: string, context?: Record<string, unknown>): void;
4
+ info(message: string, context?: Record<string, unknown>): void;
5
+ warn(message: string, context?: Record<string, unknown>): void;
6
+ error(message: string, context?: Record<string, unknown>): void;
7
+ }
8
+ export interface CreateLoggerOptions {
9
+ level?: LogLevel;
10
+ prefix?: string;
11
+ }
12
+ export declare function createLogger(options?: CreateLoggerOptions): Logger;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLogger = createLogger;
4
+ const LOG_LEVELS = {
5
+ debug: 0,
6
+ info: 1,
7
+ warn: 2,
8
+ error: 3,
9
+ };
10
+ function formatTimestamp() {
11
+ return new Date().toISOString();
12
+ }
13
+ function formatMessage(level, prefix, message, context) {
14
+ const timestamp = formatTimestamp();
15
+ const prefixPart = prefix ? `[${prefix}] ` : "";
16
+ const contextPart = context ? ` ${JSON.stringify(context)}` : "";
17
+ return `${timestamp} ${prefixPart}${level.toUpperCase()}: ${message}${contextPart}`;
18
+ }
19
+ function createLogger(options = {}) {
20
+ const level = options.level ?? "info";
21
+ const prefix = options.prefix;
22
+ const levelNumber = LOG_LEVELS[level];
23
+ return {
24
+ debug(message, context) {
25
+ if (levelNumber <= LOG_LEVELS.debug) {
26
+ console.debug(formatMessage("debug", prefix, message, context));
27
+ }
28
+ },
29
+ info(message, context) {
30
+ if (levelNumber <= LOG_LEVELS.info) {
31
+ console.info(formatMessage("info", prefix, message, context));
32
+ }
33
+ },
34
+ warn(message, context) {
35
+ if (levelNumber <= LOG_LEVELS.warn) {
36
+ console.warn(formatMessage("warn", prefix, message, context));
37
+ }
38
+ },
39
+ error(message, context) {
40
+ if (levelNumber <= LOG_LEVELS.error) {
41
+ console.error(formatMessage("error", prefix, message, context));
42
+ }
43
+ },
44
+ };
45
+ }
@@ -6,6 +6,7 @@ export interface CreateFrameworkAppOptions {
6
6
  actions?: Record<string, ActionHandler<{
7
7
  inputs: Record<string, unknown>;
8
8
  }>>;
9
+ errorFragments?: import("./hosted-app").CreateHostedAppOptions["errorFragments"];
9
10
  mode?: "dev" | "start";
10
11
  devState?: unknown;
11
12
  }
@@ -7,5 +7,6 @@ function createFrameworkApp(options) {
7
7
  rootDir: options.rootDir,
8
8
  config: options.config,
9
9
  actions: options.actions,
10
+ errorFragments: options.errorFragments,
10
11
  });
11
12
  }