@hatchingpoint/point 0.0.11 → 0.0.13

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.
@@ -24,6 +24,7 @@ export type PointSemanticDeclaration =
24
24
  | PointSemanticActionDeclaration
25
25
  | PointSemanticPolicyDeclaration
26
26
  | PointSemanticViewDeclaration
27
+ | PointSemanticPageDeclaration
27
28
  | PointSemanticRouteDeclaration
28
29
  | PointSemanticWorkflowDeclaration
29
30
  | PointSemanticCommandDeclaration;
@@ -123,6 +124,16 @@ export interface PointSemanticViewDeclaration {
123
124
  span?: PointSourceSpan;
124
125
  }
125
126
 
127
+ export interface PointSemanticPageDeclaration {
128
+ kind: "page";
129
+ name: string;
130
+ inputs: PointSemanticBinding[];
131
+ title: PointSemanticExpression;
132
+ description?: PointSemanticExpression;
133
+ main: PointSemanticExpression;
134
+ span?: PointSourceSpan;
135
+ }
136
+
126
137
  export interface PointSemanticRouteDeclaration {
127
138
  kind: "route";
128
139
  name: string;
@@ -182,7 +193,9 @@ export type PointSemanticPolicyStatement =
182
193
 
183
194
  export type PointSemanticViewStatement =
184
195
  | { kind: "render"; value: PointSemanticExpression; span?: PointSourceSpan }
185
- | { kind: "whenRender"; condition: PointSemanticExpression; value: PointSemanticExpression; span?: PointSourceSpan };
196
+ | { kind: "whenRender"; condition: PointSemanticExpression; value: PointSemanticExpression; span?: PointSourceSpan }
197
+ | { kind: "bindCheckbox"; label: string; target: PointSemanticExpression; span?: PointSourceSpan }
198
+ | { kind: "onChangeCall"; callback: string; span?: PointSourceSpan };
186
199
 
187
200
  export type PointSemanticRouteStatement = { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan };
188
201
 
@@ -4,6 +4,7 @@ const CALLABLE_KEYWORDS = [
4
4
  "label",
5
5
  "action",
6
6
  "view",
7
+ "page",
7
8
  "route",
8
9
  "workflow",
9
10
  "command",
@@ -47,5 +48,5 @@ function collectBody(lines: string[], start: number): { lines: string[]; next: n
47
48
  }
48
49
 
49
50
  function isTopLevel(line: string): boolean {
50
- return /^(module|use|record|calculation|rule|label|external|action|policy|view|route|workflow|command)\s+/.test(line);
51
+ return /^(module|use|record|calculation|rule|label|external|action|policy|view|page|route|workflow|command)\s+/.test(line);
51
52
  }
@@ -27,6 +27,7 @@ export type PointSemanticSymbolKind =
27
27
  | "action"
28
28
  | "policy"
29
29
  | "view"
30
+ | "page"
30
31
  | "route"
31
32
  | "workflow"
32
33
  | "command"
@@ -215,6 +216,15 @@ function callableDeclaration(declaration: PointSemanticDeclaration):
215
216
  if (declaration.kind === "view" || declaration.kind === "route" || declaration.kind === "workflow" || declaration.kind === "command") {
216
217
  return { kind: declaration.kind, name: declaration.name, inputs: declaration.inputs, output: declaration.output, declaration };
217
218
  }
219
+ if (declaration.kind === "page") {
220
+ return {
221
+ kind: "page",
222
+ name: declaration.name,
223
+ inputs: declaration.inputs,
224
+ output: { name: "page", type: { kind: "typeRef", name: "Page", args: [] } },
225
+ declaration,
226
+ };
227
+ }
218
228
  return null;
219
229
  }
220
230
 
@@ -313,7 +323,7 @@ function relatedRefsFor(symbol: PointSemanticSymbol, index: PointSemanticIndex):
313
323
  const ownerPath = symbol.path.split(".").slice(0, 2).join(".");
314
324
  return index.refs.filter((candidate) => candidate.path.startsWith(`${ownerPath}.`) && candidate.ref !== symbol.ref).map((candidate) => candidate.ref);
315
325
  }
316
- if (symbol.kind === "record" || symbol.kind === "calculation" || symbol.kind === "rule" || symbol.kind === "label" || symbol.kind === "action" || symbol.kind === "policy" || symbol.kind === "view" || symbol.kind === "route" || symbol.kind === "workflow" || symbol.kind === "command" || symbol.kind === "external") {
326
+ if (symbol.kind === "record" || symbol.kind === "calculation" || symbol.kind === "rule" || symbol.kind === "label" || symbol.kind === "action" || symbol.kind === "policy" || symbol.kind === "view" || symbol.kind === "page" || symbol.kind === "route" || symbol.kind === "workflow" || symbol.kind === "command" || symbol.kind === "external") {
317
327
  return index.refs.filter((candidate) => candidate.path.startsWith(`${symbol.path}.`)).map((candidate) => candidate.ref);
318
328
  }
319
329
  return [];
@@ -332,6 +342,7 @@ function summaryFor(symbol: PointSemanticSymbol): string {
332
342
  if (symbol.kind === "action") return `Semantic action ${symbol.name} returns ${symbol.type}; effects: ${(symbol.effects ?? []).join(", ") || "none"}.`;
333
343
  if (symbol.kind === "policy") return `Semantic policy ${symbol.name} returns ${symbol.type}.`;
334
344
  if (symbol.kind === "view") return `Semantic view ${symbol.name} returns ${symbol.type}.`;
345
+ if (symbol.kind === "page") return `Semantic page ${symbol.name} returns a Next.js page shell (${symbol.type}).`;
335
346
  if (symbol.kind === "route") return `Semantic route ${symbol.name} returns ${symbol.type}.`;
336
347
  if (symbol.kind === "workflow") return `Semantic workflow ${symbol.name} returns ${symbol.type}.`;
337
348
  if (symbol.kind === "command") return `Semantic command ${symbol.name} returns ${symbol.type}.`;
@@ -11,6 +11,7 @@ import type {
11
11
  PointCoreTypeExpression,
12
12
  PointCoreValueDeclaration,
13
13
  PointSourceSpan,
14
+ PointSemanticViewControls,
14
15
  } from "../core/ast.ts";
15
16
  import type {
16
17
  PointSemanticBinding,
@@ -35,6 +36,8 @@ import type {
35
36
  PointSemanticTypeExpression,
36
37
  PointSemanticUseDeclaration,
37
38
  PointSemanticViewDeclaration,
39
+ PointSemanticViewStatement,
40
+ PointSemanticPageDeclaration,
38
41
  PointSemanticWorkflowDeclaration,
39
42
  PointSemanticWorkflowStatement,
40
43
  PointSemanticActionDeclaration,
@@ -104,6 +107,7 @@ function buildCallableMap(
104
107
  declaration.kind === "action" ||
105
108
  declaration.kind === "policy" ||
106
109
  declaration.kind === "view" ||
110
+ declaration.kind === "page" ||
107
111
  declaration.kind === "route" ||
108
112
  declaration.kind === "workflow" ||
109
113
  declaration.kind === "command"
@@ -119,6 +123,7 @@ function defaultOutputName(declaration: PointSemanticDeclaration): string {
119
123
  if (declaration.kind === "label") return "label";
120
124
  if (declaration.kind === "policy") return "policy";
121
125
  if (declaration.kind === "view") return "view";
126
+ if (declaration.kind === "page") return "page";
122
127
  if (declaration.kind === "route") return "route";
123
128
  if ("output" in declaration) return toIdentifier(declaration.output.name);
124
129
  return "result";
@@ -152,6 +157,8 @@ function desugarDeclaration(
152
157
  return [desugarPolicy(declaration, records, callables)];
153
158
  case "view":
154
159
  return [desugarView(declaration, records, callables)];
160
+ case "page":
161
+ return [desugarPage(declaration, records, callables)];
155
162
  case "route":
156
163
  return [desugarRoute(declaration, records, callables)];
157
164
  case "workflow":
@@ -296,13 +303,85 @@ function desugarView(
296
303
  const outputType: PointCoreTypeExpression = { kind: "typeRef", name: "Text", args: [] };
297
304
  const { params, bindings } = collectBindings(declaration.inputs, declaration.output);
298
305
  const ctx: DesugarContext = { records, callables, bindings, outputName: "page", outputType };
306
+ const renderStatements = declaration.body.filter(
307
+ (statement): statement is Extract<PointSemanticViewStatement, { kind: "render" | "whenRender" }> =>
308
+ statement.kind === "render" || statement.kind === "whenRender",
309
+ );
310
+ const metadata = semanticDeclarationMetadata(declaration);
311
+ const viewControls = buildViewControls(declaration, ctx);
312
+ if (viewControls) metadata.viewControls = viewControls;
299
313
  return {
300
314
  kind: "function",
301
315
  name: semanticFunctionName(declaration.name, "view", "view"),
302
316
  params,
303
317
  returnType: outputType,
304
- body: desugarViewBody(declaration.body, ctx),
305
- semantic: semanticDeclarationMetadata(declaration),
318
+ body: desugarViewBody(renderStatements, ctx),
319
+ semantic: metadata,
320
+ span: declaration.span,
321
+ };
322
+ }
323
+
324
+ function buildViewControls(
325
+ declaration: PointSemanticViewDeclaration,
326
+ ctx: DesugarContext,
327
+ ): PointSemanticViewControls | undefined {
328
+ const bindStatements = declaration.body.filter(
329
+ (statement): statement is Extract<PointSemanticViewStatement, { kind: "bindCheckbox" }> => statement.kind === "bindCheckbox",
330
+ );
331
+ if (bindStatements.length === 0) return undefined;
332
+
333
+ const onChangeCall = declaration.body.find(
334
+ (statement): statement is Extract<PointSemanticViewStatement, { kind: "onChangeCall" }> => statement.kind === "onChangeCall",
335
+ );
336
+ const handlerInput = declaration.inputs.find((input) => input.type.name === "Handler" && input.type.args.length === 1);
337
+ const callbackLabel = onChangeCall?.callback ?? handlerInput?.label;
338
+ if (!callbackLabel) {
339
+ throw new Error(`View ${declaration.name} with bind checkbox requires input Handler T or on change call`);
340
+ }
341
+ const changeCallback = toIdentifier(callbackLabel);
342
+
343
+ return {
344
+ changeCallback,
345
+ checkboxes: bindStatements.map((statement) => {
346
+ const target = desugarExpression(statement.target, ctx);
347
+ if (target.kind !== "property") {
348
+ throw new Error(`bind checkbox target must be a record field access`);
349
+ }
350
+ if (target.target.kind !== "identifier") {
351
+ throw new Error(`bind checkbox target must start with an input record`);
352
+ }
353
+ return {
354
+ label: statement.label,
355
+ target,
356
+ recordParam: target.target.name,
357
+ fieldName: target.name,
358
+ };
359
+ }),
360
+ };
361
+ }
362
+
363
+ function desugarPage(
364
+ declaration: PointSemanticPageDeclaration,
365
+ records: Map<string, Map<string, string>>,
366
+ callables: Map<string, string>,
367
+ ): PointCoreFunctionDeclaration {
368
+ const outputType: PointCoreTypeExpression = { kind: "typeRef", name: "Text", args: [] };
369
+ const pageType: PointCoreTypeExpression = { kind: "typeRef", name: "Page", args: [] };
370
+ const { params, bindings } = collectBindings(declaration.inputs, { name: "page", type: pageType });
371
+ const ctx: DesugarContext = { records, callables, bindings, outputName: "page", outputType };
372
+ const metadata = semanticDeclarationMetadata(declaration);
373
+ metadata.pageLayout = {
374
+ title: desugarExpression(declaration.title, ctx),
375
+ description: declaration.description ? desugarExpression(declaration.description, ctx) : undefined,
376
+ main: desugarExpression(declaration.main, ctx),
377
+ };
378
+ return {
379
+ kind: "function",
380
+ name: semanticFunctionName(declaration.name, "page", "page"),
381
+ params,
382
+ returnType: outputType,
383
+ body: [{ kind: "return", value: metadata.pageLayout.main, span: declaration.span }],
384
+ semantic: metadata,
306
385
  span: declaration.span,
307
386
  };
308
387
  }
@@ -390,7 +469,7 @@ function desugarParameter(binding: PointSemanticBinding): PointCoreParameter {
390
469
  }
391
470
 
392
471
  function desugarType(type: PointSemanticTypeExpression): PointCoreTypeExpression {
393
- if (type.name === "List" || type.name === "Maybe" || type.name === "Or") {
472
+ if (type.name === "List" || type.name === "Maybe" || type.name === "Or" || type.name === "Handler") {
394
473
  return { kind: "typeRef", name: type.name, args: type.args.map(desugarType) };
395
474
  }
396
475
  const primitives = new Set(["Text", "Int", "Float", "Bool", "Void", "Error", "Page"]);
@@ -32,6 +32,14 @@ export function parseSemanticTypeExpression(source: string): PointSemanticTypeEx
32
32
  if (maybeMatch) {
33
33
  return { kind: "typeRef", name: "Maybe", args: [parseSemanticTypeExpression(maybeMatch[1] ?? "")] };
34
34
  }
35
+ const handlerMatch = trimmed.match(/^Handler\s+(.+)$/);
36
+ if (handlerMatch) {
37
+ return { kind: "typeRef", name: "Handler", args: [parseSemanticTypeExpression(handlerMatch[1] ?? "")] };
38
+ }
39
+ const handlerGenericMatch = trimmed.match(/^Handler<(.+)>$/);
40
+ if (handlerGenericMatch) {
41
+ return { kind: "typeRef", name: "Handler", args: [parseSemanticTypeExpression(handlerGenericMatch[1] ?? "")] };
42
+ }
35
43
  return { kind: "typeRef", name: trimmed, args: [] };
36
44
  }
37
45
 
@@ -138,9 +146,8 @@ function parsePrimaryExpression(source: string, context: PointSemanticExpression
138
146
  return parseRecordExpression(trimmed, context);
139
147
  }
140
148
  if (trimmed.startsWith('"')) {
141
- const end = trimmed.indexOf('"', 1);
142
- const value = JSON.parse(trimmed.slice(0, end + 1));
143
- return { expression: { kind: "literal", value }, consumed: trimmed.slice(end + 1) };
149
+ const literal = parseJsonStringLiteral(trimmed);
150
+ return { expression: { kind: "literal", value: literal.value }, consumed: literal.consumed };
144
151
  }
145
152
  if (/^(true|false|null|none)\b/.test(trimmed)) {
146
153
  const match = trimmed.match(/^(true|false|null|none)\b/);
@@ -183,6 +190,24 @@ function parsePrimaryExpression(source: string, context: PointSemanticExpression
183
190
  throw new Error(`Unable to parse semantic expression: ${source}`);
184
191
  }
185
192
 
193
+ function parseJsonStringLiteral(source: string): { value: string; consumed: string } {
194
+ if (!source.startsWith('"')) throw new Error(`Expected string literal: ${source}`);
195
+ let index = 1;
196
+ while (index < source.length) {
197
+ const char = source[index];
198
+ if (char === "\\") {
199
+ index += 2;
200
+ continue;
201
+ }
202
+ if (char === '"') {
203
+ const value = JSON.parse(source.slice(0, index + 1)) as string;
204
+ return { value, consumed: source.slice(index + 1) };
205
+ }
206
+ index += 1;
207
+ }
208
+ throw new Error(`Unterminated string literal: ${source}`);
209
+ }
210
+
186
211
  function parseListExpression(source: string, context: PointSemanticExpressionContext): { expression: PointSemanticExpression; consumed: string } {
187
212
  let rest = source.trim().slice(1).trimStart();
188
213
  const items: PointSemanticExpression[] = [];
@@ -83,6 +83,14 @@ function formatDeclaration(declaration: PointSemanticDeclaration): string[] {
83
83
  ...formatViewOutput(declaration.output),
84
84
  ...declaration.body.map((statement) => ` ${formatViewStatement(statement)}`),
85
85
  ];
86
+ case "page":
87
+ return [
88
+ `page ${declaration.name}`,
89
+ ...formatInputs(declaration.inputs),
90
+ ` title ${formatExpression(declaration.title)}`,
91
+ ...(declaration.description ? [` description ${formatExpression(declaration.description)}`] : []),
92
+ ` main render ${formatExpression(declaration.main)}`,
93
+ ];
86
94
  case "route":
87
95
  return [
88
96
  `route ${declaration.name}`,
@@ -176,6 +184,8 @@ function formatPolicyStatement(statement: PointSemanticPolicyStatement): string
176
184
 
177
185
  function formatViewStatement(statement: PointSemanticViewStatement): string {
178
186
  if (statement.kind === "whenRender") return `when ${formatExpression(statement.condition)} render ${formatExpression(statement.value)}`;
187
+ if (statement.kind === "bindCheckbox") return `bind checkbox "${statement.label}" to ${formatExpression(statement.target)}`;
188
+ if (statement.kind === "onChangeCall") return `on change call ${statement.callback}`;
179
189
  return `render ${formatExpression(statement.value)}`;
180
190
  }
181
191
 
@@ -30,6 +30,9 @@ export function semanticDeclarationMetadata(declaration: PointSemanticDeclaratio
30
30
  effects: [],
31
31
  };
32
32
  }
33
+ if (declaration.kind === "page") {
34
+ return { kind: "page", name: declaration.name, outputName: "page", effects: [] };
35
+ }
33
36
  if (declaration.kind === "route") {
34
37
  return { kind: "route", name: declaration.name, outputName: declaration.output.name, effects: [] };
35
38
  }
@@ -11,7 +11,7 @@ export function toIdentifier(label: string): string {
11
11
  export function semanticFunctionName(
12
12
  label: string,
13
13
  outputName: string,
14
- kind: "calculation" | "rule" | "label" | "action" | "policy" | "view" | "route" | "workflow" | "command",
14
+ kind: "calculation" | "rule" | "label" | "action" | "policy" | "view" | "page" | "route" | "workflow" | "command",
15
15
  ): string {
16
16
  const base = toIdentifier(label);
17
17
  const suffix =
@@ -21,7 +21,9 @@ export function semanticFunctionName(
21
21
  ? "Policy"
22
22
  : kind === "view"
23
23
  ? "View"
24
- : kind === "route"
24
+ : kind === "page"
25
+ ? "Page"
26
+ : kind === "route"
25
27
  ? "Route"
26
28
  : kind === "workflow"
27
29
  ? "Workflow"
@@ -25,6 +25,7 @@ import type {
25
25
  PointSemanticUseDeclaration,
26
26
  PointSemanticViewDeclaration,
27
27
  PointSemanticViewStatement,
28
+ PointSemanticPageDeclaration,
28
29
  PointSemanticWorkflowDeclaration,
29
30
  PointSemanticWorkflowStatement,
30
31
  PointSemanticBinding,
@@ -129,6 +130,12 @@ export function parseSemanticSource(source: string): PointSemanticProgram {
129
130
  index = parsed.next;
130
131
  continue;
131
132
  }
133
+ if (trimmed.startsWith("page ")) {
134
+ const parsed = parsePage(lines, index, source, records, callables);
135
+ declarations.push(parsed.declaration);
136
+ index = parsed.next;
137
+ continue;
138
+ }
132
139
  if (trimmed.startsWith("route ")) {
133
140
  const parsed = parseRoute(lines, index, source, records, callables);
134
141
  declarations.push(parsed.declaration);
@@ -586,6 +593,24 @@ function parseView(
586
593
  });
587
594
  continue;
588
595
  }
596
+ const bindCheckbox = line.match(/^bind checkbox "(.+)" to (.+)$/);
597
+ if (bindCheckbox) {
598
+ statements.push({
599
+ kind: "bindCheckbox",
600
+ label: bindCheckbox[1] ?? "",
601
+ target: parseLineExpression(bindCheckbox[2] ?? "", context, source, lineNumber),
602
+ span: lineSpan(source, lineNumber),
603
+ });
604
+ continue;
605
+ }
606
+ if (line.startsWith("on change call ")) {
607
+ statements.push({
608
+ kind: "onChangeCall",
609
+ callback: line.slice("on change call ".length).trim(),
610
+ span: lineSpan(source, lineNumber),
611
+ });
612
+ continue;
613
+ }
589
614
  throw new Error(`Unknown view statement: ${line}`);
590
615
  }
591
616
 
@@ -595,6 +620,57 @@ function parseView(
595
620
  };
596
621
  }
597
622
 
623
+ function parsePage(
624
+ lines: string[],
625
+ start: number,
626
+ source: string,
627
+ records: Map<string, Map<string, string>>,
628
+ callables: string[],
629
+ ): { declaration: PointSemanticPageDeclaration; next: number } {
630
+ const name = (lines[start] ?? "").trim().slice("page ".length).trim();
631
+ const body = collectSemanticBody(lines, start + 1);
632
+ const inputs: PointSemanticBinding[] = [];
633
+ const paramTypes = new Map<string, string>();
634
+ const bindings: string[] = [];
635
+ let title: PointSemanticPageDeclaration["title"] | undefined;
636
+ let description: PointSemanticPageDeclaration["description"];
637
+ let main: PointSemanticPageDeclaration["main"] | undefined;
638
+
639
+ for (let lineIndex = 0; lineIndex < body.lines.length; lineIndex += 1) {
640
+ const line = body.lines[lineIndex] ?? "";
641
+ const lineNumber = body.lineNumbers[lineIndex] ?? start + 2;
642
+ if (line.startsWith("input ")) {
643
+ const binding = parseInputBinding(line.slice("input ".length), source, lineNumber);
644
+ inputs.push(binding);
645
+ paramTypes.set(binding.label, typeLabel(binding.type));
646
+ bindings.push(binding.label);
647
+ continue;
648
+ }
649
+ const context = buildExpressionContext({ bindings, paramTypes, recordFields: records, callables });
650
+ if (line.startsWith("title ")) {
651
+ title = parseLineExpression(line.slice("title ".length), context, source, lineNumber);
652
+ continue;
653
+ }
654
+ if (line.startsWith("description ")) {
655
+ description = parseLineExpression(line.slice("description ".length), context, source, lineNumber);
656
+ continue;
657
+ }
658
+ if (line.startsWith("main render ")) {
659
+ main = parseLineExpression(line.slice("main render ".length), context, source, lineNumber);
660
+ continue;
661
+ }
662
+ throw new Error(`Unknown page statement: ${line}`);
663
+ }
664
+
665
+ if (!title) throw new Error(`Page ${name} requires a title`);
666
+ if (!main) throw new Error(`Page ${name} requires main render`);
667
+
668
+ return {
669
+ declaration: { kind: "page", name, inputs, title, description, main, span: lineSpan(source, start + 1) },
670
+ next: body.next,
671
+ };
672
+ }
673
+
598
674
  function parseRoute(
599
675
  lines: string[],
600
676
  start: number,
@@ -893,7 +969,7 @@ function collectSemanticBody(lines: string[], start: number): SemanticBody {
893
969
  }
894
970
 
895
971
  function isSemanticTopLevel(line: string): boolean {
896
- return /^(module|use|record|calculation|rule|label|external|action|policy|view|route|workflow|command)\s+/.test(line);
972
+ return /^(module|use|record|calculation|rule|label|external|action|policy|view|page|route|workflow|command)\s+/.test(line);
897
973
  }
898
974
 
899
975
  function isLoopBoundary(line: string): boolean {
@@ -914,7 +990,10 @@ function typeLabel(type: { kind: "typeRef"; name: string; args: unknown[] }): st
914
990
  if (type.name === "Maybe" && type.args[0]) {
915
991
  return `Maybe<${typeLabel(type.args[0] as { kind: "typeRef"; name: string; args: unknown[] })}>`;
916
992
  }
917
- const primitives = new Set(["Text", "Int", "Float", "Bool", "Void", "Maybe", "Or", "Error", "Page"]);
993
+ if (type.name === "Handler" && type.args[0]) {
994
+ return `Handler ${typeLabel(type.args[0] as { kind: "typeRef"; name: string; args: unknown[] })}`;
995
+ }
996
+ const primitives = new Set(["Text", "Int", "Float", "Bool", "Void", "Maybe", "Or", "Error", "Page", "Handler"]);
918
997
  if (primitives.has(type.name)) return type.name;
919
998
  return toPascalCase(type.name);
920
999
  }
@@ -0,0 +1,30 @@
1
+ type PointStdError = { message: string };
2
+
3
+ async function readResponseText(response: Response): Promise<string | PointStdError> {
4
+ if (!response.ok) {
5
+ return { message: `HTTP ${response.status}: ${response.statusText}` };
6
+ }
7
+ return response.text();
8
+ }
9
+
10
+ export async function httpGet(url: string): Promise<string | PointStdError> {
11
+ try {
12
+ return await readResponseText(await fetch(url));
13
+ } catch (error) {
14
+ return { message: error instanceof Error ? error.message : String(error) };
15
+ }
16
+ }
17
+
18
+ export async function httpPost(url: string, body: string): Promise<string | PointStdError> {
19
+ try {
20
+ return await readResponseText(
21
+ await fetch(url, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
24
+ body,
25
+ }),
26
+ );
27
+ } catch (error) {
28
+ return { message: error instanceof Error ? error.message : String(error) };
29
+ }
30
+ }
@@ -0,0 +1,13 @@
1
+ export type PointStdError = { message: string };
2
+
3
+ export function jsonParse(value: string): string | PointStdError {
4
+ try {
5
+ return JSON.stringify(JSON.parse(value));
6
+ } catch (error) {
7
+ return { message: error instanceof Error ? error.message : String(error) };
8
+ }
9
+ }
10
+
11
+ export function jsonStringify(value: string): string {
12
+ return JSON.stringify(JSON.parse(value));
13
+ }