@procedures/compiler 0.0.0-stage → 0.3.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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +254 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/checks.d.ts +24 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +123 -0
- package/dist/checks.js.map +1 -0
- package/dist/codegen-closure.d.ts +19 -0
- package/dist/codegen-closure.d.ts.map +1 -0
- package/dist/codegen-closure.js +200 -0
- package/dist/codegen-closure.js.map +1 -0
- package/dist/codegen-text.d.ts +44 -0
- package/dist/codegen-text.d.ts.map +1 -0
- package/dist/codegen-text.js +360 -0
- package/dist/codegen-text.js.map +1 -0
- package/dist/diagnostics.d.ts +67 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +38 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/effects.d.ts +63 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +185 -0
- package/dist/effects.js.map +1 -0
- package/dist/explain.d.ts +58 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +144 -0
- package/dist/explain.js.map +1 -0
- package/dist/facts.d.ts +66 -0
- package/dist/facts.d.ts.map +1 -0
- package/dist/facts.js +102 -0
- package/dist/facts.js.map +1 -0
- package/dist/fold.d.ts +27 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/fold.js +128 -0
- package/dist/fold.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor.d.ts +51 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +116 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/ir.d.ts +240 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +74 -0
- package/dist/ir.js.map +1 -0
- package/dist/mermaid.d.ts +53 -0
- package/dist/mermaid.d.ts.map +1 -0
- package/dist/mermaid.js +423 -0
- package/dist/mermaid.js.map +1 -0
- package/dist/module.d.ts +20 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +124 -0
- package/dist/module.js.map +1 -0
- package/dist/normalize.d.ts +43 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +529 -0
- package/dist/normalize.js.map +1 -0
- package/dist/openrpc.d.ts +63 -0
- package/dist/openrpc.d.ts.map +1 -0
- package/dist/openrpc.js +57 -0
- package/dist/openrpc.js.map +1 -0
- package/dist/print.d.ts +8 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +148 -0
- package/dist/print.js.map +1 -0
- package/dist/procedures/program/check.d.ts +14 -0
- package/dist/procedures/program/check.d.ts.map +1 -0
- package/dist/procedures/program/check.js +30 -0
- package/dist/procedures/program/check.js.map +1 -0
- package/dist/procedures/program/compile.d.ts +14 -0
- package/dist/procedures/program/compile.d.ts.map +1 -0
- package/dist/procedures/program/compile.js +35 -0
- package/dist/procedures/program/compile.js.map +1 -0
- package/dist/procedures/program/diagram.d.ts +14 -0
- package/dist/procedures/program/diagram.d.ts.map +1 -0
- package/dist/procedures/program/diagram.js +23 -0
- package/dist/procedures/program/diagram.js.map +1 -0
- package/dist/procedures/program/effects-output.d.ts +8 -0
- package/dist/procedures/program/effects-output.d.ts.map +1 -0
- package/dist/procedures/program/effects-output.js +13 -0
- package/dist/procedures/program/effects-output.js.map +1 -0
- package/dist/procedures/program/explain.d.ts +14 -0
- package/dist/procedures/program/explain.d.ts.map +1 -0
- package/dist/procedures/program/explain.js +43 -0
- package/dist/procedures/program/explain.js.map +1 -0
- package/dist/procedures/program/index.d.ts +5 -0
- package/dist/procedures/program/index.d.ts.map +1 -0
- package/dist/procedures/program/index.js +5 -0
- package/dist/procedures/program/index.js.map +1 -0
- package/dist/procedures/sdk/index.d.ts +3 -0
- package/dist/procedures/sdk/index.d.ts.map +1 -0
- package/dist/procedures/sdk/index.js +3 -0
- package/dist/procedures/sdk/index.js.map +1 -0
- package/dist/procedures/sdk/manifest.d.ts +11 -0
- package/dist/procedures/sdk/manifest.d.ts.map +1 -0
- package/dist/procedures/sdk/manifest.js +15 -0
- package/dist/procedures/sdk/manifest.js.map +1 -0
- package/dist/procedures/sdk/openrpc.d.ts +11 -0
- package/dist/procedures/sdk/openrpc.d.ts.map +1 -0
- package/dist/procedures/sdk/openrpc.js +13 -0
- package/dist/procedures/sdk/openrpc.js.map +1 -0
- package/dist/procedures/sdk/typescript.d.ts +11 -0
- package/dist/procedures/sdk/typescript.d.ts.map +1 -0
- package/dist/procedures/sdk/typescript.js +14 -0
- package/dist/procedures/sdk/typescript.js.map +1 -0
- package/dist/program.d.ts +84 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +108 -0
- package/dist/program.js.map +1 -0
- package/dist/refs.d.ts +9 -0
- package/dist/refs.d.ts.map +1 -0
- package/dist/refs.js +9 -0
- package/dist/refs.js.map +1 -0
- package/dist/register.d.ts +6 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +8 -0
- package/dist/register.js.map +1 -0
- package/dist/runtime.d.ts +68 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +138 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +41 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +325 -0
- package/dist/schema.js.map +1 -0
- package/dist/scopes.d.ts +25 -0
- package/dist/scopes.d.ts.map +1 -0
- package/dist/scopes.js +202 -0
- package/dist/scopes.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +157 -0
- package/dist/types.js.map +1 -0
- package/dist/typescript-sdk.d.ts +34 -0
- package/dist/typescript-sdk.d.ts.map +1 -0
- package/dist/typescript-sdk.js +231 -0
- package/dist/typescript-sdk.js.map +1 -0
- package/package.json +83 -3
- package/src/checks.ts +128 -0
- package/src/codegen-closure.ts +224 -0
- package/src/codegen-text.ts +396 -0
- package/src/diagnostics.ts +98 -0
- package/src/effects.ts +231 -0
- package/src/explain.ts +194 -0
- package/src/facts.ts +161 -0
- package/src/fold.ts +136 -0
- package/src/index.ts +35 -0
- package/src/interceptor.ts +149 -0
- package/src/ir.ts +330 -0
- package/src/mermaid.ts +460 -0
- package/src/module.ts +171 -0
- package/src/normalize.ts +694 -0
- package/src/openrpc.ts +104 -0
- package/src/print.ts +143 -0
- package/src/procedures/program/check.ts +31 -0
- package/src/procedures/program/compile.ts +36 -0
- package/src/procedures/program/diagram.ts +25 -0
- package/src/procedures/program/effects-output.ts +16 -0
- package/src/procedures/program/explain.ts +46 -0
- package/src/procedures/program/index.ts +4 -0
- package/src/procedures/sdk/index.ts +2 -0
- package/src/procedures/sdk/manifest.ts +17 -0
- package/src/procedures/sdk/openrpc.ts +16 -0
- package/src/procedures/sdk/typescript.ts +17 -0
- package/src/program.ts +181 -0
- package/src/refs.ts +9 -0
- package/src/register.ts +10 -0
- package/src/runtime.ts +188 -0
- package/src/schema.ts +318 -0
- package/src/scopes.ts +196 -0
- package/src/types.ts +318 -0
- package/src/typescript-sdk.ts +282 -0
package/src/openrpc.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An OpenRPC document from a manifest (roadmap R23).
|
|
3
|
+
*
|
|
4
|
+
* The manifest of a host (`describeRegistry()`, `procedure.manifest`, roadmap R17) has the JSON
|
|
5
|
+
* Schemas of each procedure. This module writes them as an OpenRPC 1.3 document: one method for
|
|
6
|
+
* each procedure. A tool of the OpenRPC ecosystem can then show the procedures or make a client.
|
|
7
|
+
*
|
|
8
|
+
* The keys of each object have a fixed order, and the methods are in the order of their names.
|
|
9
|
+
* Thus one manifest always gives the same text, and a golden file can hold it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Effects, ProcedureManifest, ProcedurePath } from "@procedures/client/browser";
|
|
13
|
+
|
|
14
|
+
/** The version of the OpenRPC specification of the documents. */
|
|
15
|
+
export const OPENRPC_VERSION = "1.3.2";
|
|
16
|
+
|
|
17
|
+
/** A content descriptor of OpenRPC: a name and a JSON Schema. */
|
|
18
|
+
export interface OpenRpcContentDescriptor {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly required?: boolean | undefined;
|
|
21
|
+
readonly schema: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** One method of an OpenRPC document: one procedure. */
|
|
25
|
+
export interface OpenRpcMethod {
|
|
26
|
+
/** The key of the procedure: its path with dots. */
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly description?: string | undefined;
|
|
29
|
+
readonly tags?: ReadonlyArray<{ readonly name: string }> | undefined;
|
|
30
|
+
readonly params: readonly [OpenRpcContentDescriptor];
|
|
31
|
+
readonly result: OpenRpcContentDescriptor;
|
|
32
|
+
readonly deprecated?: boolean | undefined;
|
|
33
|
+
/** The path of the procedure. A segment can have a dot, so the name alone does not give the path. */
|
|
34
|
+
readonly "x-path": ProcedurePath;
|
|
35
|
+
/** The declared effects of the procedure. Absent: the procedure does not declare its effects. */
|
|
36
|
+
readonly "x-effects"?: Effects | undefined;
|
|
37
|
+
/** True: the procedure operates its input as a program (control flow, a defined procedure). */
|
|
38
|
+
readonly "x-data-driven"?: boolean | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** An OpenRPC 1.3 document. */
|
|
42
|
+
export interface OpenRpcDocument {
|
|
43
|
+
readonly openrpc: string;
|
|
44
|
+
readonly info: { readonly title: string; readonly version: string; readonly description?: string | undefined };
|
|
45
|
+
readonly methods: OpenRpcMethod[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The options of `toOpenRpc()`. */
|
|
49
|
+
export interface OpenRpcOptions {
|
|
50
|
+
/** The title of the document. The default is "Procedures". */
|
|
51
|
+
readonly title?: string | undefined;
|
|
52
|
+
/** The version of the document. The default is the hash of the manifest. */
|
|
53
|
+
readonly version?: string | undefined;
|
|
54
|
+
/** The description of the document. */
|
|
55
|
+
readonly description?: string | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A copy of a JSON value: only JSON values, and no `undefined`. */
|
|
59
|
+
function jsonCopy(value: unknown): Record<string, unknown> {
|
|
60
|
+
if (value === undefined) return {};
|
|
61
|
+
const copy = JSON.parse(JSON.stringify(value)) as unknown;
|
|
62
|
+
return copy !== null && typeof copy === "object" && !Array.isArray(copy) ? (copy as Record<string, unknown>) : {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The effects with their keys in a fixed order. */
|
|
66
|
+
function effectsCopy(effects: Effects): Effects {
|
|
67
|
+
return {
|
|
68
|
+
...(effects.reads ? { reads: [...effects.reads] } : {}),
|
|
69
|
+
...(effects.writes ? { writes: [...effects.writes] } : {}),
|
|
70
|
+
...(effects.destructive !== undefined ? { destructive: effects.destructive } : {}),
|
|
71
|
+
...(effects.idempotent !== undefined ? { idempotent: effects.idempotent } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* This function writes a manifest as an OpenRPC 1.3 document. Each procedure is a method with
|
|
77
|
+
* its key as the name. The input schema is the one parameter `input`. The output schema is the
|
|
78
|
+
* result `output`. The declared effects are the extension `x-effects`.
|
|
79
|
+
*/
|
|
80
|
+
export function toOpenRpc(manifest: ProcedureManifest, options: OpenRpcOptions = {}): OpenRpcDocument {
|
|
81
|
+
const entries = [...manifest.procedures].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
82
|
+
const methods = entries.map(
|
|
83
|
+
(entry): OpenRpcMethod => ({
|
|
84
|
+
name: entry.key,
|
|
85
|
+
...(entry.description !== undefined ? { description: entry.description } : {}),
|
|
86
|
+
...(entry.tags && entry.tags.length > 0 ? { tags: entry.tags.map((name) => ({ name })) } : {}),
|
|
87
|
+
params: [{ name: "input", required: true, schema: jsonCopy(entry.input) }],
|
|
88
|
+
result: { name: "output", schema: jsonCopy(entry.output) },
|
|
89
|
+
...(entry.deprecated ? { deprecated: true } : {}),
|
|
90
|
+
"x-path": [...entry.path],
|
|
91
|
+
...(entry.effects ? { "x-effects": effectsCopy(entry.effects) } : {}),
|
|
92
|
+
...(entry.dataDriven ? { "x-data-driven": true } : {}),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
return {
|
|
96
|
+
openrpc: OPENRPC_VERSION,
|
|
97
|
+
info: {
|
|
98
|
+
title: options.title ?? "Procedures",
|
|
99
|
+
version: options.version ?? manifest.hash,
|
|
100
|
+
...(options.description !== undefined ? { description: options.description } : {}),
|
|
101
|
+
},
|
|
102
|
+
methods,
|
|
103
|
+
};
|
|
104
|
+
}
|
package/src/print.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A text form of the IR, with one node on each line. The golden tests and the persons who read
|
|
3
|
+
* the result of the compiler use it.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Binding, IRNode, ItemsSpec, ProgramIR, ScopeSnapshot } from "./ir.js";
|
|
7
|
+
|
|
8
|
+
function json(value: unknown): string {
|
|
9
|
+
try {
|
|
10
|
+
const text = JSON.stringify(value);
|
|
11
|
+
return text === undefined ? String(value) : text.length > 80 ? `${text.slice(0, 77)}...` : text;
|
|
12
|
+
} catch {
|
|
13
|
+
return "<not JSON>";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function binding(value: Binding | undefined): string {
|
|
18
|
+
if (!value) return "-";
|
|
19
|
+
return value.name === undefined ? `b${value.id}` : `b${value.id}:${value.name}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scope(snapshot: ScopeSnapshot): string {
|
|
23
|
+
if (!snapshot) return "none";
|
|
24
|
+
return snapshot
|
|
25
|
+
.map((frame) => `[${frame.names.map(([name, value]) => `${name}=${binding(value)}`).join(" ")}${frame.last ? ` $last=${binding(frame.last)}` : ""}]`)
|
|
26
|
+
.join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function extra(value: Readonly<Record<string, unknown>>): string {
|
|
30
|
+
return Object.keys(value).length > 0 ? ` extra=${json(value)}` : "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** This function gives the text of the IR of a program. */
|
|
34
|
+
export function printIR(ir: ProgramIR): string {
|
|
35
|
+
const lines: string[] = [`program mode=${ir.mode}${ir.input ? ` input=${binding(ir.input)}` : ""}`];
|
|
36
|
+
const line = (depth: number, text: string): void => {
|
|
37
|
+
lines.push(`${" ".repeat(depth)}${text}`);
|
|
38
|
+
};
|
|
39
|
+
const items = (spec: ItemsSpec, depth: number): void => {
|
|
40
|
+
if (spec.kind === "list") {
|
|
41
|
+
line(depth, `items list(${spec.items.length})`);
|
|
42
|
+
for (const item of spec.items) visit(item, depth + 1);
|
|
43
|
+
} else {
|
|
44
|
+
line(depth, "items value");
|
|
45
|
+
visit(spec.value, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const visit = (node: IRNode, depth: number): void => {
|
|
49
|
+
const at = ` @${json(node.pointer)}`;
|
|
50
|
+
switch (node.kind) {
|
|
51
|
+
case "literal":
|
|
52
|
+
line(depth, `literal ${node.origin} ${json(node.value)}${at}`);
|
|
53
|
+
return;
|
|
54
|
+
case "object":
|
|
55
|
+
line(depth, `object${at}`);
|
|
56
|
+
for (const entry of node.entries) {
|
|
57
|
+
line(depth + 1, `key ${json(entry.key)}`);
|
|
58
|
+
visit(entry.value, depth + 2);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
case "array":
|
|
62
|
+
line(depth, `array(${node.items.length})${at}`);
|
|
63
|
+
for (const item of node.items) visit(item, depth + 1);
|
|
64
|
+
return;
|
|
65
|
+
case "reference":
|
|
66
|
+
line(depth, `ref ${json(node.text)} -> ${node.binding ? binding(node.binding) : node.deferred ? "data (deferred)" : "unknown"}${at}`);
|
|
67
|
+
return;
|
|
68
|
+
case "call":
|
|
69
|
+
line(depth, `call ${node.key} ${node.position}${extra(node.extra)}${at}`);
|
|
70
|
+
visit(node.input, depth + 1);
|
|
71
|
+
return;
|
|
72
|
+
case "deferred":
|
|
73
|
+
line(depth, `deferred ${node.key} when=${json(node.when)}${at}`);
|
|
74
|
+
visit(node.input, depth + 1);
|
|
75
|
+
return;
|
|
76
|
+
case "dynamic":
|
|
77
|
+
line(depth, `dynamic ${node.key} ${node.position} reason=${node.reason}${extra(node.extra)} scope=${scope(node.scope)}${at}`);
|
|
78
|
+
visit(node.input, depth + 1);
|
|
79
|
+
if (node.model) {
|
|
80
|
+
line(depth + 1, "model");
|
|
81
|
+
visit(node.model, depth + 2);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
case "fail":
|
|
85
|
+
line(depth, `fail ${json(node.message)}${at}`);
|
|
86
|
+
return;
|
|
87
|
+
case "chain":
|
|
88
|
+
line(depth, `chain${node.implicit ? " implicit" : ""} ${node.position}${node.passThrough ? ` passThrough initial=${binding(node.initial)}` : ""}${at}`);
|
|
89
|
+
if (node.initialInput) {
|
|
90
|
+
line(depth + 1, "initialInput");
|
|
91
|
+
visit(node.initialInput, depth + 2);
|
|
92
|
+
}
|
|
93
|
+
for (const step of node.steps) {
|
|
94
|
+
line(depth + 1, `step -> ${binding(step.result)}`);
|
|
95
|
+
visit(step.node, depth + 2);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
case "parallel":
|
|
99
|
+
line(depth, `parallel ${node.position} concurrency=${node.concurrency ?? "all"} failFast=${node.failFast}${at}`);
|
|
100
|
+
for (const task of node.tasks) visit(task, depth + 1);
|
|
101
|
+
return;
|
|
102
|
+
case "conditional":
|
|
103
|
+
line(depth, `conditional ${node.position}${at}`);
|
|
104
|
+
visit(node.condition, depth + 1);
|
|
105
|
+
line(depth + 1, "then");
|
|
106
|
+
if (node.then) visit(node.then, depth + 2);
|
|
107
|
+
line(depth + 1, "else");
|
|
108
|
+
if (node.else) visit(node.else, depth + 2);
|
|
109
|
+
return;
|
|
110
|
+
case "try":
|
|
111
|
+
line(depth, `try ${node.position} error=${binding(node.error)}${at}`);
|
|
112
|
+
visit(node.try, depth + 1);
|
|
113
|
+
line(depth + 1, "catch");
|
|
114
|
+
visit(node.catch, depth + 2);
|
|
115
|
+
return;
|
|
116
|
+
case "and":
|
|
117
|
+
case "or":
|
|
118
|
+
line(depth, `${node.kind} ${node.position}${at}`);
|
|
119
|
+
for (const value of node.values) visit(value, depth + 1);
|
|
120
|
+
return;
|
|
121
|
+
case "map":
|
|
122
|
+
line(depth, `map ${node.position} item=${binding(node.item)} index=${binding(node.index)}${at}`);
|
|
123
|
+
items(node.items, depth + 1);
|
|
124
|
+
if (node.fn) {
|
|
125
|
+
line(depth + 1, "fn");
|
|
126
|
+
visit(node.fn, depth + 2);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
case "reduce":
|
|
130
|
+
line(depth, `reduce ${node.position} acc=${binding(node.acc)} item=${binding(node.item)} index=${binding(node.index)}${at}`);
|
|
131
|
+
if (node.items) items(node.items, depth + 1);
|
|
132
|
+
line(depth + 1, "initial");
|
|
133
|
+
visit(node.initial, depth + 2);
|
|
134
|
+
if (node.fn) {
|
|
135
|
+
line(depth + 1, "fn");
|
|
136
|
+
visit(node.fn, depth + 2);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
visit(ir.root, 1);
|
|
142
|
+
return `${lines.join("\n")}\n`;
|
|
143
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* program.check procedure
|
|
3
|
+
*
|
|
4
|
+
* Check a program before it runs: the names, the procedures, the inputs, the capability and the
|
|
5
|
+
* effects. No call of the program starts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PROCEDURE_REGISTRY, type ProcedureContext } from "@procedures/client/browser";
|
|
9
|
+
import { checkProgram } from "../../program.js";
|
|
10
|
+
import { toCapability, type ProgramCheckInput, type ProgramCheckOutput } from "../../types.js";
|
|
11
|
+
import { effectsOutput } from "./effects-output.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This function checks a program against the registry and the expose rule of the host. The
|
|
15
|
+
* capability is the capability of the input, or the capability of the caller.
|
|
16
|
+
*/
|
|
17
|
+
export async function programCheck(input: ProgramCheckInput, ctx?: ProcedureContext): Promise<ProgramCheckOutput> {
|
|
18
|
+
const result = checkProgram(input.program, {
|
|
19
|
+
registry: ctx?.registry ?? PROCEDURE_REGISTRY,
|
|
20
|
+
expose: ctx?.expose,
|
|
21
|
+
capability: input.capability ? toCapability(input.capability) : ctx?.capability,
|
|
22
|
+
mode: input.mode,
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
ok: result.ok,
|
|
26
|
+
effects: effectsOutput(result.effects),
|
|
27
|
+
mustEffects: result.mustEffects,
|
|
28
|
+
diagnostics: result.diagnostics,
|
|
29
|
+
registryHash: result.registryHash,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* program.compile procedure
|
|
3
|
+
*
|
|
4
|
+
* This procedure compiles a program to a JavaScript function. It checks the program first. Then
|
|
5
|
+
* it writes the text of an ES module.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PROCEDURE_REGISTRY, type ProcedureContext } from "@procedures/client/browser";
|
|
9
|
+
import { compileProgram } from "../../program.js";
|
|
10
|
+
import { toCapability, type ProgramCompileInput, type ProgramCompileOutput } from "../../types.js";
|
|
11
|
+
import { effectsOutput } from "./effects-output.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This function compiles a program against the registry and the expose rule of the host. The
|
|
15
|
+
* output has the source text, not the closure code: a closure does not go through JSON.
|
|
16
|
+
*/
|
|
17
|
+
export async function programCompile(input: ProgramCompileInput, ctx?: ProcedureContext): Promise<ProgramCompileOutput> {
|
|
18
|
+
const result = await compileProgram(input.program, {
|
|
19
|
+
registry: ctx?.registry ?? PROCEDURE_REGISTRY,
|
|
20
|
+
expose: ctx?.expose,
|
|
21
|
+
capability: input.capability ? toCapability(input.capability) : ctx?.capability,
|
|
22
|
+
mode: input.mode,
|
|
23
|
+
fold: input.fold,
|
|
24
|
+
language: input.language,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
ok: result.ok,
|
|
28
|
+
effects: effectsOutput(result.effects),
|
|
29
|
+
mustEffects: result.mustEffects,
|
|
30
|
+
diagnostics: result.diagnostics,
|
|
31
|
+
registryHash: result.registryHash,
|
|
32
|
+
code: result.code,
|
|
33
|
+
sourceMap: result.sourceMap,
|
|
34
|
+
folded: [...result.folded],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* program.diagram procedure
|
|
3
|
+
*
|
|
4
|
+
* This procedure draws a program as a Mermaid flowchart: the order of the calls, the control
|
|
5
|
+
* flow and the names. No call of the program starts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PROCEDURE_REGISTRY, type ProcedureContext } from "@procedures/client/browser";
|
|
9
|
+
import { diagramProgram } from "../../mermaid.js";
|
|
10
|
+
import type { ProgramDiagramInput, ProgramDiagramOutput } from "../../types.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* This function checks a program against the registry and the expose rule of the host. It gives
|
|
14
|
+
* the flowchart of the IR and the diagnostics of the check.
|
|
15
|
+
*/
|
|
16
|
+
export async function programDiagram(input: ProgramDiagramInput, ctx?: ProcedureContext): Promise<ProgramDiagramOutput> {
|
|
17
|
+
const result = diagramProgram(input.program, {
|
|
18
|
+
registry: ctx?.registry ?? PROCEDURE_REGISTRY,
|
|
19
|
+
expose: ctx?.expose,
|
|
20
|
+
capability: ctx?.capability,
|
|
21
|
+
mode: input.mode,
|
|
22
|
+
direction: input.direction,
|
|
23
|
+
});
|
|
24
|
+
return { mermaid: result.mermaid, diagnostics: result.diagnostics };
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effects of a program in the JSON output of the procedures.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Effects } from "@procedures/client/browser";
|
|
6
|
+
import type { EffectsOutput } from "../../types.js";
|
|
7
|
+
|
|
8
|
+
/** This function copies the effects into mutable lists for the output schema. */
|
|
9
|
+
export function effectsOutput(effects: Effects): EffectsOutput {
|
|
10
|
+
return {
|
|
11
|
+
...(effects.reads ? { reads: [...effects.reads] } : {}),
|
|
12
|
+
...(effects.writes ? { writes: [...effects.writes] } : {}),
|
|
13
|
+
...(effects.destructive !== undefined ? { destructive: effects.destructive } : {}),
|
|
14
|
+
...(effects.idempotent !== undefined ? { idempotent: effects.idempotent } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* program.explain procedure
|
|
3
|
+
*
|
|
4
|
+
* This procedure explains a program before it starts: the effects, each call with its declared
|
|
5
|
+
* effects and the answer of the capability, and the diagnostics. No call of the program starts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PROCEDURE_REGISTRY, type ProcedureContext } from "@procedures/client/browser";
|
|
9
|
+
import { explainProgram } from "../../explain.js";
|
|
10
|
+
import { toCapability, type CallExplanationOutput, type ProgramExplainInput, type ProgramExplainOutput } from "../../types.js";
|
|
11
|
+
import { effectsOutput } from "./effects-output.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This function explains a program against the registry and the expose rule of the host. The
|
|
15
|
+
* capability is the capability of the input, or the capability of the caller.
|
|
16
|
+
*/
|
|
17
|
+
export async function programExplain(input: ProgramExplainInput, ctx?: ProcedureContext): Promise<ProgramExplainOutput> {
|
|
18
|
+
const result = explainProgram(input.program, {
|
|
19
|
+
registry: ctx?.registry ?? PROCEDURE_REGISTRY,
|
|
20
|
+
expose: ctx?.expose,
|
|
21
|
+
capability: input.capability ? toCapability(input.capability) : ctx?.capability,
|
|
22
|
+
mode: input.mode,
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
ok: result.ok,
|
|
26
|
+
effects: effectsOutput(result.effects),
|
|
27
|
+
mustEffects: result.mustEffects,
|
|
28
|
+
calls: result.calls.map(
|
|
29
|
+
(call): CallExplanationOutput => ({
|
|
30
|
+
key: call.key,
|
|
31
|
+
path: [...call.path],
|
|
32
|
+
pointer: call.pointer,
|
|
33
|
+
kind: call.kind,
|
|
34
|
+
...(call.effects ? { effects: effectsOutput(call.effects) } : {}),
|
|
35
|
+
known: call.known,
|
|
36
|
+
exposed: call.exposed,
|
|
37
|
+
permitted: call.permitted,
|
|
38
|
+
...(call.reason !== undefined ? { reason: call.reason } : {}),
|
|
39
|
+
message: call.message,
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
diagnostics: result.diagnostics,
|
|
43
|
+
registryHash: result.registryHash,
|
|
44
|
+
summary: result.summary,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The manifest that the procedures `sdk.openrpc` and `sdk.typescript` read.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describeRegistry, PROCEDURE_REGISTRY, type AnyProcedure, type ProcedureContext, type ProcedureManifest } from "@procedures/client/browser";
|
|
6
|
+
import type { SdkInput } from "../../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This function describes the registry of the caller, as `procedure.manifest` does. The manifest
|
|
10
|
+
* has only the procedures that the host exposes, and only one namespace when the input names it.
|
|
11
|
+
*/
|
|
12
|
+
export function manifestOf(input: SdkInput, ctx?: ProcedureContext): ProcedureManifest {
|
|
13
|
+
return describeRegistry(ctx?.registry ?? PROCEDURE_REGISTRY, {
|
|
14
|
+
...(ctx?.expose ? { expose: ctx.expose } : {}),
|
|
15
|
+
...(input.namespace ? { include: (procedure: AnyProcedure) => procedure.path[0] === input.namespace } : {}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sdk.openrpc procedure
|
|
3
|
+
*
|
|
4
|
+
* This procedure describes the procedures of the registry as an OpenRPC 1.3 document. It reads
|
|
5
|
+
* the registry of the caller and obeys the expose rule of the host, as `procedure.manifest` does.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ProcedureContext } from "@procedures/client/browser";
|
|
9
|
+
import { toOpenRpc } from "../../openrpc.js";
|
|
10
|
+
import type { SdkOpenrpcInput, SdkOpenrpcOutput } from "../../types.js";
|
|
11
|
+
import { manifestOf } from "./manifest.js";
|
|
12
|
+
|
|
13
|
+
/** This function gives the OpenRPC document of the procedures that the host exposes. */
|
|
14
|
+
export async function sdkOpenrpc(input: SdkOpenrpcInput, ctx?: ProcedureContext): Promise<SdkOpenrpcOutput> {
|
|
15
|
+
return toOpenRpc(manifestOf(input, ctx)) as unknown as SdkOpenrpcOutput;
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sdk.typescript procedure
|
|
3
|
+
*
|
|
4
|
+
* This procedure writes a typed TypeScript SDK for the procedures of the registry. It reads the
|
|
5
|
+
* registry of the caller and obeys the expose rule of the host, as `procedure.manifest` does.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ProcedureContext } from "@procedures/client/browser";
|
|
9
|
+
import { toTypeScriptSdk } from "../../typescript-sdk.js";
|
|
10
|
+
import type { SdkTypescriptInput, SdkTypescriptOutput } from "../../types.js";
|
|
11
|
+
import { manifestOf } from "./manifest.js";
|
|
12
|
+
|
|
13
|
+
/** This function gives the source of the SDK of the procedures that the host exposes. */
|
|
14
|
+
export async function sdkTypescript(input: SdkTypescriptInput, ctx?: ProcedureContext): Promise<SdkTypescriptOutput> {
|
|
15
|
+
const manifest = manifestOf(input, ctx);
|
|
16
|
+
return { code: toTypeScriptSdk(manifest), procedures: manifest.procedures.length, registryHash: manifest.hash };
|
|
17
|
+
}
|
package/src/program.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The functions of the compiler: `checkProgram()` and `compileProgram()`.
|
|
3
|
+
*
|
|
4
|
+
* Both read the facts of the procedures from a manifest (a browser) or from a registry (a host).
|
|
5
|
+
* They run the passes in order: normalize, scopes, effects and checks. `compileProgram()` then
|
|
6
|
+
* folds the pure calls (optional) and writes the code: the closure code and the source text.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { pathToKey, type Capability, type Effects, type ProcedureManifest, type ProcedurePath } from "@procedures/client/browser";
|
|
10
|
+
import type { ProgramIR } from "./ir.js";
|
|
11
|
+
import { manifestFacts, registryFacts, withDefinitions, type ProcedureFacts, type RegistryLike } from "./facts.js";
|
|
12
|
+
import { findDefinitions, normalize } from "./normalize.js";
|
|
13
|
+
import { resolveScopes } from "./scopes.js";
|
|
14
|
+
import { inferEffects, toEffects, type EffectSummary } from "./effects.js";
|
|
15
|
+
import { checkCalls } from "./checks.js";
|
|
16
|
+
import { hasErrors, sortDiagnostics, type Diagnostic } from "./diagnostics.js";
|
|
17
|
+
import { foldProgram } from "./fold.js";
|
|
18
|
+
import { compileClosure } from "./codegen-closure.js";
|
|
19
|
+
import { emitText, type SourceMapLine } from "./codegen-text.js";
|
|
20
|
+
import type { CompiledProgram } from "./runtime.js";
|
|
21
|
+
|
|
22
|
+
/** The options of `checkProgram()`. Give a manifest, a registry or facts. */
|
|
23
|
+
export interface CheckOptions {
|
|
24
|
+
/** The manifest of the host (`procedure.manifest`). A browser has only this. */
|
|
25
|
+
readonly manifest?: ProcedureManifest | undefined;
|
|
26
|
+
/** The registry of the host. It also gives the bodies of the defined procedures. */
|
|
27
|
+
readonly registry?: RegistryLike | undefined;
|
|
28
|
+
/** The facts of the procedures, when the caller made them already. */
|
|
29
|
+
readonly facts?: ProcedureFacts | undefined;
|
|
30
|
+
/** The expose rule of the host. The default exposes each procedure. */
|
|
31
|
+
readonly expose?: ((path: ProcedurePath) => boolean) | undefined;
|
|
32
|
+
/** The capability of the run. The check refuses each call that the capability refuses. */
|
|
33
|
+
readonly capability?: Capability<unknown> | undefined;
|
|
34
|
+
/** `exec` (the default): the program runs as `client.exec()` runs it. `body`: as the body of a defined procedure. */
|
|
35
|
+
readonly mode?: "exec" | "body" | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The effects of each run in which no call fails. */
|
|
39
|
+
export interface MustEffects {
|
|
40
|
+
readonly reads: string[];
|
|
41
|
+
readonly writes: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The result of `checkProgram()`. */
|
|
45
|
+
export interface CheckResult {
|
|
46
|
+
/** True when no diagnostic is an error. A host starts the program only then. */
|
|
47
|
+
readonly ok: boolean;
|
|
48
|
+
/** The effects that a run can have (the upper bound). An absent `reads` or `writes`: not known. */
|
|
49
|
+
readonly effects: Effects;
|
|
50
|
+
/** The effects of each run in which no call fails (the lower bound). */
|
|
51
|
+
readonly mustEffects: MustEffects;
|
|
52
|
+
/** The problems, in the order of the program. */
|
|
53
|
+
readonly diagnostics: Diagnostic[];
|
|
54
|
+
/** The hash of the manifest that the check used. */
|
|
55
|
+
readonly registryHash: string;
|
|
56
|
+
/** The IR of the program. */
|
|
57
|
+
readonly ir: ProgramIR;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The options of `compileProgram()`. */
|
|
61
|
+
export interface CompileOptions extends CheckOptions {
|
|
62
|
+
/** True: compute the pure core calls with literal inputs at compile time (the fold pass). The default is false. */
|
|
63
|
+
readonly fold?: boolean | undefined;
|
|
64
|
+
/** The language of the source text: `js` (the default) or `ts`. */
|
|
65
|
+
readonly language?: "js" | "ts" | undefined;
|
|
66
|
+
/** The import specifier of the runtime in the source text. The default is `@procedures/compiler/runtime`. */
|
|
67
|
+
readonly runtime?: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The result of `compileProgram()`. */
|
|
71
|
+
export interface CompileResult extends CheckResult {
|
|
72
|
+
/** The closure code: a host runs it as `run(ctx, input)`. It uses no `eval`. */
|
|
73
|
+
readonly run: CompiledProgram;
|
|
74
|
+
/** The source text: an ES module whose default export is `async (ctx, input) => output`. */
|
|
75
|
+
readonly code: string;
|
|
76
|
+
/** The body of an async function with the parameters `rt`, `ctx` and `input` (JavaScript). */
|
|
77
|
+
readonly body: string;
|
|
78
|
+
/** The lines of `code` and the pointers of their parts of the program. */
|
|
79
|
+
readonly sourceMap: SourceMapLine[];
|
|
80
|
+
/** The pointers of the calls that the fold pass computed. */
|
|
81
|
+
readonly folded: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The facts of the options: the facts, else the facts of the registry, else the facts of the manifest. */
|
|
85
|
+
export function baseFacts(options: CheckOptions): ProcedureFacts {
|
|
86
|
+
if (options.facts) return options.facts;
|
|
87
|
+
if (options.registry) return registryFacts(options.registry, { expose: options.expose });
|
|
88
|
+
if (options.manifest) return manifestFacts(options.manifest, { expose: options.expose });
|
|
89
|
+
throw new Error("checkProgram: give a manifest, a registry or facts");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface Analysis {
|
|
93
|
+
readonly facts: ProcedureFacts;
|
|
94
|
+
readonly ir: ProgramIR;
|
|
95
|
+
readonly diagnostics: Diagnostic[];
|
|
96
|
+
readonly program: EffectSummary;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function analyze(program: unknown, options: CheckOptions): Analysis {
|
|
100
|
+
const definitions = findDefinitions(program);
|
|
101
|
+
const facts = withDefinitions(baseFacts(options), definitions);
|
|
102
|
+
const diagnostics: Diagnostic[] = [];
|
|
103
|
+
|
|
104
|
+
// The effects of a defined body: a recursive definition gives unknown effects
|
|
105
|
+
const bodies = new Map<string, EffectSummary | "pending">();
|
|
106
|
+
const bodyEffects = (path: ProcedurePath): EffectSummary | undefined => {
|
|
107
|
+
const key = pathToKey(path);
|
|
108
|
+
const cached = bodies.get(key);
|
|
109
|
+
if (cached === "pending") return undefined;
|
|
110
|
+
if (cached) return cached;
|
|
111
|
+
const body = facts.body(path);
|
|
112
|
+
if (body === undefined) return undefined;
|
|
113
|
+
bodies.set(key, "pending");
|
|
114
|
+
const { ir } = normalize(body, facts, { mode: "body" });
|
|
115
|
+
resolveScopes(ir);
|
|
116
|
+
const summary = inferEffects(ir.root, facts, bodyEffects).program;
|
|
117
|
+
bodies.set(key, summary);
|
|
118
|
+
return summary;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// The bodies that the program defines are a part of the program: their problems have pointers in it
|
|
122
|
+
for (const definition of definitions.values()) {
|
|
123
|
+
const body = normalize(definition.body, facts, { mode: "body", base: definition.pointer });
|
|
124
|
+
diagnostics.push(...body.diagnostics, ...resolveScopes(body.ir));
|
|
125
|
+
diagnostics.push(...checkCalls(body.ir.root, facts, inferEffects(body.ir.root, facts, bodyEffects)));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = normalize(program, facts, { mode: options.mode ?? "exec" });
|
|
129
|
+
diagnostics.push(...result.diagnostics, ...resolveScopes(result.ir));
|
|
130
|
+
const effects = inferEffects(result.ir.root, facts, bodyEffects);
|
|
131
|
+
diagnostics.push(...checkCalls(result.ir.root, facts, effects, { capability: options.capability }));
|
|
132
|
+
return { facts, ir: result.ir, diagnostics, program: effects.program };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function summarize(analysis: Analysis, diagnostics: Diagnostic[]): Omit<CheckResult, "ir"> {
|
|
136
|
+
const sorted = sortDiagnostics(diagnostics);
|
|
137
|
+
return {
|
|
138
|
+
ok: !hasErrors(sorted),
|
|
139
|
+
effects: toEffects(analysis.program.may),
|
|
140
|
+
mustEffects: { reads: [...analysis.program.must.reads].sort(), writes: [...analysis.program.must.writes].sort() },
|
|
141
|
+
diagnostics: sorted,
|
|
142
|
+
registryHash: analysis.facts.hash,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* This function checks a program before it starts. It finds each unknown name and procedure, and
|
|
148
|
+
* each input that does not agree with its schema. It also finds each call that the capability
|
|
149
|
+
* refuses, and the effects of the whole program. No call starts.
|
|
150
|
+
*/
|
|
151
|
+
export function checkProgram(program: unknown, options: CheckOptions): CheckResult {
|
|
152
|
+
const analysis = analyze(program, options);
|
|
153
|
+
return { ...summarize(analysis, analysis.diagnostics), ir: analysis.ir };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* This function checks a program and writes it as a function: the closure code for a host and
|
|
158
|
+
* the source text for an export. The code makes the calls of the interpreter in the same order.
|
|
159
|
+
*/
|
|
160
|
+
export async function compileProgram(program: unknown, options: CompileOptions): Promise<CompileResult> {
|
|
161
|
+
const analysis = analyze(program, options);
|
|
162
|
+
let ir = analysis.ir;
|
|
163
|
+
let folded: readonly string[] = [];
|
|
164
|
+
if (options.fold) {
|
|
165
|
+
const result = await foldProgram(ir.root, analysis.facts);
|
|
166
|
+
ir = { ...ir, root: result.root };
|
|
167
|
+
folded = result.folded;
|
|
168
|
+
}
|
|
169
|
+
const rawKeys = analysis.facts.rawKeys;
|
|
170
|
+
const run = compileClosure(ir, { rawKeys });
|
|
171
|
+
const text = emitText(ir, { language: options.language, runtime: options.runtime, registryHash: analysis.facts.hash, rawKeys });
|
|
172
|
+
return {
|
|
173
|
+
...summarize(analysis, [...analysis.diagnostics, ...text.diagnostics]),
|
|
174
|
+
ir,
|
|
175
|
+
run,
|
|
176
|
+
code: text.module,
|
|
177
|
+
body: text.body,
|
|
178
|
+
sourceMap: text.sourceMap,
|
|
179
|
+
folded,
|
|
180
|
+
};
|
|
181
|
+
}
|
package/src/refs.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The test of a `$ref` object, as `isOutputRef()` of the client has it (the browser entry of the
|
|
3
|
+
* client does not export that function).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** True for an object with a `$ref` string (also an inherited one, as the `in` test of the client). */
|
|
7
|
+
export function isOutputRef(value: unknown): value is { readonly $ref: string } {
|
|
8
|
+
return typeof value === "object" && value !== null && "$ref" in value && typeof (value as { $ref?: unknown }).$ref === "string";
|
|
9
|
+
}
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing this file registers the procedures of @procedures/compiler in the global registry:
|
|
3
|
+
* `import "@procedures/compiler/register"`. The package root registers nothing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { registerCompilerProcedures } from "./module.js";
|
|
7
|
+
|
|
8
|
+
export * from "./module.js";
|
|
9
|
+
|
|
10
|
+
registerCompilerProcedures();
|