@nanobpm/urban 0.48.0 → 0.49.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/README.md +80 -21
- package/dist/runtime/adapters/deno.js +22 -4
- package/dist/runtime/adapters/node.js +34 -4
- package/dist/runtime/core/context.d.ts +0 -4
- package/dist/runtime/core/glob.d.ts +9 -0
- package/dist/runtime/core/glob.js +21 -0
- package/dist/runtime/core/host.d.ts +71 -8
- package/dist/runtime/core/host.js +13 -0
- package/dist/runtime/core/manifest.d.ts +89 -2
- package/dist/runtime/core/manifest.js +72 -0
- package/dist/runtime/core/modules/deploy.d.ts +9 -3
- package/dist/runtime/core/modules/deploy.js +69 -37
- package/dist/runtime/core/modules/instance-tracking.d.ts +1 -1
- package/dist/runtime/core/modules/instance-tracking.js +20 -5
- package/dist/runtime/core/modules/pages.js +34 -2
- package/dist/runtime/core/modules/surfaces.js +192 -18
- package/dist/runtime/core/router.d.ts +7 -0
- package/dist/runtime/core/router.js +9 -0
- package/dist/runtime/core/runtime.d.ts +0 -5
- package/dist/runtime/core/runtime.js +27 -4
- package/dist/runtime/core/validate.js +65 -1
- package/dist/runtime/engine/nanosdk.d.ts +9 -6
- package/dist/runtime/engine/nanosdk.js +65 -1
- package/dist/runtime/index.d.ts +3 -5
- package/dist/runtime/index.js +3 -4
- package/dist/runtime/run.js +0 -1
- package/dist/toolkit/fsio.js +9 -0
- package/dist/toolkit/gen.d.ts +26 -1
- package/dist/toolkit/gen.js +54 -6
- package/dist/toolkit/models.d.ts +6 -4
- package/dist/toolkit/models.js +7 -4
- package/package.json +1 -1
- package/src/runtime/adapters/deno.ts +26 -6
- package/src/runtime/adapters/node.test.ts +67 -0
- package/src/runtime/adapters/node.ts +37 -4
- package/src/runtime/core/context.ts +0 -4
- package/src/runtime/core/glob.ts +24 -0
- package/src/runtime/core/host.ts +74 -4
- package/src/runtime/core/manifest.test.ts +61 -1
- package/src/runtime/core/manifest.ts +136 -2
- package/src/runtime/core/modules/actions.test.ts +1 -0
- package/src/runtime/core/modules/api.test.ts +1 -0
- package/src/runtime/core/modules/cancel.test.ts +1 -0
- package/src/runtime/core/modules/connectors.test.ts +1 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +1 -0
- package/src/runtime/core/modules/deploy.test.ts +97 -161
- package/src/runtime/core/modules/deploy.ts +69 -37
- package/src/runtime/core/modules/instance-tracking.test.ts +90 -0
- package/src/runtime/core/modules/instance-tracking.ts +19 -6
- package/src/runtime/core/modules/pages.test.ts +43 -0
- package/src/runtime/core/modules/pages.ts +34 -2
- package/src/runtime/core/modules/surfaces.test.ts +198 -5
- package/src/runtime/core/modules/surfaces.ts +191 -18
- package/src/runtime/core/modules/triggers.test.ts +1 -0
- package/src/runtime/core/modules/workers.test.ts +1 -0
- package/src/runtime/core/router.test.ts +10 -1
- package/src/runtime/core/router.ts +10 -0
- package/src/runtime/core/runtime.test.ts +67 -1
- package/src/runtime/core/runtime.ts +28 -9
- package/src/runtime/core/validate.test.ts +145 -0
- package/src/runtime/core/validate.ts +64 -1
- package/src/runtime/engine/nanosdk.test.ts +122 -0
- package/src/runtime/engine/nanosdk.ts +72 -2
- package/src/runtime/index.ts +11 -4
- package/src/runtime/run.ts +0 -1
- package/src/toolkit/fsio.ts +8 -0
- package/src/toolkit/gen.test.ts +46 -10
- package/src/toolkit/gen.ts +59 -6
- package/src/toolkit/models.test.ts +5 -5
- package/src/toolkit/models.ts +8 -5
- package/dist/runtime/core/modules/templates.d.ts +0 -18
- package/dist/runtime/core/modules/templates.js +0 -133
- package/src/runtime/core/modules/templates.ts +0 -153
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
// Template substitution for deployed model resources. A model file (`.bpmn`/`.dmn`/`.form`) may
|
|
2
|
-
// carry `{{ name }}` placeholders — e.g. an agent service task whose prompt lives in a
|
|
3
|
-
// `zeebe:header` value. Before deploy we replace each placeholder with the named template's
|
|
4
|
-
// content, escaped for the resource's content-type, so a large asset (a prompt) is authored once
|
|
5
|
-
// in a file and inlined into the model at deploy time rather than hand-pasted into XML or shipped
|
|
6
|
-
// as a bulky per-instance process variable.
|
|
7
|
-
//
|
|
8
|
-
// Templates come from two sources, merged in order (later wins on a name collision):
|
|
9
|
-
// 1. the manifest `models.templates` (globs, or a bare directory that is scanned), and
|
|
10
|
-
// 2. a programmatic `templates` deploy option (globs, or an explicit `name → content` map).
|
|
11
|
-
// A template's name is its file's stem (basename without extension): `prompts/review-round.md`
|
|
12
|
-
// resolves the placeholder `{{review-round}}`.
|
|
13
|
-
|
|
14
|
-
import type { HostContext } from "../host.ts";
|
|
15
|
-
import { expandPatterns } from "../glob.ts";
|
|
16
|
-
|
|
17
|
-
/** A template source: a list of globs/directories/files (name = file stem), or an explicit
|
|
18
|
-
* `name → content` map. */
|
|
19
|
-
export type TemplateSource = string[] | Record<string, string>;
|
|
20
|
-
|
|
21
|
-
/** A file's stem: basename without its final extension (`a/b/review-round.md` → `review-round`). */
|
|
22
|
-
function stem(path: string): string {
|
|
23
|
-
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
24
|
-
const dot = base.lastIndexOf(".");
|
|
25
|
-
return dot > 0 ? base.slice(0, dot) : base;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// A template name is referenceable only if it matches the placeholder charset (`[\w.-]`, see
|
|
29
|
-
// `PLACEHOLDER`). A file stem outside that set (e.g. containing spaces) could be loaded but never
|
|
30
|
-
// referenced, and its `{{…}}` would not even be detected as unresolved — so we reject it loudly.
|
|
31
|
-
const VALID_NAME = /^[\w.-]+$/;
|
|
32
|
-
|
|
33
|
-
/** Files contributed by one entry: a glob (`prompts/*.md`), a bare directory (scanned, non-glob),
|
|
34
|
-
* or a literal file. Returns root-prefixed paths, matching `expandPatterns`. */
|
|
35
|
-
async function filesFor(host: HostContext, root: string, entry: string): Promise<string[]> {
|
|
36
|
-
if (entry.includes("*")) return expandPatterns(host, root, [entry]);
|
|
37
|
-
const trimmed = entry.replace(/\/+$/, "");
|
|
38
|
-
// A non-glob entry is either a directory (scan it) or a literal file. Scan as a directory first;
|
|
39
|
-
// a non-empty directory resolves here.
|
|
40
|
-
const scanned = await expandPatterns(host, root, [`${trimmed}/*`]);
|
|
41
|
-
if (scanned.length > 0) return scanned;
|
|
42
|
-
// No children matched: the entry is an empty (or subdir-only) directory, a literal file, or
|
|
43
|
-
// missing. A directory never appears in its parent's `listDir` (which yields file names only),
|
|
44
|
-
// so if the parent lists this basename it is a real file to read; otherwise treat the entry as an
|
|
45
|
-
// empty template directory that contributes nothing — and, crucially, emit no misleading
|
|
46
|
-
// "unreadable entry" warning for a valid-but-empty directory (readTextFile on it would EISDIR).
|
|
47
|
-
const slash = trimmed.lastIndexOf("/");
|
|
48
|
-
const base = trimmed.slice(slash + 1);
|
|
49
|
-
const parent = slash > 0 ? trimmed.slice(0, slash) : "";
|
|
50
|
-
const parentDir = parent ? `${root.replace(/\/+$/, "")}/${parent}` : root;
|
|
51
|
-
const siblings = await host.listDir(parentDir);
|
|
52
|
-
return siblings.includes(base) ? expandPatterns(host, root, [trimmed]) : [];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Build the `name → content` template map from the given sources in order (later sources win on
|
|
56
|
-
* a name collision). Array sources are resolved to files (stem = name) and read; map sources are
|
|
57
|
-
* merged verbatim. */
|
|
58
|
-
export async function resolveTemplates(
|
|
59
|
-
host: HostContext,
|
|
60
|
-
root: string,
|
|
61
|
-
sources: ReadonlyArray<TemplateSource | undefined>,
|
|
62
|
-
): Promise<Record<string, string>> {
|
|
63
|
-
// A null-prototype dictionary: template names (file stems or caller-supplied map keys) are
|
|
64
|
-
// untrusted, so a name like `__proto__` must set a plain own property rather than trip the
|
|
65
|
-
// magic prototype setter that a normal `{}` inherits from `Object.prototype`.
|
|
66
|
-
const map: Record<string, string> = Object.create(null);
|
|
67
|
-
for (const source of sources) {
|
|
68
|
-
if (!source) continue;
|
|
69
|
-
if (Array.isArray(source)) {
|
|
70
|
-
const seen = new Set<string>();
|
|
71
|
-
for (const entry of source) {
|
|
72
|
-
for (const file of await filesFor(host, root, entry)) {
|
|
73
|
-
if (seen.has(file)) continue;
|
|
74
|
-
seen.add(file);
|
|
75
|
-
const name = stem(file);
|
|
76
|
-
if (!VALID_NAME.test(name)) {
|
|
77
|
-
// Loud failure for a misnamed template file rather than a silently unreferenceable entry.
|
|
78
|
-
host.log("warn", "template: skipped unsupported name", { file, name });
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
map[name] = await host.readTextFile(file);
|
|
83
|
-
} catch (err) {
|
|
84
|
-
// A directory path that slipped through the file fallback, or an unreadable file.
|
|
85
|
-
host.log("warn", "template: skipped unreadable entry", { file, error: String(err) });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
Object.assign(map, source);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return map;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// `{{ name }}` — a double-brace placeholder around a template name. Double braces avoid clashing
|
|
97
|
-
// with FEEL's single-brace context syntax used elsewhere in BPMN.
|
|
98
|
-
const PLACEHOLDER = /\{\{\s*([\w.-]+)\s*\}\}/g;
|
|
99
|
-
|
|
100
|
-
/** Escape template content for an XML attribute value (e.g. a `zeebe:header` `value="…"`). Beyond
|
|
101
|
-
* the markup-significant characters, newlines and tabs MUST become character references:
|
|
102
|
-
* XML attribute-value normalization collapses literal newlines/tabs to spaces, which would
|
|
103
|
-
* destroy a multi-line prompt. Character references are exempt from that normalization, so
|
|
104
|
-
* ` ` round-trips as a newline. This escaping is also valid for XML element text, so it is
|
|
105
|
-
* the safe default for any `.bpmn`/`.dmn` placeholder position. */
|
|
106
|
-
function xmlAttrEscape(s: string): string {
|
|
107
|
-
return s
|
|
108
|
-
.replace(/&/g, "&")
|
|
109
|
-
.replace(/</g, "<")
|
|
110
|
-
.replace(/>/g, ">")
|
|
111
|
-
.replace(/"/g, """)
|
|
112
|
-
.replace(/'/g, "'")
|
|
113
|
-
.replace(/\r/g, " ")
|
|
114
|
-
.replace(/\n/g, " ")
|
|
115
|
-
.replace(/\t/g, "	");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Escape template content for embedding inside a JSON string literal (a `.form` value). */
|
|
119
|
-
function jsonStringEscape(s: string): string {
|
|
120
|
-
// Stringify to a valid JSON string, then drop the surrounding quotes → an inner-string fragment.
|
|
121
|
-
const quoted = JSON.stringify(s);
|
|
122
|
-
return quoted.slice(1, quoted.length - 1);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function escaperFor(contentType: string): (s: string) => string {
|
|
126
|
-
if (contentType === "application/json") return jsonStringEscape;
|
|
127
|
-
return xmlAttrEscape; // text/xml (.bpmn/.dmn) and any other markup
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export interface TemplateApplication {
|
|
131
|
-
content: string;
|
|
132
|
-
/** Distinct placeholder names with no matching template (left in place, surfaced by the caller). */
|
|
133
|
-
unresolved: string[];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Replace every `{{ name }}` placeholder in `content` with the named template's content, escaped
|
|
137
|
-
* for `contentType`. Single-pass and non-recursive: a template that itself contains `{{…}}` is
|
|
138
|
-
* not re-expanded. Unknown placeholders are left verbatim (so the miss is visible, not silently
|
|
139
|
-
* dropped) and reported in `unresolved`. */
|
|
140
|
-
export function applyTemplates(
|
|
141
|
-
content: string,
|
|
142
|
-
contentType: string,
|
|
143
|
-
templates: Record<string, string>,
|
|
144
|
-
): TemplateApplication {
|
|
145
|
-
const escape = escaperFor(contentType);
|
|
146
|
-
const unresolved: string[] = [];
|
|
147
|
-
const out = content.replace(PLACEHOLDER, (whole: string, name: string) => {
|
|
148
|
-
if (Object.prototype.hasOwnProperty.call(templates, name)) return escape(templates[name]);
|
|
149
|
-
if (!unresolved.includes(name)) unresolved.push(name);
|
|
150
|
-
return whole;
|
|
151
|
-
});
|
|
152
|
-
return { content: out, unresolved };
|
|
153
|
-
}
|