@mdsnai/sdk 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +4 -18
  13. package/dist/core/protocol/statements.d.ts +3 -8
  14. package/dist/core/protocol/statements.js +47 -72
  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 +14 -14
  49. package/dist/server/markdown.d.ts +2 -6
  50. package/dist/server/markdown.js +12 -11
  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
@@ -1,4 +1,5 @@
1
1
  import { type ActionHandler } from "../server/action-host";
2
+ import { type SerializableBlock } from "../server/markdown";
2
3
  import { type RenderHostedPageOptions } from "../server/page-host";
3
4
  export interface CreateHostedAppOptions {
4
5
  pages: Record<string, string>;
@@ -6,8 +7,28 @@ export interface CreateHostedAppOptions {
6
7
  inputs: Record<string, unknown>;
7
8
  }>>;
8
9
  publicDir?: string;
10
+ errorFragments?: Partial<HostedAppErrorFragments>;
9
11
  render?: Omit<RenderHostedPageOptions, "accept" | "routePath" | "layoutTemplate"> & {
10
12
  resolveLayoutTemplate?: (routePath: string, rawPage: string) => string | undefined;
11
13
  };
12
14
  }
15
+ export interface HostedAppErrorFragmentContext {
16
+ actionId: string;
17
+ block: SerializableBlock;
18
+ method: "GET" | "POST";
19
+ path: string;
20
+ }
21
+ export interface HostedAppActionNotAvailableContext extends HostedAppErrorFragmentContext {
22
+ }
23
+ export interface HostedAppUnsupportedContentTypeContext extends HostedAppErrorFragmentContext {
24
+ contentType: string;
25
+ }
26
+ export interface HostedAppInternalErrorContext extends HostedAppErrorFragmentContext {
27
+ error: unknown;
28
+ }
29
+ export interface HostedAppErrorFragments {
30
+ actionNotAvailable: (ctx: HostedAppActionNotAvailableContext) => string;
31
+ unsupportedContentType: (ctx: HostedAppUnsupportedContentTypeContext) => string;
32
+ internalError: (ctx: HostedAppInternalErrorContext) => string;
33
+ }
13
34
  export declare function createHostedApp(options: CreateHostedAppOptions): import("express-serve-static-core").Express;
@@ -6,7 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createHostedApp = createHostedApp;
7
7
  const express_1 = __importDefault(require("express"));
8
8
  const page_definition_1 = require("../core/document/page-definition");
9
+ const block_1 = require("../core/model/block");
9
10
  const action_host_1 = require("../server/action-host");
11
+ const action_inputs_1 = require("../server/action-inputs");
12
+ const error_fragments_1 = require("../server/error-fragments");
10
13
  const page_links_1 = require("../server/page-links");
11
14
  const page_host_1 = require("../server/page-host");
12
15
  const negotiate_1 = require("../server/negotiate");
@@ -23,6 +26,9 @@ function normalizeActionId(target) {
23
26
  }
24
27
  return trimmed.replace(/^\/+/u, "");
25
28
  }
29
+ function createActionBindingKey(method, path) {
30
+ return `${method}:${path}`;
31
+ }
26
32
  function buildActionBindings(pages) {
27
33
  const bindings = new Map();
28
34
  for (const pageSource of Object.values(pages)) {
@@ -34,82 +40,166 @@ function buildActionBindings(pages) {
34
40
  continue;
35
41
  }
36
42
  for (const block of document.blocks) {
37
- for (const operation of [...block.reads, ...block.writes]) {
43
+ for (const operation of block.reads) {
44
+ if (!operation.name || (0, block_1.isStreamAccept)(operation.accept)) {
45
+ continue;
46
+ }
47
+ const targetPath = (0, page_links_1.mapPageTargetToHttpPath)(operation.target);
48
+ const actionId = normalizeActionId(operation.target);
49
+ if (!actionId) {
50
+ continue;
51
+ }
52
+ const key = createActionBindingKey("GET", targetPath);
53
+ const existing = bindings.get(key);
54
+ if (existing
55
+ && (existing.actionId !== actionId || existing.blockName !== block.name || existing.kind !== "read")) {
56
+ throw new Error(`Action target must bind to one stable block context: GET ${targetPath}`);
57
+ }
58
+ bindings.set(key, {
59
+ actionId,
60
+ blockName: block.name,
61
+ block,
62
+ kind: "read",
63
+ });
64
+ }
65
+ for (const operation of block.writes) {
38
66
  const targetPath = (0, page_links_1.mapPageTargetToHttpPath)(operation.target);
39
67
  const actionId = normalizeActionId(operation.target);
40
68
  if (!actionId) {
41
69
  continue;
42
70
  }
43
- const existing = bindings.get(targetPath);
44
- if (existing && (existing.actionId !== actionId || existing.blockName !== block.name)) {
45
- throw new Error(`Action target must bind to one stable block context: ${targetPath}`);
71
+ const key = createActionBindingKey("POST", targetPath);
72
+ const existing = bindings.get(key);
73
+ if (existing
74
+ && (existing.actionId !== actionId || existing.blockName !== block.name || existing.kind !== "write")) {
75
+ throw new Error(`Action target must bind to one stable block context: POST ${targetPath}`);
46
76
  }
47
- bindings.set(targetPath, {
77
+ bindings.set(key, {
48
78
  actionId,
49
79
  blockName: block.name,
80
+ block,
81
+ kind: "write",
50
82
  });
51
83
  }
52
84
  }
53
85
  }
54
86
  return bindings;
55
87
  }
88
+ function blockToSerializableBlock(block) {
89
+ return {
90
+ name: block.name,
91
+ inputs: block.inputs.map((input) => ({
92
+ name: input.name,
93
+ type: input.type,
94
+ required: input.required,
95
+ secret: input.secret,
96
+ options: input.options,
97
+ })),
98
+ reads: block.reads.map((read) => ({
99
+ name: read.name,
100
+ target: read.target,
101
+ inputs: read.inputs,
102
+ accept: read.accept,
103
+ })),
104
+ writes: block.writes.map((write) => ({
105
+ name: write.name,
106
+ target: write.target,
107
+ inputs: write.inputs,
108
+ })),
109
+ };
110
+ }
111
+ function sendActionFragment(reqAccept, status, markdown, binding, res, options) {
112
+ if ((0, negotiate_1.wantsMarkdown)(reqAccept) || !(0, negotiate_1.wantsHtml)(reqAccept)) {
113
+ res.status(status).type("text/markdown; charset=utf-8").send(markdown);
114
+ return;
115
+ }
116
+ res.status(status).type("text/html; charset=utf-8").send((0, fragment_render_1.renderBlockFragmentHtml)(markdown, binding.blockName, {
117
+ mapActionTarget: options.render?.mapActionTarget ?? page_links_1.mapPageTargetToHttpPath,
118
+ markdown: options.render?.markdown,
119
+ }));
120
+ }
56
121
  function createHostedApp(options) {
57
122
  const app = (0, express_1.default)();
58
123
  const actionBindings = buildActionBindings(options.pages);
124
+ const errorFragments = {
125
+ actionNotAvailable: (ctx) => (0, error_fragments_1.renderActionNotAvailableFragment)({ block: ctx.block }),
126
+ unsupportedContentType: (ctx) => (0, error_fragments_1.renderUnsupportedContentTypeFragment)({ block: ctx.block }),
127
+ internalError: (ctx) => (0, error_fragments_1.renderInternalErrorFragment)({ block: ctx.block, error: ctx.error }),
128
+ ...options.errorFragments,
129
+ };
59
130
  const routedPages = (0, route_matcher_1.sortRoutedPagesForMatching)(Object.entries(options.pages).map(([routePath, source]) => ({
60
131
  routePath,
61
132
  source,
62
133
  })));
63
- app.use(express_1.default.json());
134
+ app.use(express_1.default.text({
135
+ type: ["text/markdown"],
136
+ limit: "1mb",
137
+ }));
64
138
  if (typeof options.publicDir === "string") {
65
139
  app.use(express_1.default.static(options.publicDir));
66
140
  }
67
141
  app.get("/__mdsn/client.js", (_req, res) => {
68
142
  res.type("application/javascript; charset=utf-8").send((0, page_client_script_1.getPageClientRuntimeScript)());
69
143
  });
70
- app.post("*", async (req, res, next) => {
71
- const binding = actionBindings.get(req.path);
144
+ async function handleActionRequest(req, res, method, next) {
145
+ const binding = actionBindings.get(createActionBindingKey(method, req.path));
72
146
  if (!binding) {
73
147
  next();
74
148
  return;
75
149
  }
76
150
  const action = options.actions?.[binding.actionId];
77
151
  if (!action) {
78
- res.status(404).json({
79
- ok: false,
80
- errorCode: "NOT_FOUND",
81
- });
152
+ const block = blockToSerializableBlock(binding.block);
153
+ sendActionFragment(req.headers.accept, 404, errorFragments.actionNotAvailable({
154
+ actionId: binding.actionId,
155
+ block,
156
+ method,
157
+ path: req.path,
158
+ }), binding, res, options);
82
159
  return;
83
160
  }
84
161
  try {
85
- const result = await (0, action_host_1.executeActionHandler)(action, {
86
- inputs: (req.body?.inputs ?? {}),
87
- });
88
- if (result.ok && result.kind === "fragment") {
89
- if ((0, negotiate_1.wantsMarkdown)(req.headers.accept)) {
90
- res.status(200).type("text/markdown; charset=utf-8").send(result.markdown);
162
+ if (method === "POST") {
163
+ const contentType = req.headers["content-type"] ?? "";
164
+ const normalized = String(contentType).toLowerCase();
165
+ if (!normalized.includes("text/markdown")) {
166
+ const block = blockToSerializableBlock(binding.block);
167
+ sendActionFragment(req.headers.accept, 415, errorFragments.unsupportedContentType({
168
+ actionId: binding.actionId,
169
+ block,
170
+ method,
171
+ path: req.path,
172
+ contentType: String(contentType),
173
+ }), binding, res, options);
91
174
  return;
92
175
  }
93
- res.status(200).json({
94
- ...result,
95
- html: (0, fragment_render_1.renderBlockFragmentHtml)(result.markdown, binding.blockName, {
96
- mapActionTarget: options.render?.mapActionTarget ?? page_links_1.mapPageTargetToHttpPath,
97
- markdown: options.render?.markdown,
98
- }),
99
- });
100
- return;
101
176
  }
102
- res.status(result.ok ? 200 : 400).json(result);
177
+ const result = await (0, action_host_1.executeActionHandler)(action, {
178
+ inputs: method === "GET"
179
+ ? (0, action_inputs_1.parseActionInputs)(req.query)
180
+ : (0, action_inputs_1.parseActionInputs)(typeof req.body === "string" ? req.body : ""),
181
+ });
182
+ sendActionFragment(req.headers.accept, 200, result, binding, res, options);
103
183
  }
104
184
  catch (error) {
105
- res.status(500).json({
106
- ok: false,
107
- errorCode: "INTERNAL_ERROR",
108
- message: error instanceof Error ? error.message : String(error),
109
- });
185
+ sendActionFragment(req.headers.accept, 500, errorFragments.internalError({
186
+ actionId: binding.actionId,
187
+ block: blockToSerializableBlock(binding.block),
188
+ method,
189
+ path: req.path,
190
+ error,
191
+ }), binding, res, options);
110
192
  }
193
+ }
194
+ app.post("*", async (req, res, next) => {
195
+ await handleActionRequest(req, res, "POST", next);
111
196
  });
112
- app.get("*", (req, res) => {
197
+ app.get("*", async (req, res) => {
198
+ const actionBinding = actionBindings.get(createActionBindingKey("GET", req.path));
199
+ if (actionBinding) {
200
+ await handleActionRequest(req, res, "GET", () => undefined);
201
+ return;
202
+ }
113
203
  const matchedPage = (0, route_matcher_1.resolveRoutedPageForPath)(req.path, routedPages);
114
204
  if (!matchedPage) {
115
205
  res.status(404).type("text/plain; charset=utf-8").send("Not Found");
@@ -1,4 +1,6 @@
1
+ export { createHostedApp } from "./hosted-app";
1
2
  export { createFrameworkApp } from "./create-framework-app";
3
+ export type { CreateHostedAppOptions } from "./hosted-app";
2
4
  export type { CreateFrameworkAppOptions } from "./create-framework-app";
3
5
  export { defineConfig } from "../server/config";
4
6
  export type { MdsnConfig } from "../server/config";
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defineConfig = exports.createFrameworkApp = void 0;
3
+ exports.defineConfig = exports.createFrameworkApp = exports.createHostedApp = void 0;
4
+ var hosted_app_1 = require("./hosted-app");
5
+ Object.defineProperty(exports, "createHostedApp", { enumerable: true, get: function () { return hosted_app_1.createHostedApp; } });
4
6
  var create_framework_app_1 = require("./create-framework-app");
5
7
  Object.defineProperty(exports, "createFrameworkApp", { enumerable: true, get: function () { return create_framework_app_1.createFrameworkApp; } });
6
8
  var config_1 = require("../server/config");
@@ -8,5 +8,6 @@ export interface CreateSiteAppOptions {
8
8
  rootDir: string;
9
9
  config?: MdsnConfig;
10
10
  actions?: CreateHostedAppOptions["actions"];
11
+ errorFragments?: CreateHostedAppOptions["errorFragments"];
11
12
  }
12
13
  export declare function createSiteApp(options: CreateSiteAppOptions): import("express-serve-static-core").Express;
@@ -132,6 +132,7 @@ function createSiteApp(options) {
132
132
  pages,
133
133
  actions,
134
134
  publicDir,
135
+ errorFragments: options.errorFragments,
135
136
  render: {
136
137
  siteTitle: resolvedConfig.site.title,
137
138
  siteDescription: resolvedConfig.site.description,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { parsePageDefinition } from "./core";
2
- export type { BlockAnchorDefinition, BlockDefinition, DocumentDefinition, FrontmatterData, InputDefinition, InputType, ReadDefinition, RedirectDefinition, SchemaDefinition, WriteDefinition, } from "./core";
2
+ export type { BlockAnchorDefinition, BlockDefinition, DocumentDefinition, FrontmatterData, InputDefinition, InputType, ReadDefinition, WriteDefinition, } from "./core";
3
3
  export { createRenderModel, getClientRuntimeScript, parseFragment, parseMarkdown, parsePage, renderDefaultHtmlDocument, renderPageHtml, } from "./web";
4
4
  export type { CreateRenderModelOptions, ParsedFragment, ParsedPage, RenderModel, } from "./web";
5
- export { createFrameworkApp, defineConfig, } from "./framework";
6
- export type { CreateFrameworkAppOptions, MdsnConfig, } from "./framework";
7
- export { defineAction } from "./server";
8
- export type { ActionContext, ActionDefinition, ActionResult, } from "./server";
5
+ export { createHostedApp, createFrameworkApp, defineConfig, } from "./framework";
6
+ export type { CreateHostedAppOptions, CreateFrameworkAppOptions, MdsnConfig, } from "./framework";
7
+ export { HttpCookieJar, createActionContextFromRequest, defineAction, defineActions, parseActionInputs, parseCookieHeader, renderActionNotAvailableFragment, renderAuthRequiredFragment, renderErrorFragment, renderHostedPage, renderInternalErrorFragment, renderMarkdownFragment, renderMarkdownValue, renderUnsupportedContentTypeFragment, requireSessionFromCookie, serializeActionInputsAsMarkdown, serializeBlock, } from "./server";
8
+ export type { ActionContext, ActionDefinition, ActionDefinitionMap, CreateActionContextFromRequestOptions, HostedPageResponse, SerializableBlock, SerializableInput, SerializableRead, SerializableWrite, } from "./server";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defineAction = exports.defineConfig = exports.createFrameworkApp = exports.renderPageHtml = exports.renderDefaultHtmlDocument = exports.parsePage = exports.parseMarkdown = exports.parseFragment = exports.getClientRuntimeScript = exports.createRenderModel = exports.parsePageDefinition = void 0;
3
+ exports.serializeBlock = exports.serializeActionInputsAsMarkdown = exports.requireSessionFromCookie = exports.renderUnsupportedContentTypeFragment = exports.renderMarkdownValue = exports.renderMarkdownFragment = exports.renderInternalErrorFragment = exports.renderHostedPage = exports.renderErrorFragment = exports.renderAuthRequiredFragment = exports.renderActionNotAvailableFragment = exports.parseCookieHeader = exports.parseActionInputs = exports.defineActions = exports.defineAction = exports.createActionContextFromRequest = exports.HttpCookieJar = exports.defineConfig = exports.createFrameworkApp = exports.createHostedApp = exports.renderPageHtml = exports.renderDefaultHtmlDocument = exports.parsePage = exports.parseMarkdown = exports.parseFragment = exports.getClientRuntimeScript = exports.createRenderModel = exports.parsePageDefinition = void 0;
4
4
  var core_1 = require("./core");
5
5
  Object.defineProperty(exports, "parsePageDefinition", { enumerable: true, get: function () { return core_1.parsePageDefinition; } });
6
6
  var web_1 = require("./web");
@@ -12,7 +12,24 @@ Object.defineProperty(exports, "parsePage", { enumerable: true, get: function ()
12
12
  Object.defineProperty(exports, "renderDefaultHtmlDocument", { enumerable: true, get: function () { return web_1.renderDefaultHtmlDocument; } });
13
13
  Object.defineProperty(exports, "renderPageHtml", { enumerable: true, get: function () { return web_1.renderPageHtml; } });
14
14
  var framework_1 = require("./framework");
15
+ Object.defineProperty(exports, "createHostedApp", { enumerable: true, get: function () { return framework_1.createHostedApp; } });
15
16
  Object.defineProperty(exports, "createFrameworkApp", { enumerable: true, get: function () { return framework_1.createFrameworkApp; } });
16
17
  Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function () { return framework_1.defineConfig; } });
17
18
  var server_1 = require("./server");
19
+ Object.defineProperty(exports, "HttpCookieJar", { enumerable: true, get: function () { return server_1.HttpCookieJar; } });
20
+ Object.defineProperty(exports, "createActionContextFromRequest", { enumerable: true, get: function () { return server_1.createActionContextFromRequest; } });
18
21
  Object.defineProperty(exports, "defineAction", { enumerable: true, get: function () { return server_1.defineAction; } });
22
+ Object.defineProperty(exports, "defineActions", { enumerable: true, get: function () { return server_1.defineActions; } });
23
+ Object.defineProperty(exports, "parseActionInputs", { enumerable: true, get: function () { return server_1.parseActionInputs; } });
24
+ Object.defineProperty(exports, "parseCookieHeader", { enumerable: true, get: function () { return server_1.parseCookieHeader; } });
25
+ Object.defineProperty(exports, "renderActionNotAvailableFragment", { enumerable: true, get: function () { return server_1.renderActionNotAvailableFragment; } });
26
+ Object.defineProperty(exports, "renderAuthRequiredFragment", { enumerable: true, get: function () { return server_1.renderAuthRequiredFragment; } });
27
+ Object.defineProperty(exports, "renderErrorFragment", { enumerable: true, get: function () { return server_1.renderErrorFragment; } });
28
+ Object.defineProperty(exports, "renderHostedPage", { enumerable: true, get: function () { return server_1.renderHostedPage; } });
29
+ Object.defineProperty(exports, "renderInternalErrorFragment", { enumerable: true, get: function () { return server_1.renderInternalErrorFragment; } });
30
+ Object.defineProperty(exports, "renderMarkdownFragment", { enumerable: true, get: function () { return server_1.renderMarkdownFragment; } });
31
+ Object.defineProperty(exports, "renderMarkdownValue", { enumerable: true, get: function () { return server_1.renderMarkdownValue; } });
32
+ Object.defineProperty(exports, "renderUnsupportedContentTypeFragment", { enumerable: true, get: function () { return server_1.renderUnsupportedContentTypeFragment; } });
33
+ Object.defineProperty(exports, "requireSessionFromCookie", { enumerable: true, get: function () { return server_1.requireSessionFromCookie; } });
34
+ Object.defineProperty(exports, "serializeActionInputsAsMarkdown", { enumerable: true, get: function () { return server_1.serializeActionInputsAsMarkdown; } });
35
+ Object.defineProperty(exports, "serializeBlock", { enumerable: true, get: function () { return server_1.serializeBlock; } });
@@ -0,0 +1,11 @@
1
+ import type express from "express";
2
+ import type { ActionContext } from "./action";
3
+ export interface CreateActionContextFromRequestOptions {
4
+ inputs?: Record<string, unknown>;
5
+ pathname?: string;
6
+ cookies?: Record<string, string>;
7
+ env?: Record<string, string | undefined>;
8
+ siteTitle?: string;
9
+ siteBaseUrl?: string;
10
+ }
11
+ export declare function createActionContextFromRequest(req: express.Request, options?: CreateActionContextFromRequestOptions): ActionContext;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createActionContextFromRequest = createActionContextFromRequest;
4
+ function createActionContextFromRequest(req, options = {}) {
5
+ const body = (req.body ?? {});
6
+ return {
7
+ inputs: options.inputs ?? body.inputs ?? {},
8
+ params: Object.fromEntries(Object.entries(req.params).map(([key, value]) => [key, String(value)])),
9
+ query: new URLSearchParams(Object.entries(req.query).flatMap(([key, value]) => {
10
+ if (value === undefined)
11
+ return [];
12
+ if (Array.isArray(value)) {
13
+ return value.map((item) => [key, String(item)]);
14
+ }
15
+ return [[key, String(value)]];
16
+ })),
17
+ pathname: options.pathname ?? (typeof body.pathname === "string" ? body.pathname : req.path),
18
+ request: req,
19
+ cookies: options.cookies ?? {},
20
+ env: options.env ?? process.env,
21
+ site: {
22
+ title: options.siteTitle,
23
+ baseUrl: options.siteBaseUrl,
24
+ },
25
+ };
26
+ }
@@ -1,3 +1,2 @@
1
- import { type ActionResult } from "../core/action";
2
- export type ActionHandler<Context = unknown> = (ctx: Context) => Promise<unknown> | unknown;
3
- export declare function executeActionHandler<Context>(handler: ActionHandler<Context>, ctx?: Context): Promise<ActionResult>;
1
+ export type ActionHandler<Context = unknown> = (ctx: Context) => Promise<string> | string;
2
+ export declare function executeActionHandler<Context>(handler: ActionHandler<Context>, ctx?: Context): Promise<string>;
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeActionHandler = executeActionHandler;
4
- const action_1 = require("../core/action");
5
4
  async function executeActionHandler(handler, ctx) {
6
5
  const result = await handler(ctx);
7
- return (0, action_1.normalizeActionResult)(result);
6
+ if (typeof result !== "string") {
7
+ throw new Error("Invalid action result: expected markdown string");
8
+ }
9
+ return result;
8
10
  }
@@ -0,0 +1,3 @@
1
+ export declare function serializeActionInputsAsMarkdown(inputs: Record<string, unknown>): string;
2
+ export declare function parseActionInputs(payload: unknown): Record<string, unknown>;
3
+ export declare function normalizeActionInputPayloadToMarkdown(payload: unknown): string;
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeActionInputsAsMarkdown = serializeActionInputsAsMarkdown;
4
+ exports.parseActionInputs = parseActionInputs;
5
+ exports.normalizeActionInputPayloadToMarkdown = normalizeActionInputPayloadToMarkdown;
6
+ const core_1 = require("../core");
7
+ function isPlainObject(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ function parseScalarToken(raw) {
11
+ const trimmed = raw.trim();
12
+ if (!trimmed) {
13
+ return "";
14
+ }
15
+ (0, core_1.validateInputLength)(trimmed, core_1.MAX_INPUT_LENGTH);
16
+ if (trimmed.startsWith("{")
17
+ || trimmed.startsWith("[")
18
+ || trimmed.startsWith("\"")
19
+ || trimmed === "true"
20
+ || trimmed === "false"
21
+ || trimmed === "null"
22
+ || /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/u.test(trimmed)) {
23
+ try {
24
+ return JSON.parse(trimmed);
25
+ }
26
+ catch {
27
+ // Fall through to plain string.
28
+ }
29
+ }
30
+ if ((trimmed.startsWith("'") && trimmed.endsWith("'"))
31
+ || (trimmed.startsWith("\"") && trimmed.endsWith("\""))) {
32
+ return trimmed.slice(1, -1);
33
+ }
34
+ return trimmed;
35
+ }
36
+ function parseMarkdownInputs(source) {
37
+ (0, core_1.validateInputLength)(source, core_1.MAX_INPUT_LENGTH * 10);
38
+ const inputs = {};
39
+ const segments = splitInputPairs(source);
40
+ for (const segment of segments) {
41
+ const separator = segment.indexOf(":");
42
+ if (separator <= 0) {
43
+ continue;
44
+ }
45
+ const name = segment.slice(0, separator).trim();
46
+ if (!/^[a-zA-Z_][\w-]*$/u.test(name)) {
47
+ continue;
48
+ }
49
+ if (name.length > core_1.MAX_IDENTIFIER_LENGTH) {
50
+ continue;
51
+ }
52
+ const value = segment.slice(separator + 1);
53
+ inputs[name] = parseScalarToken(value);
54
+ }
55
+ return inputs;
56
+ }
57
+ function splitInputPairs(source) {
58
+ const pairs = [];
59
+ let current = "";
60
+ let depthObject = 0;
61
+ let depthArray = 0;
62
+ let inSingleQuote = false;
63
+ let inDoubleQuote = false;
64
+ let escaped = false;
65
+ const flushCurrent = () => {
66
+ const trimmed = current.trim();
67
+ if (trimmed) {
68
+ pairs.push(trimmed);
69
+ }
70
+ current = "";
71
+ };
72
+ for (const char of source) {
73
+ if (escaped) {
74
+ current += char;
75
+ escaped = false;
76
+ continue;
77
+ }
78
+ if (inSingleQuote) {
79
+ current += char;
80
+ if (char === "\\") {
81
+ escaped = true;
82
+ }
83
+ else if (char === "'") {
84
+ inSingleQuote = false;
85
+ }
86
+ continue;
87
+ }
88
+ if (inDoubleQuote) {
89
+ current += char;
90
+ if (char === "\\") {
91
+ escaped = true;
92
+ }
93
+ else if (char === "\"") {
94
+ inDoubleQuote = false;
95
+ }
96
+ continue;
97
+ }
98
+ if (char === "'") {
99
+ inSingleQuote = true;
100
+ current += char;
101
+ continue;
102
+ }
103
+ if (char === "\"") {
104
+ inDoubleQuote = true;
105
+ current += char;
106
+ continue;
107
+ }
108
+ if (char === "{") {
109
+ depthObject += 1;
110
+ current += char;
111
+ continue;
112
+ }
113
+ if (char === "}") {
114
+ depthObject = Math.max(0, depthObject - 1);
115
+ current += char;
116
+ continue;
117
+ }
118
+ if (char === "[") {
119
+ depthArray += 1;
120
+ current += char;
121
+ continue;
122
+ }
123
+ if (char === "]") {
124
+ depthArray = Math.max(0, depthArray - 1);
125
+ current += char;
126
+ continue;
127
+ }
128
+ const isTopLevel = depthObject === 0 && depthArray === 0;
129
+ if (isTopLevel && (char === "," || char === "," || char === "\n" || char === "\r")) {
130
+ flushCurrent();
131
+ continue;
132
+ }
133
+ current += char;
134
+ }
135
+ flushCurrent();
136
+ return pairs;
137
+ }
138
+ function normalizeObjectInputs(payload) {
139
+ const inputs = {};
140
+ for (const [name, value] of Object.entries(payload)) {
141
+ if (!/^[a-zA-Z_][\w-]*$/u.test(name)) {
142
+ continue;
143
+ }
144
+ if (Array.isArray(value)) {
145
+ if (value.length > 0) {
146
+ inputs[name] = value[0];
147
+ }
148
+ continue;
149
+ }
150
+ if (typeof value === "string") {
151
+ inputs[name] = parseScalarToken(value);
152
+ continue;
153
+ }
154
+ if (typeof value === "number" || typeof value === "boolean") {
155
+ inputs[name] = value;
156
+ continue;
157
+ }
158
+ }
159
+ return inputs;
160
+ }
161
+ function serializeActionInputsAsMarkdown(inputs) {
162
+ return Object.entries(inputs)
163
+ .filter(([, value]) => value !== undefined)
164
+ .map(([name, value]) => `${name}: ${JSON.stringify(value)}`)
165
+ .join(", ");
166
+ }
167
+ function parseActionInputs(payload) {
168
+ if (typeof payload === "string") {
169
+ return parseMarkdownInputs(payload);
170
+ }
171
+ if (isPlainObject(payload)) {
172
+ return normalizeObjectInputs(payload);
173
+ }
174
+ return {};
175
+ }
176
+ function normalizeActionInputPayloadToMarkdown(payload) {
177
+ return serializeActionInputsAsMarkdown(parseActionInputs(payload));
178
+ }
@@ -1,8 +1,8 @@
1
- import type express from "express";
2
- import { type ActionContext, type ActionRegistry } from "./action";
1
+ import { type ActionRegistry } from "./action";
2
+ import { createActionContextFromRequest } from "./action-context";
3
3
  export declare function summarizeActionInputs(inputs: Record<string, unknown>): string;
4
4
  export declare function createActionFilesSignature(actionFiles: string[], actionsDir: string): string;
5
5
  export declare function loadActionRegistry(actionFiles: string[], actionsDir: string, options?: {
6
6
  fresh?: boolean;
7
7
  }): Promise<ActionRegistry>;
8
- export declare function createActionContext(req: express.Request, siteTitle?: string, siteBaseUrl?: string): ActionContext;
8
+ export declare function createActionContext(req: Parameters<typeof createActionContextFromRequest>[0], siteTitle?: string, siteBaseUrl?: string): import("./action").ActionContext;
@@ -10,6 +10,7 @@ exports.createActionContext = createActionContext;
10
10
  const node_fs_1 = require("node:fs");
11
11
  const node_path_1 = __importDefault(require("node:path"));
12
12
  const action_1 = require("./action");
13
+ const action_context_1 = require("./action-context");
13
14
  const module_loader_1 = require("./module-loader");
14
15
  function toPosixRelativePath(baseDir, filePath) {
15
16
  return node_path_1.default.relative(baseDir, filePath).split(node_path_1.default.sep).join("/");
@@ -57,25 +58,5 @@ async function loadActionRegistry(actionFiles, actionsDir, options = {}) {
57
58
  return (0, action_1.createActionRegistry)(entries);
58
59
  }
59
60
  function createActionContext(req, siteTitle, siteBaseUrl) {
60
- const body = (req.body ?? {});
61
- return {
62
- inputs: body.inputs ?? {},
63
- params: Object.fromEntries(Object.entries(req.params).map(([key, value]) => [key, String(value)])),
64
- query: new URLSearchParams(Object.entries(req.query).flatMap(([key, value]) => {
65
- if (value === undefined)
66
- return [];
67
- if (Array.isArray(value)) {
68
- return value.map((item) => [key, String(item)]);
69
- }
70
- return [[key, String(value)]];
71
- })),
72
- pathname: typeof body.pathname === "string" ? body.pathname : req.path,
73
- request: req,
74
- cookies: {},
75
- env: process.env,
76
- site: {
77
- title: siteTitle,
78
- baseUrl: siteBaseUrl,
79
- },
80
- };
61
+ return (0, action_context_1.createActionContextFromRequest)(req, { siteTitle, siteBaseUrl });
81
62
  }
@@ -1,4 +1,3 @@
1
- import type { ActionResult as HostedActionResult } from "../core/action";
2
1
  export type ActionContext = {
3
2
  inputs: Record<string, unknown>;
4
3
  params: Record<string, string>;
@@ -12,14 +11,7 @@ export type ActionContext = {
12
11
  baseUrl?: string;
13
12
  };
14
13
  };
15
- export type ActionFailure = {
16
- ok: false;
17
- errorCode: string;
18
- message?: string;
19
- fieldErrors?: Record<string, string>;
20
- };
21
- export type ActionResult = HostedActionResult | ActionFailure;
22
- export type ActionReturnValue = string | ActionResult;
14
+ export type ActionReturnValue = string;
23
15
  export type ActionDefinition = {
24
16
  name?: string;
25
17
  auth?: boolean;