@hatchingpoint/point 0.0.12 → 0.0.14
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/package.json +8 -2
- package/src/cli.ts +0 -0
- package/src/core/ast.ts +21 -1
- package/src/core/check.ts +10 -2
- package/src/core/cli.ts +87 -23
- package/src/core/context.ts +3 -0
- package/src/core/emit-javascript.ts +5 -1
- package/src/core/emit-python.ts +22 -7
- package/src/core/emit-typescript.ts +84 -3
- package/src/core/index.ts +1 -0
- package/src/core/packages.ts +230 -0
- package/src/core/run-bridge.ts +93 -0
- package/src/core/semantic-source.ts +2 -2
- package/src/core/test-only/legacy-lowering.ts +76 -7
- package/src/semantic/ast.ts +14 -1
- package/src/semantic/callables.ts +2 -1
- package/src/semantic/context.ts +12 -1
- package/src/semantic/desugar.ts +82 -3
- package/src/semantic/expressions.ts +8 -0
- package/src/semantic/format.ts +10 -0
- package/src/semantic/metadata.ts +3 -0
- package/src/semantic/naming.ts +4 -2
- package/src/semantic/parse.ts +81 -2
- package/src/std/env.ts +4 -0
- package/src/std/fs.ts +19 -0
- package/src/std/http.ts +30 -0
- package/src/std/json.ts +13 -0
- package/src/std/text.ts +15 -0
- package/src/std/time.ts +15 -0
package/src/semantic/desugar.ts
CHANGED
|
@@ -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(
|
|
305
|
-
semantic:
|
|
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
|
|
package/src/semantic/format.ts
CHANGED
|
@@ -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
|
|
package/src/semantic/metadata.ts
CHANGED
|
@@ -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
|
}
|
package/src/semantic/naming.ts
CHANGED
|
@@ -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 === "
|
|
24
|
+
: kind === "page"
|
|
25
|
+
? "Page"
|
|
26
|
+
: kind === "route"
|
|
25
27
|
? "Route"
|
|
26
28
|
: kind === "workflow"
|
|
27
29
|
? "Workflow"
|
package/src/semantic/parse.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/std/env.ts
ADDED
package/src/std/fs.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
type PointStdError = { message: string };
|
|
4
|
+
|
|
5
|
+
export function readFile(path: string): string | PointStdError {
|
|
6
|
+
try {
|
|
7
|
+
return readFileSync(path, "utf8");
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return { message: error instanceof Error ? error.message : String(error) };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function writeFile(path: string, contents: string): void | PointStdError {
|
|
14
|
+
try {
|
|
15
|
+
writeFileSync(path, contents, "utf8");
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return { message: error instanceof Error ? error.message : String(error) };
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/std/http.ts
ADDED
|
@@ -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
|
+
}
|
package/src/std/json.ts
ADDED
|
@@ -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
|
+
}
|
package/src/std/text.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function textLength(value: string): number {
|
|
2
|
+
return value.length;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function textContains(value: string, search: string): boolean {
|
|
6
|
+
return value.includes(search);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function textSplit(value: string, separator: string): string[] {
|
|
10
|
+
return value.split(separator);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function textTrim(value: string): string {
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
package/src/std/time.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function now(): string {
|
|
2
|
+
return new Date().toISOString();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function sleep(ms: number): Promise<void> {
|
|
6
|
+
await Bun.sleep(ms);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatTime(value: string): string {
|
|
10
|
+
const date = new Date(value);
|
|
11
|
+
if (Number.isNaN(date.getTime())) {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
return date.toUTCString();
|
|
15
|
+
}
|