@uipath/maestro-builder-sdk 6.4.0 → 6.5.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/dist/cli/commands/registry.js +68 -12
- package/dist/registry/archive/entry-policy.d.ts +36 -0
- package/dist/registry/archive/entry-policy.js +60 -0
- package/dist/registry/archive/extract.d.ts +31 -0
- package/dist/registry/archive/extract.js +92 -0
- package/dist/registry/archive/zip-reader.d.ts +38 -0
- package/dist/registry/archive/zip-reader.js +256 -0
- package/dist/registry/codecs/pyjson.d.ts +62 -0
- package/dist/registry/codecs/pyjson.js +208 -0
- package/dist/registry/compat/compare.d.ts +18 -0
- package/dist/registry/compat/compare.js +32 -0
- package/dist/registry/compat/paths.d.ts +4 -0
- package/dist/registry/compat/paths.js +17 -0
- package/dist/registry/compat/pystr.d.ts +50 -0
- package/dist/registry/compat/pystr.js +221 -0
- package/dist/registry/concurrency.d.ts +10 -0
- package/dist/registry/concurrency.js +34 -0
- package/dist/registry/curated-overlay.d.ts +36 -4
- package/dist/registry/curated-overlay.js +87 -14
- package/dist/registry/errors.d.ts +26 -0
- package/dist/registry/errors.js +38 -0
- package/dist/registry/extractor.d.ts +9 -10
- package/dist/registry/extractor.js +5 -15
- package/dist/registry/files.d.ts +26 -0
- package/dist/registry/files.js +160 -0
- package/dist/registry/generators/descriptors.d.ts +81 -0
- package/dist/registry/generators/descriptors.js +405 -0
- package/dist/registry/generators/fields.d.ts +46 -0
- package/dist/registry/generators/fields.js +111 -0
- package/dist/registry/generators/markdown/common.d.ts +21 -0
- package/dist/registry/generators/markdown/common.js +55 -0
- package/dist/registry/generators/markdown/index.d.ts +33 -0
- package/dist/registry/generators/markdown/index.js +37 -0
- package/dist/registry/generators/markdown/page.d.ts +8 -0
- package/dist/registry/generators/markdown/page.js +52 -0
- package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
- package/dist/registry/generators/markdown/pyvalue.js +106 -0
- package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
- package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
- package/dist/registry/generators/markdown/sections/example.js +295 -0
- package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/filtering.js +76 -0
- package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/header.js +37 -0
- package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
- package/dist/registry/generators/markdown/sections/inputs.js +151 -0
- package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
- package/dist/registry/generators/markdown/sections/lookups.js +150 -0
- package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
- package/dist/registry/generators/markdown/sections/outputs.js +9 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
- package/dist/registry/generators/materialize.d.ts +30 -0
- package/dist/registry/generators/materialize.js +132 -0
- package/dist/registry/generators/md-symbols.d.ts +22 -0
- package/dist/registry/generators/md-symbols.js +58 -0
- package/dist/registry/generators/triggers.d.ts +15 -0
- package/dist/registry/generators/triggers.js +85 -0
- package/dist/registry/generators/type-registry.d.ts +50 -0
- package/dist/registry/generators/type-registry.js +154 -0
- package/dist/registry/generators/type-tree.d.ts +65 -0
- package/dist/registry/generators/type-tree.js +179 -0
- package/dist/registry/library-build/build.d.ts +57 -0
- package/dist/registry/library-build/build.js +286 -0
- package/dist/registry/library-build/canonical.d.ts +119 -0
- package/dist/registry/library-build/canonical.js +512 -0
- package/dist/registry/library-build/cli.d.ts +45 -0
- package/dist/registry/library-build/cli.js +182 -0
- package/dist/registry/library-build/context.d.ts +107 -0
- package/dist/registry/library-build/context.js +267 -0
- package/dist/registry/library-build/enrich.d.ts +36 -0
- package/dist/registry/library-build/enrich.js +158 -0
- package/dist/registry/library-build/events.d.ts +108 -0
- package/dist/registry/library-build/events.js +476 -0
- package/dist/registry/library-build/generic-expand.d.ts +77 -0
- package/dist/registry/library-build/generic-expand.js +363 -0
- package/dist/registry/library-build/plan.d.ts +71 -0
- package/dist/registry/library-build/plan.js +270 -0
- package/dist/registry/library-build/py.d.ts +45 -0
- package/dist/registry/library-build/py.js +167 -0
- package/dist/registry/library-build/schema-action.d.ts +38 -0
- package/dist/registry/library-build/schema-action.js +107 -0
- package/dist/registry/library-build/write.d.ts +6 -0
- package/dist/registry/library-build/write.js +26 -0
- package/dist/registry/library-reader.d.ts +20 -0
- package/dist/registry/library-reader.js +50 -0
- package/dist/registry/lookup-specs.d.ts +55 -0
- package/dist/registry/lookup-specs.js +140 -0
- package/dist/registry/materialize-cli.js +28 -14
- package/dist/registry/materializer.d.ts +25 -3
- package/dist/registry/materializer.js +45 -29
- package/dist/registry/model/library.d.ts +254 -0
- package/dist/registry/model/library.js +23 -0
- package/dist/registry/naming.d.ts +54 -0
- package/dist/registry/naming.js +142 -0
- package/dist/registry/orphans.d.ts +4 -0
- package/dist/registry/orphans.js +86 -0
- package/dist/registry/prepare/actions.d.ts +26 -0
- package/dist/registry/prepare/actions.js +94 -0
- package/dist/registry/prepare/connections.d.ts +181 -0
- package/dist/registry/prepare/connections.js +399 -0
- package/dist/registry/prepare/event.d.ts +96 -0
- package/dist/registry/prepare/event.js +268 -0
- package/dist/registry/prepare/handoff.d.ts +21 -0
- package/dist/registry/prepare/handoff.js +91 -0
- package/dist/registry/prepare/ledger.d.ts +104 -0
- package/dist/registry/prepare/ledger.js +216 -0
- package/dist/registry/prepare/messages.d.ts +190 -0
- package/dist/registry/prepare/messages.js +472 -0
- package/dist/registry/prepare/objects.d.ts +130 -0
- package/dist/registry/prepare/objects.js +318 -0
- package/dist/registry/prepare/overlay.d.ts +149 -0
- package/dist/registry/prepare/overlay.js +487 -0
- package/dist/registry/prepare/parents.d.ts +41 -0
- package/dist/registry/prepare/parents.js +122 -0
- package/dist/registry/prepare/pipeline.d.ts +91 -0
- package/dist/registry/prepare/pipeline.js +376 -0
- package/dist/registry/prepare/regenerate.d.ts +9 -0
- package/dist/registry/prepare/regenerate.js +50 -0
- package/dist/registry/prepare/resolve.d.ts +199 -0
- package/dist/registry/prepare/resolve.js +470 -0
- package/dist/registry/prepare/retry.d.ts +83 -0
- package/dist/registry/prepare/retry.js +109 -0
- package/dist/registry/prepare/source-import.d.ts +54 -0
- package/dist/registry/prepare/source-import.js +192 -0
- package/dist/registry/prepare/support.d.ts +41 -0
- package/dist/registry/prepare/support.js +178 -0
- package/dist/registry/prepare-cli.d.ts +16 -5
- package/dist/registry/prepare-cli.js +60 -53
- package/dist/registry/pull-cli.js +8 -7
- package/dist/registry/reporter.d.ts +26 -0
- package/dist/registry/reporter.js +41 -0
- package/dist/registry/uip/client.d.ts +99 -0
- package/dist/registry/uip/client.js +226 -0
- package/dist/registry/uip/commands.d.ts +40 -0
- package/dist/registry/uip/commands.js +80 -0
- package/dist/registry/uip/envelope.d.ts +37 -0
- package/dist/registry/uip/envelope.js +84 -0
- package/dist/registry/uip/response-cache.d.ts +28 -0
- package/dist/registry/uip/response-cache.js +57 -0
- package/lib/README.md +10 -8
- package/package.json +3 -3
- package/dist/generators/_connections.py +0 -331
- package/dist/generators/_events.py +0 -355
- package/dist/generators/_lookups.py +0 -114
- package/dist/generators/_naming.py +0 -185
- package/dist/generators/_objects.py +0 -299
- package/dist/generators/_resolve.py +0 -319
- package/dist/generators/convert_library_to_md.py +0 -1441
- package/dist/generators/extract_library.py +0 -125
- package/dist/generators/generate_connectors_ts.py +0 -936
- package/dist/generators/generate_library_json.py +0 -2527
- package/dist/generators/generate_triggers_ts.py +0 -110
- package/dist/generators/materialize_registry.py +0 -155
- package/dist/generators/prepare_connector.py +0 -1860
- package/dist/registry/python.d.ts +0 -26
- package/dist/registry/python.js +0 -46
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The blockquote on an operation whose real inputs arrive from a live
|
|
3
|
+
* connection (`runtime.requiresConnectionForSchema`). It points the agent at
|
|
4
|
+
* `registry prepare` without claiming every marked operation is incomplete; a
|
|
5
|
+
* discovery entry (`objectFromDiscovery`) is missing its OBJECT rather than
|
|
6
|
+
* field values, so its prose and its `--object` flag differ.
|
|
7
|
+
*/
|
|
8
|
+
import { afterLast, pyStr } from "../../../compat/pystr.js";
|
|
9
|
+
import { dictOf, listOf, pyOr, pyTruthy } from "../pyvalue.js";
|
|
10
|
+
export function schemaDynamicLines(j, nodeType) {
|
|
11
|
+
if (!pyTruthy(dictOf(j.runtime).requiresConnectionForSchema))
|
|
12
|
+
return [];
|
|
13
|
+
const conn = dictOf(j.connector);
|
|
14
|
+
const op = dictOf(j.operation);
|
|
15
|
+
const key = pyStr(pyOr(conn.key, "<key>"));
|
|
16
|
+
const prefix = `uipath.connector.${key}.`;
|
|
17
|
+
const action = nodeType.startsWith(prefix) ? nodeType.slice(prefix.length) : afterLast(nodeType, ".");
|
|
18
|
+
const parents = listOf(pyOr(dictOf(op.schemaAction).parents, []));
|
|
19
|
+
const discovery = pyTruthy(op.objectFromDiscovery);
|
|
20
|
+
const out = [];
|
|
21
|
+
let supply;
|
|
22
|
+
if (discovery) {
|
|
23
|
+
out.push("> ⚠️ **The object is not in this catalog.** This node works on " +
|
|
24
|
+
"any object in the connection, including custom ones, so it " +
|
|
25
|
+
"carries no inputs here — what has to be resolved is the " +
|
|
26
|
+
"OBJECT, not a field value. Author first, then one command " +
|
|
27
|
+
"resolves it; **Choosing the object** below has the rules.");
|
|
28
|
+
supply = "--object <name-as-the-task-said-it>";
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
out.push("> ⚠️ **This operation resolves its real inputs from a live " +
|
|
32
|
+
"connection.** The inputs listed below are only what the " +
|
|
33
|
+
"tenant-agnostic snapshot carries. Do NOT hand-guess the " +
|
|
34
|
+
"missing names.");
|
|
35
|
+
out.push(">");
|
|
36
|
+
if (parents.length) {
|
|
37
|
+
out.push("> Its parents are named outright, with the call that " +
|
|
38
|
+
"resolves each one, under **Resolving the field set** " +
|
|
39
|
+
"below. Read that first.");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
out.push("> Any input whose reference path carries a `{placeholder}` " +
|
|
43
|
+
"is a parent: resolve a value for EVERY parent first, then " +
|
|
44
|
+
"prepare the operation with all of them. Too few parents is " +
|
|
45
|
+
"refused; a wrong VALUE is not — it resolves right back to " +
|
|
46
|
+
"the parent fields.");
|
|
47
|
+
}
|
|
48
|
+
// The `-f` stays visible even when the catalog cannot name the parents.
|
|
49
|
+
supply = parents.length
|
|
50
|
+
? parents.map((name) => `-f ${pyStr(name)}=<value>`).join(" ")
|
|
51
|
+
: "-f <parent-field>=<value> ...";
|
|
52
|
+
}
|
|
53
|
+
out.push(">");
|
|
54
|
+
out.push("> ```bash");
|
|
55
|
+
// ONE spelling on every page, the one `check` prints; no `--connection-id`.
|
|
56
|
+
if (discovery) {
|
|
57
|
+
out.push(`> npx flow-sdk registry prepare ${key} ${action} ${supply}`);
|
|
58
|
+
}
|
|
59
|
+
else if (supply) {
|
|
60
|
+
out.push(`> npx flow-sdk registry prepare ${key} ${action} \\`);
|
|
61
|
+
out.push(`> ${supply}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
out.push(`> npx flow-sdk registry prepare ${key} ${action}`);
|
|
65
|
+
}
|
|
66
|
+
out.push("> ```");
|
|
67
|
+
out.push(">");
|
|
68
|
+
let overlay = "> It writes a `./connectors-local/` overlay the compiler " +
|
|
69
|
+
"auto-detects (its fields are unioned onto this entry).";
|
|
70
|
+
if (!discovery) {
|
|
71
|
+
overlay +=
|
|
72
|
+
" The full loop — how to find the parents and resolve their " +
|
|
73
|
+
"values — is in the connector guide " +
|
|
74
|
+
"(`references/connector-params.md`) under \"Schema-dynamic " +
|
|
75
|
+
"operations: the parent-field loop\".";
|
|
76
|
+
}
|
|
77
|
+
out.push(overlay);
|
|
78
|
+
out.push("> This requires a working live connection. For an explicitly " +
|
|
79
|
+
"offline/validate-only flow that needs no connection-specific fields, " +
|
|
80
|
+
"use the published static inputs below instead; a placeholder " +
|
|
81
|
+
"connection cannot materialize a live schema. If that input list is " +
|
|
82
|
+
"empty, pass `{}`: offline evidence proves operation identity and " +
|
|
83
|
+
"control routing, not the provider payload or dispatch.");
|
|
84
|
+
out.push("");
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Reporter } from "../reporter.js";
|
|
2
|
+
export interface MaterializeOptions {
|
|
3
|
+
/** The extracted library; must hold an `index.json`. */
|
|
4
|
+
libraryJsonPath: string;
|
|
5
|
+
/** Where the Markdown goes. Omit to skip it. */
|
|
6
|
+
libraryMarkdownPath?: string;
|
|
7
|
+
/** Where the descriptor modules go. Omit to generate none. */
|
|
8
|
+
connectorsPath?: string;
|
|
9
|
+
/** Connector keys to emit descriptors for, instead of all of them. */
|
|
10
|
+
connectorSubset?: readonly string[];
|
|
11
|
+
/** Module specifier the generated TypeScript imports the SDK from. */
|
|
12
|
+
connectorImport?: string;
|
|
13
|
+
/** `registry-triggers.json`, for `<key>.triggers.ts` modules beside the descriptors. */
|
|
14
|
+
triggersPath?: string;
|
|
15
|
+
/** Also emit descriptors for GenericExpanded entries (the overlay's case). */
|
|
16
|
+
includeGenericExpanded?: boolean;
|
|
17
|
+
/** Fail when a page cites a symbol the modules do not export. */
|
|
18
|
+
checkSymbols?: boolean;
|
|
19
|
+
reporter?: Reporter;
|
|
20
|
+
}
|
|
21
|
+
export interface MaterializeResult {
|
|
22
|
+
markdownFiles: number;
|
|
23
|
+
typescriptModules: number;
|
|
24
|
+
/** `nodeType → exported const`, for callers that print a handoff. */
|
|
25
|
+
symbols: Map<string, string>;
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function materialize(options: MaterializeOptions): MaterializeResult;
|
|
29
|
+
/** Remove a staging directory left by an interrupted run, if any. */
|
|
30
|
+
export declare function cleanStaging(target: string): void;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn an extracted `library-json/` into the two derived views: Markdown pages
|
|
3
|
+
* an agent reads and TypeScript descriptor modules an author imports.
|
|
4
|
+
*
|
|
5
|
+
* This is `materialize_registry.py` without the subprocesses: the three
|
|
6
|
+
* generators it used to spawn are function calls. What it keeps is every
|
|
7
|
+
* observable rule of the Python — the connectors directory is wiped first, the
|
|
8
|
+
* Markdown is staged beside its target and swapped in, a missing trigger
|
|
9
|
+
* catalogue is an error, the trigger subset is deliberately NOT narrowed to the
|
|
10
|
+
* connector subset (the Python never forwarded it; file counts depend on that),
|
|
11
|
+
* and a required-input gap fails the run after the descriptor modules are
|
|
12
|
+
* written but before the trigger modules are, the way the descriptor
|
|
13
|
+
* generator's `sys.exit` did.
|
|
14
|
+
*
|
|
15
|
+
* New, behind a flag: `checkSymbols` runs the cross-check that used to be a
|
|
16
|
+
* separate script in `unpack-library.sh`, so a page can never cite a symbol
|
|
17
|
+
* the descriptors do not export.
|
|
18
|
+
*/
|
|
19
|
+
import { PACKAGE_NAME } from "../../package-name.js";
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
21
|
+
import { join, resolve } from "node:path";
|
|
22
|
+
import { pyJsonDocument } from "../codecs/pyjson.js";
|
|
23
|
+
import { Refusal } from "../errors.js";
|
|
24
|
+
import { listFiles, resetDir, stageAndSwap, writeText } from "../files.js";
|
|
25
|
+
import { loadLibrary } from "../library-reader.js";
|
|
26
|
+
import { silentReporter } from "../reporter.js";
|
|
27
|
+
import { formatRequiredInputGaps, renderDescriptorModules } from "./descriptors.js";
|
|
28
|
+
import { renderMarkdownLibrary } from "./markdown/index.js";
|
|
29
|
+
import { citedSymbolMismatches, formatMismatches } from "./md-symbols.js";
|
|
30
|
+
import { renderTriggerModules } from "./triggers.js";
|
|
31
|
+
const DEFAULT_IMPORT = PACKAGE_NAME;
|
|
32
|
+
function countFiles(dir, suffix, recursive) {
|
|
33
|
+
if (!dir || !existsSync(dir))
|
|
34
|
+
return 0;
|
|
35
|
+
if (recursive)
|
|
36
|
+
return listFiles(dir, (rel) => rel.endsWith(suffix)).length;
|
|
37
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(suffix)).length;
|
|
38
|
+
}
|
|
39
|
+
/** The pages on disk under a Markdown tree, keyed like `renderMarkdownLibrary` keys them. */
|
|
40
|
+
function readPages(markdownDir) {
|
|
41
|
+
const pages = new Map();
|
|
42
|
+
for (const rel of listFiles(markdownDir, (r) => r.endsWith(".md"))) {
|
|
43
|
+
pages.set(rel, readFileSync(join(markdownDir, rel), "utf8"));
|
|
44
|
+
}
|
|
45
|
+
return pages;
|
|
46
|
+
}
|
|
47
|
+
export function materialize(options) {
|
|
48
|
+
const reporter = options.reporter ?? silentReporter;
|
|
49
|
+
const libraryJson = resolve(options.libraryJsonPath);
|
|
50
|
+
const libraryMd = options.libraryMarkdownPath ? resolve(options.libraryMarkdownPath) : undefined;
|
|
51
|
+
const connectors = options.connectorsPath ? resolve(options.connectorsPath) : undefined;
|
|
52
|
+
if (!existsSync(join(libraryJson, "index.json"))) {
|
|
53
|
+
throw new Refusal(`materialize-registry: missing ${join(libraryJson, "index.json")}`, "LIBRARY_MISSING");
|
|
54
|
+
}
|
|
55
|
+
const outputs = [libraryMd, connectors].filter((p) => p !== undefined);
|
|
56
|
+
if (outputs.includes(libraryJson) || new Set(outputs).size !== outputs.length) {
|
|
57
|
+
throw new Refusal("materialize-registry: input and output directories must be distinct", "OUTPUTS_NOT_DISTINCT");
|
|
58
|
+
}
|
|
59
|
+
const warnings = [];
|
|
60
|
+
const library = loadLibrary(libraryJson);
|
|
61
|
+
if (connectors !== undefined)
|
|
62
|
+
resetDir(connectors);
|
|
63
|
+
let pages;
|
|
64
|
+
if (libraryMd !== undefined) {
|
|
65
|
+
// Generate beside the target and swap, exactly as pull-cli does for
|
|
66
|
+
// library-json: two `registry pull`s racing on the shared final path (a
|
|
67
|
+
// coding agent issues tool calls in parallel) once left one of them with
|
|
68
|
+
// "Directory not empty" mid-write.
|
|
69
|
+
const rendered = renderMarkdownLibrary(library);
|
|
70
|
+
for (const warning of rendered.warnings)
|
|
71
|
+
reporter.warn(warning);
|
|
72
|
+
warnings.push(...rendered.warnings);
|
|
73
|
+
stageAndSwap(libraryMd, (staging) => {
|
|
74
|
+
for (const [rel, text] of rendered.pages)
|
|
75
|
+
writeText(join(staging, rel), text);
|
|
76
|
+
writeText(join(staging, "index.json"), pyJsonDocument(rendered.index));
|
|
77
|
+
});
|
|
78
|
+
pages = rendered.pages;
|
|
79
|
+
}
|
|
80
|
+
let modules = new Map();
|
|
81
|
+
let symbols = new Map();
|
|
82
|
+
if (connectors !== undefined) {
|
|
83
|
+
const output = renderDescriptorModules(library, {
|
|
84
|
+
importSpecifier: options.connectorImport ?? DEFAULT_IMPORT,
|
|
85
|
+
connectors: options.connectorSubset?.filter((c) => c.length > 0),
|
|
86
|
+
includeGenericExpanded: options.includeGenericExpanded,
|
|
87
|
+
});
|
|
88
|
+
modules = output.modules;
|
|
89
|
+
symbols = output.symbols;
|
|
90
|
+
for (const [name, text] of modules)
|
|
91
|
+
writeText(join(connectors, name), text);
|
|
92
|
+
// The descriptor generator wrote every module and then exited 1 on a gap,
|
|
93
|
+
// and `materialize_registry.py` ran it with `check=True`, so the trigger
|
|
94
|
+
// generator that came next never ran. Same here: the descriptor modules
|
|
95
|
+
// are on disk, the failure is as loud, and no `.triggers.ts` is written
|
|
96
|
+
// beside them.
|
|
97
|
+
if (output.requiredInputGaps.length > 0) {
|
|
98
|
+
throw new Refusal(formatRequiredInputGaps(output.requiredInputGaps), "REQUIRED_INPUT_GAP");
|
|
99
|
+
}
|
|
100
|
+
if (options.triggersPath) {
|
|
101
|
+
const triggersPath = resolve(options.triggersPath);
|
|
102
|
+
if (!existsSync(triggersPath)) {
|
|
103
|
+
throw new Refusal(`materialize-registry: missing trigger catalog ${triggersPath}`, "TRIGGERS_MISSING");
|
|
104
|
+
}
|
|
105
|
+
const catalogue = JSON.parse(readFileSync(triggersPath, "utf8"));
|
|
106
|
+
// The connector subset is not forwarded here, as the Python never did.
|
|
107
|
+
for (const [name, text] of renderTriggerModules(catalogue, { importSpecifier: options.connectorImport ?? DEFAULT_IMPORT })) {
|
|
108
|
+
writeText(join(connectors, name), text);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (options.checkSymbols && connectors !== undefined) {
|
|
113
|
+
const checked = pages ?? (libraryMd && existsSync(libraryMd) ? readPages(libraryMd) : undefined);
|
|
114
|
+
if (checked) {
|
|
115
|
+
const bad = citedSymbolMismatches(checked, modules);
|
|
116
|
+
if (bad.length > 0)
|
|
117
|
+
throw new Refusal(formatMismatches(bad), "MD_SYMBOL_MISMATCH");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const result = {
|
|
121
|
+
markdownFiles: countFiles(libraryMd, ".md", true),
|
|
122
|
+
typescriptModules: countFiles(connectors, ".ts", false),
|
|
123
|
+
symbols,
|
|
124
|
+
warnings,
|
|
125
|
+
};
|
|
126
|
+
reporter.info(`materialize-registry: generated ${result.markdownFiles} markdown file(s) and ${result.typescriptModules} TypeScript module(s)`);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
/** Remove a staging directory left by an interrupted run, if any. */
|
|
130
|
+
export function cleanStaging(target) {
|
|
131
|
+
rmSync(`${target}.tmp-${process.pid}`, { recursive: true, force: true });
|
|
132
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every descriptor symbol the Markdown cites must exist in the emitted TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* The `## Example` on each page names an exported const —
|
|
5
|
+
* `connector(SendMessageToUser, …)` — and imports it from
|
|
6
|
+
* `./connectors/<key>.ts`. Neither the const name nor the `.by*` aliases are in
|
|
7
|
+
* the JSON library: both are derived by `naming.symbolIndex`, which the
|
|
8
|
+
* Markdown and descriptor generators share precisely so they cannot disagree.
|
|
9
|
+
* This is the check that proves the sharing is real rather than intended.
|
|
10
|
+
* Ported from `check_md_symbols.py`.
|
|
11
|
+
*/
|
|
12
|
+
/** `{ connectorKey → exported const names }` from `<key>.ts` module texts, `.triggers.ts` excluded. */
|
|
13
|
+
export declare function exportedSymbols(modules: ReadonlyMap<string, string>): Map<string, Set<string>>;
|
|
14
|
+
/**
|
|
15
|
+
* One line per page that cites a symbol its module does not export, as
|
|
16
|
+
* `<page> cites <key>.<Symbol>`, in page order.
|
|
17
|
+
*/
|
|
18
|
+
export declare function citedSymbolMismatches(pages: ReadonlyMap<string, string>, modules: ReadonlyMap<string, string>): string[];
|
|
19
|
+
/** How many mismatches to print before truncating. */
|
|
20
|
+
export declare const MAX_REPORTED = 20;
|
|
21
|
+
/** The stderr text `check_md_symbols.py` printed, for the same mismatches. */
|
|
22
|
+
export declare function formatMismatches(bad: readonly string[]): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every descriptor symbol the Markdown cites must exist in the emitted TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* The `## Example` on each page names an exported const —
|
|
5
|
+
* `connector(SendMessageToUser, …)` — and imports it from
|
|
6
|
+
* `./connectors/<key>.ts`. Neither the const name nor the `.by*` aliases are in
|
|
7
|
+
* the JSON library: both are derived by `naming.symbolIndex`, which the
|
|
8
|
+
* Markdown and descriptor generators share precisely so they cannot disagree.
|
|
9
|
+
* This is the check that proves the sharing is real rather than intended.
|
|
10
|
+
* Ported from `check_md_symbols.py`.
|
|
11
|
+
*/
|
|
12
|
+
/** `export const X =` at a line start; `\w` as Python spells it, Unicode-aware. */
|
|
13
|
+
const EXPORTED = /^export const ([\p{L}\p{N}_]+) =/gmu;
|
|
14
|
+
/** `import { X } from './connectors/<key>.ts'` or `./connectors-local/…`. */
|
|
15
|
+
const CITED = /import \{ ([\p{L}\p{N}_]+) \} from '\.\/connectors(?:-local)?\/([^']+)\.ts'/gu;
|
|
16
|
+
/** `{ connectorKey → exported const names }` from `<key>.ts` module texts, `.triggers.ts` excluded. */
|
|
17
|
+
export function exportedSymbols(modules) {
|
|
18
|
+
const out = new Map();
|
|
19
|
+
for (const [fileName, text] of modules) {
|
|
20
|
+
// `<key>.triggers.ts` is the connector-EVENT surface, a different catalogue
|
|
21
|
+
// with its own identity-only descriptors. Pages never import from it.
|
|
22
|
+
if (!fileName.endsWith(".ts") || fileName.endsWith(".triggers.ts"))
|
|
23
|
+
continue;
|
|
24
|
+
const names = new Set();
|
|
25
|
+
for (const match of text.matchAll(EXPORTED))
|
|
26
|
+
names.add(match[1]);
|
|
27
|
+
out.set(fileName.slice(0, -3), names);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One line per page that cites a symbol its module does not export, as
|
|
33
|
+
* `<page> cites <key>.<Symbol>`, in page order.
|
|
34
|
+
*/
|
|
35
|
+
export function citedSymbolMismatches(pages, modules) {
|
|
36
|
+
const exported = exportedSymbols(modules);
|
|
37
|
+
const bad = [];
|
|
38
|
+
for (const [page, text] of pages) {
|
|
39
|
+
for (const match of text.matchAll(CITED)) {
|
|
40
|
+
const [, symbol, key] = match;
|
|
41
|
+
if (!exported.get(key)?.has(symbol))
|
|
42
|
+
bad.push(`${page} cites ${key}.${symbol}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return bad;
|
|
46
|
+
}
|
|
47
|
+
/** How many mismatches to print before truncating. */
|
|
48
|
+
export const MAX_REPORTED = 20;
|
|
49
|
+
/** The stderr text `check_md_symbols.py` printed, for the same mismatches. */
|
|
50
|
+
export function formatMismatches(bad) {
|
|
51
|
+
const lines = [`error: ${bad.length} markdown page(s) cite a descriptor symbol that was never emitted:`];
|
|
52
|
+
for (const line of bad.slice(0, MAX_REPORTED))
|
|
53
|
+
lines.push(` ${line}`);
|
|
54
|
+
if (bad.length > MAX_REPORTED)
|
|
55
|
+
lines.push(` … and ${bad.length - MAX_REPORTED} more`);
|
|
56
|
+
lines.push(" Both generators derive symbol names through registry/naming.ts (symbolIndex).", " A mismatch means one of them stopped using it. See docs/CONNECTOR_LOOKUP_DESIGN.md §6.4.");
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type TriggerCatalogue, type TriggerCatalogueRow } from "../model/library.js";
|
|
2
|
+
export interface TriggerModulesOptions {
|
|
3
|
+
/** Module specifier for `import { triggerDescriptor }`. */
|
|
4
|
+
importSpecifier?: string;
|
|
5
|
+
/** Connector keys to emit; every connector when omitted or empty. */
|
|
6
|
+
connectors?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `{ "<connector-key>.triggers.ts" → module text }` for the catalogue.
|
|
10
|
+
*
|
|
11
|
+
* Modules come out sorted by connector key, triggers within a module sorted by
|
|
12
|
+
* node type, both by code point — the order the shared symbol derivation
|
|
13
|
+
* assumes.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderTriggerModules(catalogue: TriggerCatalogue | TriggerCatalogueRow[], options?: TriggerModulesOptions): Map<string, string>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-only trigger descriptors from `registry-triggers.json`.
|
|
3
|
+
*
|
|
4
|
+
* The catalogue carries `{ NodeType, DisplayName, Description, Version,
|
|
5
|
+
* connectorKey }` and nothing else, because a trigger's field schema is
|
|
6
|
+
* connection-dependent. So one `triggerDescriptor()` const per event is
|
|
7
|
+
* emitted, one module per connector, and `onEvent`/`waitForEvent` take it in
|
|
8
|
+
* place of the stringly `{ connector, event }`. Ported from
|
|
9
|
+
* `generate_triggers_ts.py`. The module text was verified byte-for-byte
|
|
10
|
+
* against that script before the Python was deleted; the differential went
|
|
11
|
+
* with it, and `tests/registry-triggers-gen.test.ts` now pins the text.
|
|
12
|
+
*/
|
|
13
|
+
import { PACKAGE_NAME } from "../../package-name.js";
|
|
14
|
+
import { pyJsonString, pyJsonStringify } from "../codecs/pyjson.js";
|
|
15
|
+
import { sortedByCodePoint, sortedStrings } from "../compat/compare.js";
|
|
16
|
+
import { afterLast, pyRstrip, pyStrip, upperFirst } from "../compat/pystr.js";
|
|
17
|
+
import { TRIGGER_PREFIX } from "../model/library.js";
|
|
18
|
+
/** The trigger generator's own pascal: same split, `Event` as the empty fallback. */
|
|
19
|
+
function pascalEvent(eventId) {
|
|
20
|
+
const joined = eventId
|
|
21
|
+
.split(/[^A-Za-z0-9]+/)
|
|
22
|
+
.filter((w) => w.length > 0)
|
|
23
|
+
.map((w) => upperFirst(w))
|
|
24
|
+
.join("");
|
|
25
|
+
return joined || "Event";
|
|
26
|
+
}
|
|
27
|
+
function emitTrigger(row, seen) {
|
|
28
|
+
const eventId = afterLast(row.NodeType, ".");
|
|
29
|
+
let name = pascalEvent(eventId);
|
|
30
|
+
while (seen.has(name))
|
|
31
|
+
name += "_";
|
|
32
|
+
seen.add(name);
|
|
33
|
+
const version = "Version" in row ? row.Version : "1.0.0";
|
|
34
|
+
const label = row.DisplayName || eventId;
|
|
35
|
+
const description = pyStrip(String(row.Description || "")).replaceAll("\n", " ");
|
|
36
|
+
let doc = description ? `${label} — ${description}` : label;
|
|
37
|
+
doc = doc.replaceAll("*/", "* /"); // never let a description close the doc comment early
|
|
38
|
+
return [
|
|
39
|
+
`/** ${doc} */`,
|
|
40
|
+
`export const ${name} = triggerDescriptor()({`,
|
|
41
|
+
` connector: ${pyJsonString(row.connectorKey)},`,
|
|
42
|
+
` event: ${pyJsonString(eventId)},`,
|
|
43
|
+
` version: ${pyJsonStringify(version)},`,
|
|
44
|
+
` displayName: ${pyJsonString(label)},`,
|
|
45
|
+
"} as const);",
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `{ "<connector-key>.triggers.ts" → module text }` for the catalogue.
|
|
50
|
+
*
|
|
51
|
+
* Modules come out sorted by connector key, triggers within a module sorted by
|
|
52
|
+
* node type, both by code point — the order the shared symbol derivation
|
|
53
|
+
* assumes.
|
|
54
|
+
*/
|
|
55
|
+
export function renderTriggerModules(catalogue, options = {}) {
|
|
56
|
+
const rows = Array.isArray(catalogue) ? catalogue : catalogue.triggers;
|
|
57
|
+
const subset = new Set((options.connectors ?? []).filter((c) => c.length > 0));
|
|
58
|
+
const importSpecifier = options.importSpecifier ?? PACKAGE_NAME;
|
|
59
|
+
const byConnector = new Map();
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
const nodeType = row.NodeType ?? "";
|
|
62
|
+
if (!nodeType.startsWith(TRIGGER_PREFIX))
|
|
63
|
+
continue; // catalogue is trigger-only, but be defensive
|
|
64
|
+
const key = row.connectorKey;
|
|
65
|
+
if (subset.size > 0 && !subset.has(key))
|
|
66
|
+
continue;
|
|
67
|
+
const group = byConnector.get(key);
|
|
68
|
+
if (group)
|
|
69
|
+
group.push(row);
|
|
70
|
+
else
|
|
71
|
+
byConnector.set(key, [row]);
|
|
72
|
+
}
|
|
73
|
+
const modules = new Map();
|
|
74
|
+
for (const key of sortedStrings(byConnector.keys())) {
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
const body = [`import { triggerDescriptor } from ${pyJsonString(importSpecifier)};`, ""];
|
|
77
|
+
for (const row of sortedByCodePoint(byConnector.get(key), (r) => r.NodeType)) {
|
|
78
|
+
body.push(...emitTrigger(row, seen), "");
|
|
79
|
+
}
|
|
80
|
+
// `"\n".join(body).rstrip() + "\n"`: `pyRstrip`, not `trimEnd`, because the
|
|
81
|
+
// two disagree on which characters are whitespace (`\x1f`, ``).
|
|
82
|
+
modules.set(`${key}.triggers.ts`, `${pyRstrip(body.join("\n"))}\n`);
|
|
83
|
+
}
|
|
84
|
+
return modules;
|
|
85
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type TypeNode, type TypeTree } from "./type-tree.js";
|
|
2
|
+
export interface NamedTypeRegistryOptions {
|
|
3
|
+
/** Appended to every interface name; `"Input"` for the input registry. */
|
|
4
|
+
suffix?: string;
|
|
5
|
+
/** Render every property optional (outputs); inputs follow `required`. */
|
|
6
|
+
allOptional?: boolean;
|
|
7
|
+
/** Merge same-named nodes into one definition (outputs) or reuse exact shapes only (inputs). */
|
|
8
|
+
mergeReused?: boolean;
|
|
9
|
+
/** Wrap property types in `ConnectorValue<…>` / `ConnectorLookupValue<…>`. */
|
|
10
|
+
isInput?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Renders one property line of an interface — `descriptors.ts`'s
|
|
14
|
+
* `emitNestedProperty`, handed to {@link NamedTypeRegistry.emit} so the
|
|
15
|
+
* registry need not import the module that imports it.
|
|
16
|
+
*/
|
|
17
|
+
export type PropertyRenderer = (name: string, node: TypeNode, indent: string, namedTypes: NamedTypeRegistry, allOptional: boolean, isInput: boolean) => string[];
|
|
18
|
+
export declare class NamedTypeRegistry {
|
|
19
|
+
readonly isInput: boolean;
|
|
20
|
+
readonly suffix: string;
|
|
21
|
+
readonly allOptional: boolean;
|
|
22
|
+
readonly mergeReused: boolean;
|
|
23
|
+
private readonly byName;
|
|
24
|
+
/** Node identity → the name it was registered under (the Python's `id(node)` map). */
|
|
25
|
+
private readonly byNode;
|
|
26
|
+
/** `(baseName, fingerprint)` → name, for the exact-shape reuse of the input registry. */
|
|
27
|
+
private readonly shapeNames;
|
|
28
|
+
/** Names in definition order; `emit()` walks it while `register()` may still append. */
|
|
29
|
+
private readonly definitions;
|
|
30
|
+
private readonly registering;
|
|
31
|
+
constructor(options?: NamedTypeRegistryOptions);
|
|
32
|
+
/** Name the node (and, recursively, its object-valued children); return the name. */
|
|
33
|
+
register(fieldName: string, node: TypeNode): string;
|
|
34
|
+
/** Register every root that renders as an interface. */
|
|
35
|
+
discover(roots: TypeTree): void;
|
|
36
|
+
/**
|
|
37
|
+
* The one registered interface an opaque `array` field can be the element
|
|
38
|
+
* type of: `Contact`, or a de-collided `Contact2`, but not when both exist.
|
|
39
|
+
*/
|
|
40
|
+
private knownArrayElement;
|
|
41
|
+
/** The TypeScript type of a property whose value is `node`. */
|
|
42
|
+
typeFor(fieldName: string, node: TypeNode): string;
|
|
43
|
+
/**
|
|
44
|
+
* Every registered interface, one blank line after each. Rendering a
|
|
45
|
+
* definition may register a further nested object, so the walk is by index
|
|
46
|
+
* over a list that can grow while it runs; a definition discovered late is
|
|
47
|
+
* emitted too.
|
|
48
|
+
*/
|
|
49
|
+
emit(renderProperty: PropertyRenderer): string[];
|
|
50
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable named interfaces shared by all operations in one connector module.
|
|
3
|
+
*
|
|
4
|
+
* Every nested object in a connector's field trees becomes an exported
|
|
5
|
+
* interface, named from its segment. The OUTPUT registry merges every node that
|
|
6
|
+
* takes a name into one canonical definition (`Address` is enriched by each
|
|
7
|
+
* operation that returns one). The INPUT registry reuses a name only for an
|
|
8
|
+
* exact shape, because an input type that gained a field from a sibling
|
|
9
|
+
* operation would let an author send a field this operation does not take;
|
|
10
|
+
* a different shape under the same segment gets `AddressInput2`, `…3`.
|
|
11
|
+
* Ported from `NamedTypeRegistry` in `generate_connectors_ts.py`.
|
|
12
|
+
*/
|
|
13
|
+
import { sortedStrings } from "../compat/compare.js";
|
|
14
|
+
import { mergeTypeNodes, nestedTypeName, typeFingerprint } from "./type-tree.js";
|
|
15
|
+
export class NamedTypeRegistry {
|
|
16
|
+
isInput;
|
|
17
|
+
suffix;
|
|
18
|
+
allOptional;
|
|
19
|
+
mergeReused;
|
|
20
|
+
byName = new Map();
|
|
21
|
+
/** Node identity → the name it was registered under (the Python's `id(node)` map). */
|
|
22
|
+
byNode = new Map();
|
|
23
|
+
/** `(baseName, fingerprint)` → name, for the exact-shape reuse of the input registry. */
|
|
24
|
+
shapeNames = new Map();
|
|
25
|
+
/** Names in definition order; `emit()` walks it while `register()` may still append. */
|
|
26
|
+
definitions = [];
|
|
27
|
+
registering = new Set();
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.suffix = options.suffix ?? "";
|
|
30
|
+
this.allOptional = options.allOptional ?? true;
|
|
31
|
+
this.mergeReused = options.mergeReused ?? true;
|
|
32
|
+
this.isInput = options.isInput ?? false;
|
|
33
|
+
}
|
|
34
|
+
/** Name the node (and, recursively, its object-valued children); return the name. */
|
|
35
|
+
register(fieldName, node) {
|
|
36
|
+
const known = this.byNode.get(node);
|
|
37
|
+
if (known)
|
|
38
|
+
return known;
|
|
39
|
+
const baseName = nestedTypeName(fieldName, node, this.suffix);
|
|
40
|
+
let typeName;
|
|
41
|
+
if (this.mergeReused) {
|
|
42
|
+
typeName = baseName;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const shapeKey = JSON.stringify([baseName, typeFingerprint(node)]);
|
|
46
|
+
typeName = this.shapeNames.get(shapeKey) ?? "";
|
|
47
|
+
if (!typeName) {
|
|
48
|
+
typeName = baseName;
|
|
49
|
+
let suffix = 2;
|
|
50
|
+
while (this.byName.has(typeName)) {
|
|
51
|
+
typeName = `${baseName}${suffix}`;
|
|
52
|
+
suffix += 1;
|
|
53
|
+
}
|
|
54
|
+
this.shapeNames.set(shapeKey, typeName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
let canonical = this.byName.get(typeName);
|
|
58
|
+
if (canonical === undefined) {
|
|
59
|
+
canonical = node;
|
|
60
|
+
this.byName.set(typeName, canonical);
|
|
61
|
+
this.definitions.push(typeName);
|
|
62
|
+
}
|
|
63
|
+
else if (canonical !== node) {
|
|
64
|
+
if (this.mergeReused)
|
|
65
|
+
mergeTypeNodes(canonical, node);
|
|
66
|
+
}
|
|
67
|
+
this.byNode.set(node, typeName);
|
|
68
|
+
// A schema can recursively repeat the same segment name
|
|
69
|
+
// (`children[*].children[*]…`). Merge it into the shared interface, but do
|
|
70
|
+
// not recursively rediscover that interface while it is active.
|
|
71
|
+
if (this.registering.has(typeName))
|
|
72
|
+
return typeName;
|
|
73
|
+
this.registering.add(typeName);
|
|
74
|
+
try {
|
|
75
|
+
// Registration can enrich a canonical node, so iterate a snapshot.
|
|
76
|
+
for (const [childName, child] of [...canonical.children]) {
|
|
77
|
+
if (child.children.size > 0 || child.opaqueObject)
|
|
78
|
+
this.register(childName, child);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
this.registering.delete(typeName);
|
|
83
|
+
}
|
|
84
|
+
return typeName;
|
|
85
|
+
}
|
|
86
|
+
/** Register every root that renders as an interface. */
|
|
87
|
+
discover(roots) {
|
|
88
|
+
for (const [fieldName, node] of roots) {
|
|
89
|
+
if (node.children.size > 0 || node.opaqueObject)
|
|
90
|
+
this.register(fieldName, node);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The one registered interface an opaque `array` field can be the element
|
|
95
|
+
* type of: `Contact`, or a de-collided `Contact2`, but not when both exist.
|
|
96
|
+
*/
|
|
97
|
+
knownArrayElement(fieldName, node) {
|
|
98
|
+
const candidate = nestedTypeName(fieldName, node, this.suffix);
|
|
99
|
+
const matches = [];
|
|
100
|
+
for (const name of this.byName.keys()) {
|
|
101
|
+
if (name === candidate || (name.startsWith(candidate) && /^[2-9][0-9]*$/.test(name.slice(candidate.length)))) {
|
|
102
|
+
matches.push(name);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
106
|
+
}
|
|
107
|
+
/** The TypeScript type of a property whose value is `node`. */
|
|
108
|
+
typeFor(fieldName, node) {
|
|
109
|
+
let base;
|
|
110
|
+
if (node.children.size > 0 || node.opaqueObject) {
|
|
111
|
+
base = this.register(fieldName, node);
|
|
112
|
+
}
|
|
113
|
+
else if (node.opaqueArray) {
|
|
114
|
+
const knownElement = this.knownArrayElement(fieldName, node);
|
|
115
|
+
if (knownElement)
|
|
116
|
+
return `Array<${knownElement}>`;
|
|
117
|
+
return "unknown[]";
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
base = sortedStrings(node.declaredTypes).join(" | ") || "unknown";
|
|
121
|
+
}
|
|
122
|
+
const hasArray = node.arrayModes.has(true);
|
|
123
|
+
const hasScalar = node.arrayModes.has(false);
|
|
124
|
+
if (hasArray && hasScalar)
|
|
125
|
+
return `${base} | Array<${base}>`;
|
|
126
|
+
if (hasArray)
|
|
127
|
+
return `Array<${base}>`;
|
|
128
|
+
return base;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Every registered interface, one blank line after each. Rendering a
|
|
132
|
+
* definition may register a further nested object, so the walk is by index
|
|
133
|
+
* over a list that can grow while it runs; a definition discovered late is
|
|
134
|
+
* emitted too.
|
|
135
|
+
*/
|
|
136
|
+
emit(renderProperty) {
|
|
137
|
+
const lines = [];
|
|
138
|
+
let index = 0;
|
|
139
|
+
while (index < this.definitions.length) {
|
|
140
|
+
const typeName = this.definitions[index];
|
|
141
|
+
const node = this.byName.get(typeName);
|
|
142
|
+
lines.push(`export interface ${typeName} {`);
|
|
143
|
+
if (node.opaqueObject)
|
|
144
|
+
lines.push(" [key: string]: unknown;");
|
|
145
|
+
for (const [childName, child] of node.children) {
|
|
146
|
+
lines.push(...renderProperty(childName, child, " ", this, this.allOptional, this.isInput));
|
|
147
|
+
}
|
|
148
|
+
lines.push("}");
|
|
149
|
+
lines.push("");
|
|
150
|
+
index += 1;
|
|
151
|
+
}
|
|
152
|
+
return lines;
|
|
153
|
+
}
|
|
154
|
+
}
|