@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.
- package/README.md +40 -0
- package/dist/core/document/markdown.js +2 -4
- package/dist/core/document/page-definition.js +1 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +9 -1
- package/dist/core/model/block.d.ts +4 -8
- package/dist/core/model/block.js +6 -0
- package/dist/core/model/document.d.ts +0 -2
- package/dist/core/model/index.d.ts +1 -2
- package/dist/core/model/input.d.ts +1 -2
- package/dist/core/protocol/mdsn.d.ts +0 -2
- package/dist/core/protocol/mdsn.js +3 -17
- package/dist/core/protocol/statements.d.ts +3 -8
- package/dist/core/protocol/statements.js +46 -71
- package/dist/core/protocol/validation.d.ts +2 -3
- package/dist/core/protocol/validation.js +21 -11
- package/dist/core/utils/html.d.ts +6 -0
- package/dist/core/utils/html.js +28 -0
- package/dist/core/utils/index.d.ts +2 -0
- package/dist/core/utils/index.js +12 -0
- package/dist/core/utils/logger.d.ts +12 -0
- package/dist/core/utils/logger.js +45 -0
- package/dist/framework/create-framework-app.d.ts +1 -0
- package/dist/framework/create-framework-app.js +1 -0
- package/dist/framework/hosted-app.d.ts +21 -0
- package/dist/framework/hosted-app.js +123 -33
- package/dist/framework/index.d.ts +2 -0
- package/dist/framework/index.js +3 -1
- package/dist/framework/site-app.d.ts +1 -0
- package/dist/framework/site-app.js +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +18 -1
- package/dist/server/action-context.d.ts +11 -0
- package/dist/server/action-context.js +26 -0
- package/dist/server/action-host.d.ts +2 -3
- package/dist/server/action-host.js +4 -2
- package/dist/server/action-inputs.d.ts +3 -0
- package/dist/server/action-inputs.js +178 -0
- package/dist/server/action-runtime.d.ts +3 -3
- package/dist/server/action-runtime.js +2 -21
- package/dist/server/action.d.ts +1 -9
- package/dist/server/action.js +5 -1
- package/dist/server/build.js +4 -0
- package/dist/server/error-fragments.d.ts +46 -0
- package/dist/server/error-fragments.js +77 -0
- package/dist/server/index.d.ts +9 -2
- package/dist/server/index.js +17 -1
- package/dist/server/init.js +13 -13
- package/dist/server/markdown.d.ts +2 -6
- package/dist/server/markdown.js +11 -10
- package/dist/server/server.d.ts +2 -1
- package/dist/server/server.js +17 -8
- package/dist/server/session.d.ts +40 -0
- package/dist/server/session.js +220 -0
- package/dist/web/block-runtime.js +15 -17
- package/dist/web/fragment-render.d.ts +0 -2
- package/dist/web/fragment-render.js +0 -1
- package/dist/web/i18n.d.ts +0 -2
- package/dist/web/i18n.js +0 -4
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js +2 -1
- package/dist/web/page-bootstrap.js +0 -1
- package/dist/web/page-client-runtime.d.ts +2 -13
- package/dist/web/page-client-runtime.js +1 -16
- package/dist/web/page-client-script.d.ts +0 -1
- package/dist/web/page-client-script.js +172 -160
- package/dist/web/page-html.js +4 -11
- package/dist/web/page-render.d.ts +1 -1
- package/dist/web/page-render.js +13 -21
- package/package.json +1 -1
- package/dist/core/action/execution.d.ts +0 -4
- package/dist/core/action/execution.js +0 -57
- package/dist/core/action/index.d.ts +0 -2
- package/dist/core/action/index.js +0 -7
- package/dist/core/action/types.d.ts +0 -19
- package/dist/core/action/types.js +0 -2
- package/dist/core/model/schema.d.ts +0 -4
- 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
|
-
|
|
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.
|
|
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
|
};
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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,
|
|
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";
|
package/dist/core/index.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/dist/core/model/block.js
CHANGED
|
@@ -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,
|
|
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" | "
|
|
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
|
}
|
|
@@ -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("
|
|
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("
|
|
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("
|
|
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
|
|
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"
|
|
7
|
-
export declare function
|
|
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 [,
|
|
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
|
|
59
|
-
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
|
|
71
|
-
throw new Error(`
|
|
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
|
-
|
|
77
|
-
.trim()
|
|
78
|
-
|
|
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 [,
|
|
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
|
|
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
|
|
114
|
+
return block.reads.length + block.writes.length;
|
|
140
115
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BlockDefinition } from "../model/block";
|
|
2
2
|
import type { BlockAnchorDefinition } from "../model/document";
|
|
3
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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, "&")
|
|
11
|
+
.replace(/</g, "<")
|
|
12
|
+
.replace(/>/g, ">")
|
|
13
|
+
.replace(/"/g, """)
|
|
14
|
+
.replace(/'/g, "'");
|
|
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,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
|
}
|