@nanobpm/urban 0.48.0 → 0.49.1

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.
Files changed (73) hide show
  1. package/README.md +80 -21
  2. package/dist/runtime/adapters/deno.js +22 -4
  3. package/dist/runtime/adapters/node.js +34 -4
  4. package/dist/runtime/core/context.d.ts +0 -4
  5. package/dist/runtime/core/glob.d.ts +9 -0
  6. package/dist/runtime/core/glob.js +21 -0
  7. package/dist/runtime/core/host.d.ts +82 -8
  8. package/dist/runtime/core/host.js +13 -0
  9. package/dist/runtime/core/manifest.d.ts +89 -2
  10. package/dist/runtime/core/manifest.js +72 -0
  11. package/dist/runtime/core/modules/deploy.d.ts +9 -3
  12. package/dist/runtime/core/modules/deploy.js +69 -37
  13. package/dist/runtime/core/modules/instance-tracking.d.ts +1 -1
  14. package/dist/runtime/core/modules/instance-tracking.js +20 -5
  15. package/dist/runtime/core/modules/pages.js +34 -2
  16. package/dist/runtime/core/modules/surfaces.js +200 -19
  17. package/dist/runtime/core/router.d.ts +7 -0
  18. package/dist/runtime/core/router.js +9 -0
  19. package/dist/runtime/core/runtime.d.ts +0 -5
  20. package/dist/runtime/core/runtime.js +27 -4
  21. package/dist/runtime/core/validate.js +65 -1
  22. package/dist/runtime/engine/nanosdk.d.ts +10 -6
  23. package/dist/runtime/engine/nanosdk.js +72 -2
  24. package/dist/runtime/index.d.ts +3 -5
  25. package/dist/runtime/index.js +3 -4
  26. package/dist/runtime/run.js +0 -1
  27. package/dist/toolkit/fsio.js +9 -0
  28. package/dist/toolkit/gen.d.ts +26 -1
  29. package/dist/toolkit/gen.js +54 -6
  30. package/dist/toolkit/models.d.ts +6 -4
  31. package/dist/toolkit/models.js +7 -4
  32. package/package.json +1 -1
  33. package/src/runtime/adapters/deno.ts +26 -6
  34. package/src/runtime/adapters/node.test.ts +67 -0
  35. package/src/runtime/adapters/node.ts +37 -4
  36. package/src/runtime/core/context.ts +0 -4
  37. package/src/runtime/core/glob.ts +24 -0
  38. package/src/runtime/core/host.ts +86 -4
  39. package/src/runtime/core/manifest.test.ts +61 -1
  40. package/src/runtime/core/manifest.ts +136 -2
  41. package/src/runtime/core/modules/actions.test.ts +1 -0
  42. package/src/runtime/core/modules/api.test.ts +1 -0
  43. package/src/runtime/core/modules/cancel.test.ts +1 -0
  44. package/src/runtime/core/modules/connectors.test.ts +1 -0
  45. package/src/runtime/core/modules/datasource.migrations.test.ts +1 -0
  46. package/src/runtime/core/modules/deploy.test.ts +97 -161
  47. package/src/runtime/core/modules/deploy.ts +69 -37
  48. package/src/runtime/core/modules/instance-tracking.test.ts +90 -0
  49. package/src/runtime/core/modules/instance-tracking.ts +19 -6
  50. package/src/runtime/core/modules/pages.test.ts +43 -0
  51. package/src/runtime/core/modules/pages.ts +34 -2
  52. package/src/runtime/core/modules/surfaces.test.ts +214 -5
  53. package/src/runtime/core/modules/surfaces.ts +199 -19
  54. package/src/runtime/core/modules/triggers.test.ts +1 -0
  55. package/src/runtime/core/modules/workers.test.ts +1 -0
  56. package/src/runtime/core/router.test.ts +10 -1
  57. package/src/runtime/core/router.ts +10 -0
  58. package/src/runtime/core/runtime.test.ts +67 -1
  59. package/src/runtime/core/runtime.ts +28 -9
  60. package/src/runtime/core/validate.test.ts +145 -0
  61. package/src/runtime/core/validate.ts +64 -1
  62. package/src/runtime/engine/nanosdk.test.ts +130 -1
  63. package/src/runtime/engine/nanosdk.ts +80 -3
  64. package/src/runtime/index.ts +11 -4
  65. package/src/runtime/run.ts +0 -1
  66. package/src/toolkit/fsio.ts +8 -0
  67. package/src/toolkit/gen.test.ts +46 -10
  68. package/src/toolkit/gen.ts +59 -6
  69. package/src/toolkit/models.test.ts +5 -5
  70. package/src/toolkit/models.ts +8 -5
  71. package/dist/runtime/core/modules/templates.d.ts +0 -18
  72. package/dist/runtime/core/modules/templates.js +0 -133
  73. 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
- * `&#10;` 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, "&amp;")
109
- .replace(/</g, "&lt;")
110
- .replace(/>/g, "&gt;")
111
- .replace(/"/g, "&quot;")
112
- .replace(/'/g, "&apos;")
113
- .replace(/\r/g, "&#13;")
114
- .replace(/\n/g, "&#10;")
115
- .replace(/\t/g, "&#9;");
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
- }