@telorun/templating 0.6.0 → 0.8.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 +9 -17
- package/dist/cel/catalog.d.ts +4 -2
- package/dist/cel/catalog.d.ts.map +1 -1
- package/dist/cel/catalog.js +2 -4
- package/dist/cel/environment.d.ts +7 -0
- package/dist/cel/environment.d.ts.map +1 -1
- package/dist/cel/environment.js +40 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/manifest-schemas.d.ts +105 -37
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +140 -19
- package/package.json +3 -3
- package/src/cel/catalog.ts +6 -6
- package/src/cel/environment.ts +38 -3
- package/src/index.ts +1 -0
- package/src/manifest-schemas.ts +144 -19
package/README.md
CHANGED
|
@@ -55,11 +55,11 @@ metadata:
|
|
|
55
55
|
A complete feedback collection REST API — no code, pure YAML.
|
|
56
56
|
Persists entries to SQLite and serves them over HTTP.
|
|
57
57
|
imports:
|
|
58
|
-
Http: std/http-server@0.
|
|
59
|
-
Sql: std/sql@0.
|
|
58
|
+
Http: std/http-server@0.9.0
|
|
59
|
+
Sql: std/sql@0.8.0
|
|
60
60
|
targets:
|
|
61
|
-
- Migrations
|
|
62
|
-
- Server
|
|
61
|
+
- !ref Migrations
|
|
62
|
+
- !ref Server
|
|
63
63
|
---
|
|
64
64
|
# SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
|
|
65
65
|
kind: Sql.Connection
|
|
@@ -72,9 +72,7 @@ file: ./tmp/feedback.db
|
|
|
72
72
|
kind: Sql.Migrations
|
|
73
73
|
metadata:
|
|
74
74
|
name: Migrations
|
|
75
|
-
connection:
|
|
76
|
-
kind: Sql.Connection
|
|
77
|
-
name: Db
|
|
75
|
+
connection: !ref Db
|
|
78
76
|
---
|
|
79
77
|
kind: Sql.Migration
|
|
80
78
|
metadata:
|
|
@@ -101,7 +99,7 @@ openapi:
|
|
|
101
99
|
version: 1.0.0
|
|
102
100
|
mounts:
|
|
103
101
|
- path: /v1
|
|
104
|
-
|
|
102
|
+
mount: !ref FeedbackRoutes
|
|
105
103
|
---
|
|
106
104
|
kind: Http.Api
|
|
107
105
|
metadata:
|
|
@@ -123,9 +121,7 @@ routes:
|
|
|
123
121
|
required: [ text ]
|
|
124
122
|
handler:
|
|
125
123
|
kind: Sql.Exec
|
|
126
|
-
connection:
|
|
127
|
-
kind: Sql.Connection
|
|
128
|
-
name: Db
|
|
124
|
+
connection: !ref Db
|
|
129
125
|
inputs:
|
|
130
126
|
sql: "INSERT INTO feedback (text, source, score) VALUES (?, ?, ?)"
|
|
131
127
|
bindings:
|
|
@@ -146,9 +142,7 @@ routes:
|
|
|
146
142
|
method: GET
|
|
147
143
|
handler:
|
|
148
144
|
kind: Sql.Select
|
|
149
|
-
connection:
|
|
150
|
-
kind: Sql.Connection
|
|
151
|
-
name: Db
|
|
145
|
+
connection: !ref Db
|
|
152
146
|
from: feedback
|
|
153
147
|
columns: [ id, text, source, score, created_at ]
|
|
154
148
|
orderBy:
|
|
@@ -172,9 +166,7 @@ routes:
|
|
|
172
166
|
required: [ id ]
|
|
173
167
|
handler:
|
|
174
168
|
kind: Sql.Select
|
|
175
|
-
connection:
|
|
176
|
-
kind: Sql.Connection
|
|
177
|
-
name: Db
|
|
169
|
+
connection: !ref Db
|
|
178
170
|
from: feedback
|
|
179
171
|
columns: [ id, text, source, score, created_at ]
|
|
180
172
|
where:
|
package/dist/cel/catalog.d.ts
CHANGED
|
@@ -24,8 +24,10 @@ export interface CelFunctionDoc {
|
|
|
24
24
|
* optional args even though cel-js itself has no optional syntax. */
|
|
25
25
|
readonly signature: string;
|
|
26
26
|
/** Actual cel-js signatures to register — one per arity for an overloaded
|
|
27
|
-
* function.
|
|
28
|
-
*
|
|
27
|
+
* function. When omitted, `deriveSignatures(signature)` is used: if the
|
|
28
|
+
* signature contains `type?`-marked optional params (e.g. `fn(string?): T`),
|
|
29
|
+
* it auto-expands to one registration per arity. Set `register` explicitly
|
|
30
|
+
* only when the auto-derivation is insufficient. */
|
|
29
31
|
readonly register?: readonly string[];
|
|
30
32
|
readonly category: CelFunctionCategory;
|
|
31
33
|
readonly summary: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/cel/catalog.ts"],"names":[],"mappings":"AAEA;;;0DAG0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClC;AAED,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,YAAY,GACZ,MAAM,GACN,UAAU,GACV,SAAS,GACT,MAAM,CAAC;AAEX;;;qBAGqB;AACrB,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;0EACsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/cel/catalog.ts"],"names":[],"mappings":"AAEA;;;0DAG0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClC;AAED,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,YAAY,GACZ,MAAM,GACN,UAAU,GACV,SAAS,GACT,MAAM,CAAC;AAEX;;;qBAGqB;AACrB,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;0EACsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;yDAIqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;wBACoB;IACpB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;2EACuE;IACvE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;CACjE;AAED,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAyE5D,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,EAoZlD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,eAAe,EAAE,CAEtD"}
|
package/dist/cel/catalog.js
CHANGED
|
@@ -359,8 +359,7 @@ export const CEL_FUNCTIONS = [
|
|
|
359
359
|
// (default "UTC"); epoch values are absolute and take none.
|
|
360
360
|
{
|
|
361
361
|
name: "nowIso",
|
|
362
|
-
signature: "nowIso(
|
|
363
|
-
register: ["nowIso(): string", "nowIso(string): string"],
|
|
362
|
+
signature: "nowIso(string?): string",
|
|
364
363
|
category: "time",
|
|
365
364
|
summary: "Current time as ISO-8601; UTC by default, or in the given IANA timezone.",
|
|
366
365
|
deterministic: false,
|
|
@@ -369,8 +368,7 @@ export const CEL_FUNCTIONS = [
|
|
|
369
368
|
},
|
|
370
369
|
{
|
|
371
370
|
name: "today",
|
|
372
|
-
signature: "today(
|
|
373
|
-
register: ["today(): string", "today(string): string"],
|
|
371
|
+
signature: "today(string?): string",
|
|
374
372
|
category: "time",
|
|
375
373
|
summary: "Current calendar date (YYYY-MM-DD); UTC by default, or in the given IANA timezone.",
|
|
376
374
|
deterministic: false,
|
|
@@ -17,5 +17,12 @@ export type { CelHandlers } from "./catalog.js";
|
|
|
17
17
|
* no fields, so terminal access (passing the value through CEL) succeeds but
|
|
18
18
|
* member access raises a CEL error at runtime — matching the analyzer's
|
|
19
19
|
* static check on `x-telo-stream`-marked properties. */
|
|
20
|
+
/** Expand a documented signature that may contain `type?`-marked optional
|
|
21
|
+
* parameters into one cel-js registration signature per arity. For example,
|
|
22
|
+
* `"nowIso(string?): string"` produces `["nowIso(): string",
|
|
23
|
+
* "nowIso(string): string"]`. Required parameters must precede optional ones.
|
|
24
|
+
* Returns `[signature]` unchanged when no `?` is present or the signature
|
|
25
|
+
* cannot be parsed. */
|
|
26
|
+
export declare function deriveSignatures(signature: string): string[];
|
|
20
27
|
export declare function buildCelEnvironment(handlers?: Partial<CelHandlers>): Environment;
|
|
21
28
|
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/cel/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE/D,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoBhD;;;;;;;;;;;;;;;yDAeyD;AACzD,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/cel/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE/D,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoBhD;;;;;;;;;;;;;;;yDAeyD;AACzD;;;;;wBAKwB;AACxB,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAyB5D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAcpF"}
|
package/dist/cel/environment.js
CHANGED
|
@@ -31,14 +31,51 @@ const STUB_HANDLERS = {
|
|
|
31
31
|
* no fields, so terminal access (passing the value through CEL) succeeds but
|
|
32
32
|
* member access raises a CEL error at runtime — matching the analyzer's
|
|
33
33
|
* static check on `x-telo-stream`-marked properties. */
|
|
34
|
+
/** Expand a documented signature that may contain `type?`-marked optional
|
|
35
|
+
* parameters into one cel-js registration signature per arity. For example,
|
|
36
|
+
* `"nowIso(string?): string"` produces `["nowIso(): string",
|
|
37
|
+
* "nowIso(string): string"]`. Required parameters must precede optional ones.
|
|
38
|
+
* Returns `[signature]` unchanged when no `?` is present or the signature
|
|
39
|
+
* cannot be parsed. */
|
|
40
|
+
export function deriveSignatures(signature) {
|
|
41
|
+
const m = signature.match(/^(\w+)\((.*?)\):\s*(.+)$/);
|
|
42
|
+
if (!m)
|
|
43
|
+
return [signature];
|
|
44
|
+
const name = m[1];
|
|
45
|
+
const paramsStr = m[2].trim();
|
|
46
|
+
const returnType = m[3].trim();
|
|
47
|
+
if (!paramsStr.includes("?"))
|
|
48
|
+
return [signature];
|
|
49
|
+
const params = paramsStr.split(",").map((p) => p.trim());
|
|
50
|
+
const required = [];
|
|
51
|
+
const optional = [];
|
|
52
|
+
for (const p of params) {
|
|
53
|
+
if (p.endsWith("?")) {
|
|
54
|
+
optional.push(p.slice(0, -1));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
if (optional.length > 0)
|
|
58
|
+
return [signature];
|
|
59
|
+
required.push(p);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (optional.length === 0)
|
|
63
|
+
return [signature];
|
|
64
|
+
return Array.from({ length: optional.length + 1 }, (_, i) => {
|
|
65
|
+
const allParams = [...required, ...optional.slice(0, i)];
|
|
66
|
+
return `${name}(${allParams.join(", ")}): ${returnType}`;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
34
69
|
export function buildCelEnvironment(handlers = {}) {
|
|
35
70
|
const h = { ...STUB_HANDLERS, ...handlers };
|
|
36
71
|
let env = new Environment({ unlistedVariablesAreDyn: true, enableOptionalTypes: true });
|
|
37
72
|
for (const fn of CEL_FUNCTIONS) {
|
|
38
73
|
const impl = fn.build(h);
|
|
39
|
-
// `register` lists one cel-js signature per arity (overloaded functions)
|
|
40
|
-
//
|
|
41
|
-
|
|
74
|
+
// `register` lists one cel-js signature per arity (overloaded functions).
|
|
75
|
+
// When absent, `deriveSignatures` expands `type?` optional-param notation
|
|
76
|
+
// into one registration per arity — so `nowIso(string?): string` registers
|
|
77
|
+
// both `nowIso(): string` and `nowIso(string): string` automatically.
|
|
78
|
+
for (const sig of fn.register ?? deriveSignatures(fn.signature)) {
|
|
42
79
|
env = env.registerFunction(sig, impl);
|
|
43
80
|
}
|
|
44
81
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export { builtinEngines, createDefaultRegistry, defaultRegistry } from "./builti
|
|
|
12
12
|
export type { AnalyzeEnv, CompileEnv, EngineDiagnostic, TemplatingEngine, } from "./engine.js";
|
|
13
13
|
export { isRefSentinel, isTaggedSentinel, makeTaggedSentinel, type TaggedSentinel } from "./sentinel.js";
|
|
14
14
|
export { buildCustomTags, defaultCustomTags } from "./yaml-tags.js";
|
|
15
|
-
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema, } from "./manifest-schemas.js";
|
|
15
|
+
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema, normalizeRefSlots, } from "./manifest-schemas.js";
|
|
16
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,cAAc,EACd,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvF,YAAY,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,cAAc,EACd,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvF,YAAY,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,4 +11,4 @@ export { TemplatingEngineRegistry } from "./registry.js";
|
|
|
11
11
|
export { builtinEngines, createDefaultRegistry, defaultRegistry } from "./builtins.js";
|
|
12
12
|
export { isRefSentinel, isTaggedSentinel, makeTaggedSentinel } from "./sentinel.js";
|
|
13
13
|
export { buildCustomTags, defaultCustomTags } from "./yaml-tags.js";
|
|
14
|
-
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema, } from "./manifest-schemas.js";
|
|
14
|
+
export { MANIFEST_SCHEMA_URI, ManifestRootSchema, ResourceRefSchema, normalizeRefSlots, } from "./manifest-schemas.js";
|
|
@@ -5,35 +5,87 @@
|
|
|
5
5
|
* layer where a single source of truth can sit. The kernel re-exports
|
|
6
6
|
* these symbols from its own `manifest-schemas` surface for downstream
|
|
7
7
|
* ergonomics. */
|
|
8
|
-
/** Schema fragment for a resource
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* ref slot
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
/** Schema fragment for a resource-reference slot. The only form a manifest
|
|
9
|
+
* author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
|
|
10
|
+
* which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
|
|
11
|
+
* bare resource name. In practice module schemas mark a ref slot with a bare
|
|
12
|
+
* `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
|
|
13
|
+
* `type: object` to reject a stray scalar); the analyzer's reference walker
|
|
14
|
+
* reads `x-telo-ref` to look the name up against that constraint, independent
|
|
15
|
+
* of this fragment. A slot opts into `$ref:
|
|
16
|
+
* "telo://manifest#/$defs/ResourceRef"` only when it wants this exact
|
|
17
|
+
* two-branch shape enforced at the AJV layer too — it is not required, and
|
|
18
|
+
* slots that also accept an inline value (e.g. `inputType` / `outputType`)
|
|
19
|
+
* deliberately do not use it (an inline JSON Schema has no `kind`).
|
|
14
20
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
21
|
+
* Two `anyOf` branches because the value's shape depends on the phase at
|
|
22
|
+
* which it is validated:
|
|
23
|
+
*
|
|
24
|
+
* 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
|
|
25
|
+
* reference can't be resolved in standalone single-file analysis (the
|
|
26
|
+
* imported module isn't loaded). `substituteCelFields` deliberately
|
|
27
|
+
* keeps the sentinel so this branch matches.
|
|
28
|
+
* 2. A resolved reference object — `{kind, name, alias?}` substituted in
|
|
29
|
+
* place of a sentinel (or an inline definition `{kind, ...config}`
|
|
30
|
+
* reached through a local `$ref` that escapes extraction). Both the
|
|
31
|
+
* kernel and the analyzer validate ref slots *after* sentinel
|
|
32
|
+
* resolution, so this is the shape AJV usually sees.
|
|
33
|
+
*
|
|
34
|
+
* The object-form `{kind, name}` reference a user could once type directly
|
|
35
|
+
* is gone: a plain object at a ref slot is only ever an inline definition
|
|
36
|
+
* or the resolver's own substitution, never an author-written reference.
|
|
37
|
+
* That removal is enforced by the analyzer (it rejects an author-written
|
|
38
|
+
* `{kind, name}` before normalization), not by this schema — branch 2
|
|
39
|
+
* cannot distinguish an author's `{kind, name}` from the resolver's. */
|
|
20
40
|
export declare const ResourceRefSchema: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
title: string;
|
|
42
|
+
anyOf: ({
|
|
43
|
+
type: string;
|
|
44
|
+
required: string[];
|
|
45
|
+
properties: {
|
|
46
|
+
__tagged: {
|
|
47
|
+
const: boolean;
|
|
48
|
+
};
|
|
49
|
+
engine: {
|
|
50
|
+
const: string;
|
|
51
|
+
};
|
|
52
|
+
source: {
|
|
53
|
+
type: string;
|
|
54
|
+
minLength: number;
|
|
55
|
+
};
|
|
56
|
+
kind?: undefined;
|
|
29
57
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
additionalProperties: boolean;
|
|
59
|
+
} | {
|
|
60
|
+
type: string;
|
|
61
|
+
required: string[];
|
|
62
|
+
properties: {
|
|
63
|
+
kind: {
|
|
64
|
+
type: string;
|
|
65
|
+
};
|
|
66
|
+
__tagged?: undefined;
|
|
67
|
+
engine?: undefined;
|
|
68
|
+
source?: undefined;
|
|
33
69
|
};
|
|
34
|
-
|
|
35
|
-
|
|
70
|
+
additionalProperties: boolean;
|
|
71
|
+
})[];
|
|
36
72
|
};
|
|
73
|
+
/** Deep-clone `schema`, dropping the stale scalar `type` constraint from every
|
|
74
|
+
* reference-slot node — one carrying an `x-telo-ref` string annotation.
|
|
75
|
+
*
|
|
76
|
+
* A reference slot's value is always a `!ref` sentinel or its resolved
|
|
77
|
+
* `{kind, name, alias?}` object (never a bare string, post-migration). Older
|
|
78
|
+
* published modules still pin `type: "string"` on these slots — the encoding
|
|
79
|
+
* references took when they were written as plain strings — which now rejects
|
|
80
|
+
* the resolved object. Removing only the scalar `type` lets the analyzer and
|
|
81
|
+
* kernel accept references uniformly across module versions during the
|
|
82
|
+
* migration away from `{kind, name}` / string references, without disturbing
|
|
83
|
+
* slots that legitimately accept an inline object (e.g. `inputType` /
|
|
84
|
+
* `outputType`, which take a Telo.Type reference *or* an inline JSON schema).
|
|
85
|
+
* The `x-telo-ref` constraint itself (which kind the reference must satisfy) is
|
|
86
|
+
* checked separately by the analyzer's reference walker, which reads the
|
|
87
|
+
* original schema — not this validation-only copy. */
|
|
88
|
+
export declare function normalizeRefSlots(schema: unknown): unknown;
|
|
37
89
|
/** Stable URI under which the shared manifest root schema is registered
|
|
38
90
|
* with module-side AJV instances. Module YAMLs reach the fragments via
|
|
39
91
|
* `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
|
|
@@ -49,21 +101,37 @@ export declare const ManifestRootSchema: {
|
|
|
49
101
|
$id: string;
|
|
50
102
|
$defs: {
|
|
51
103
|
ResourceRef: {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
title: string;
|
|
105
|
+
anyOf: ({
|
|
106
|
+
type: string;
|
|
107
|
+
required: string[];
|
|
108
|
+
properties: {
|
|
109
|
+
__tagged: {
|
|
110
|
+
const: boolean;
|
|
111
|
+
};
|
|
112
|
+
engine: {
|
|
113
|
+
const: string;
|
|
114
|
+
};
|
|
115
|
+
source: {
|
|
116
|
+
type: string;
|
|
117
|
+
minLength: number;
|
|
118
|
+
};
|
|
119
|
+
kind?: undefined;
|
|
57
120
|
};
|
|
58
|
-
|
|
59
|
-
|
|
121
|
+
additionalProperties: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
type: string;
|
|
124
|
+
required: string[];
|
|
125
|
+
properties: {
|
|
126
|
+
kind: {
|
|
127
|
+
type: string;
|
|
128
|
+
};
|
|
129
|
+
__tagged?: undefined;
|
|
130
|
+
engine?: undefined;
|
|
131
|
+
source?: undefined;
|
|
60
132
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
minLength: number;
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
additionalProperties: boolean;
|
|
133
|
+
additionalProperties: boolean;
|
|
134
|
+
})[];
|
|
67
135
|
};
|
|
68
136
|
};
|
|
69
137
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-schemas.d.ts","sourceRoot":"","sources":["../src/manifest-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;kBAMkB;AAElB
|
|
1
|
+
{"version":3,"file":"manifest-schemas.d.ts","sourceRoot":"","sources":["../src/manifest-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;kBAMkB;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEA+ByE;AACzE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoB7B,CAAC;AAqCF;;;;;;;;;;;;;;uDAcuD;AACvD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CA0C1D;AAED;;;;;+BAK+B;AAC/B,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;8BAG8B;AAC9B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAK9B,CAAC"}
|
package/dist/manifest-schemas.js
CHANGED
|
@@ -5,28 +5,149 @@
|
|
|
5
5
|
* layer where a single source of truth can sit. The kernel re-exports
|
|
6
6
|
* these symbols from its own `manifest-schemas` surface for downstream
|
|
7
7
|
* ergonomics. */
|
|
8
|
-
/** Schema fragment for a resource
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* ref slot
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
/** Schema fragment for a resource-reference slot. The only form a manifest
|
|
9
|
+
* author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
|
|
10
|
+
* which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
|
|
11
|
+
* bare resource name. In practice module schemas mark a ref slot with a bare
|
|
12
|
+
* `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
|
|
13
|
+
* `type: object` to reject a stray scalar); the analyzer's reference walker
|
|
14
|
+
* reads `x-telo-ref` to look the name up against that constraint, independent
|
|
15
|
+
* of this fragment. A slot opts into `$ref:
|
|
16
|
+
* "telo://manifest#/$defs/ResourceRef"` only when it wants this exact
|
|
17
|
+
* two-branch shape enforced at the AJV layer too — it is not required, and
|
|
18
|
+
* slots that also accept an inline value (e.g. `inputType` / `outputType`)
|
|
19
|
+
* deliberately do not use it (an inline JSON Schema has no `kind`).
|
|
14
20
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
21
|
+
* Two `anyOf` branches because the value's shape depends on the phase at
|
|
22
|
+
* which it is validated:
|
|
23
|
+
*
|
|
24
|
+
* 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
|
|
25
|
+
* reference can't be resolved in standalone single-file analysis (the
|
|
26
|
+
* imported module isn't loaded). `substituteCelFields` deliberately
|
|
27
|
+
* keeps the sentinel so this branch matches.
|
|
28
|
+
* 2. A resolved reference object — `{kind, name, alias?}` substituted in
|
|
29
|
+
* place of a sentinel (or an inline definition `{kind, ...config}`
|
|
30
|
+
* reached through a local `$ref` that escapes extraction). Both the
|
|
31
|
+
* kernel and the analyzer validate ref slots *after* sentinel
|
|
32
|
+
* resolution, so this is the shape AJV usually sees.
|
|
33
|
+
*
|
|
34
|
+
* The object-form `{kind, name}` reference a user could once type directly
|
|
35
|
+
* is gone: a plain object at a ref slot is only ever an inline definition
|
|
36
|
+
* or the resolver's own substitution, never an author-written reference.
|
|
37
|
+
* That removal is enforced by the analyzer (it rejects an author-written
|
|
38
|
+
* `{kind, name}` before normalization), not by this schema — branch 2
|
|
39
|
+
* cannot distinguish an author's `{kind, name}` from the resolver's. */
|
|
20
40
|
export const ResourceRefSchema = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
title: "Resource reference",
|
|
42
|
+
anyOf: [
|
|
43
|
+
{
|
|
44
|
+
type: "object",
|
|
45
|
+
required: ["__tagged", "engine", "source"],
|
|
46
|
+
properties: {
|
|
47
|
+
__tagged: { const: true },
|
|
48
|
+
engine: { const: "ref" },
|
|
49
|
+
source: { type: "string", minLength: 1 },
|
|
50
|
+
},
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "object",
|
|
55
|
+
required: ["kind"],
|
|
56
|
+
properties: { kind: { type: "string" } },
|
|
57
|
+
additionalProperties: true,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
29
60
|
};
|
|
61
|
+
const REF_ANNOTATION = "x-telo-ref";
|
|
62
|
+
// The legacy base types a reference slot used to pin when references were
|
|
63
|
+
// written as plain strings. Post-migration a reference resolves to an object
|
|
64
|
+
// (the `{kind, name, alias?}` shape, or an unresolved `!ref` sentinel), so a
|
|
65
|
+
// scalar `type` on a ref slot is a stale constraint that would reject the
|
|
66
|
+
// resolved value. Object / array `type`s are left alone — they already admit
|
|
67
|
+
// the reference object (and any inline value a slot like `inputType` accepts).
|
|
68
|
+
const LEGACY_REF_SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
69
|
+
// JSON Schema keywords whose values are themselves subschemas. Split by shape so
|
|
70
|
+
// the ref-slot normalizer recurses only into schema positions — never into
|
|
71
|
+
// data-bearing keywords (`default`, `const`, `enum`, `examples`), where a stray
|
|
72
|
+
// `x-telo-ref` key would be data, not an annotation.
|
|
73
|
+
const SUBSCHEMA_SINGLE = [
|
|
74
|
+
"additionalProperties",
|
|
75
|
+
"additionalItems",
|
|
76
|
+
"contains",
|
|
77
|
+
"not",
|
|
78
|
+
"if",
|
|
79
|
+
"then",
|
|
80
|
+
"else",
|
|
81
|
+
"propertyNames",
|
|
82
|
+
"unevaluatedItems",
|
|
83
|
+
"unevaluatedProperties",
|
|
84
|
+
];
|
|
85
|
+
const SUBSCHEMA_LIST = ["allOf", "anyOf", "oneOf", "prefixItems"];
|
|
86
|
+
const SUBSCHEMA_MAP = [
|
|
87
|
+
"properties",
|
|
88
|
+
"patternProperties",
|
|
89
|
+
"$defs",
|
|
90
|
+
"definitions",
|
|
91
|
+
"dependentSchemas",
|
|
92
|
+
];
|
|
93
|
+
/** Deep-clone `schema`, dropping the stale scalar `type` constraint from every
|
|
94
|
+
* reference-slot node — one carrying an `x-telo-ref` string annotation.
|
|
95
|
+
*
|
|
96
|
+
* A reference slot's value is always a `!ref` sentinel or its resolved
|
|
97
|
+
* `{kind, name, alias?}` object (never a bare string, post-migration). Older
|
|
98
|
+
* published modules still pin `type: "string"` on these slots — the encoding
|
|
99
|
+
* references took when they were written as plain strings — which now rejects
|
|
100
|
+
* the resolved object. Removing only the scalar `type` lets the analyzer and
|
|
101
|
+
* kernel accept references uniformly across module versions during the
|
|
102
|
+
* migration away from `{kind, name}` / string references, without disturbing
|
|
103
|
+
* slots that legitimately accept an inline object (e.g. `inputType` /
|
|
104
|
+
* `outputType`, which take a Telo.Type reference *or* an inline JSON schema).
|
|
105
|
+
* The `x-telo-ref` constraint itself (which kind the reference must satisfy) is
|
|
106
|
+
* checked separately by the analyzer's reference walker, which reads the
|
|
107
|
+
* original schema — not this validation-only copy. */
|
|
108
|
+
export function normalizeRefSlots(schema) {
|
|
109
|
+
if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
|
|
110
|
+
return schema;
|
|
111
|
+
}
|
|
112
|
+
const node = schema;
|
|
113
|
+
const out = { ...node };
|
|
114
|
+
// Reference slot with a stale scalar `type` (legacy string-ref encoding):
|
|
115
|
+
// drop the constraint so the resolved reference object / sentinel validates.
|
|
116
|
+
if (typeof node[REF_ANNOTATION] === "string" &&
|
|
117
|
+
typeof node.type === "string" &&
|
|
118
|
+
LEGACY_REF_SCALAR_TYPES.has(node.type)) {
|
|
119
|
+
delete out.type;
|
|
120
|
+
}
|
|
121
|
+
for (const key of SUBSCHEMA_SINGLE) {
|
|
122
|
+
const value = node[key];
|
|
123
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
124
|
+
out[key] = normalizeRefSlots(value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const key of SUBSCHEMA_LIST) {
|
|
128
|
+
const value = node[key];
|
|
129
|
+
if (Array.isArray(value))
|
|
130
|
+
out[key] = value.map(normalizeRefSlots);
|
|
131
|
+
}
|
|
132
|
+
// `items` is either a single subschema or a tuple of subschemas.
|
|
133
|
+
if (Array.isArray(node.items)) {
|
|
134
|
+
out.items = node.items.map(normalizeRefSlots);
|
|
135
|
+
}
|
|
136
|
+
else if (node.items && typeof node.items === "object") {
|
|
137
|
+
out.items = normalizeRefSlots(node.items);
|
|
138
|
+
}
|
|
139
|
+
for (const key of SUBSCHEMA_MAP) {
|
|
140
|
+
const value = node[key];
|
|
141
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
142
|
+
const mapped = {};
|
|
143
|
+
for (const [name, sub] of Object.entries(value)) {
|
|
144
|
+
mapped[name] = normalizeRefSlots(sub);
|
|
145
|
+
}
|
|
146
|
+
out[key] = mapped;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
30
151
|
/** Stable URI under which the shared manifest root schema is registered
|
|
31
152
|
* with module-side AJV instances. Module YAMLs reach the fragments via
|
|
32
153
|
* `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/templating",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Telo Templating - Engine registry and shared CEL core for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"src/**"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@marcbachmann/cel-js": "^7.
|
|
38
|
+
"@marcbachmann/cel-js": "^7.6.1",
|
|
39
39
|
"uuid": "^10.0.0",
|
|
40
40
|
"yaml": "^2.8.3"
|
|
41
41
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@types/uuid": "^10.0.0",
|
|
45
45
|
"typescript": "^5.0.0",
|
|
46
46
|
"vitest": "^2.1.8",
|
|
47
|
-
"@telorun/sdk": "0.
|
|
47
|
+
"@telorun/sdk": "0.23.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@telorun/sdk": "*"
|
package/src/cel/catalog.ts
CHANGED
|
@@ -38,8 +38,10 @@ export interface CelFunctionDoc {
|
|
|
38
38
|
* optional args even though cel-js itself has no optional syntax. */
|
|
39
39
|
readonly signature: string;
|
|
40
40
|
/** Actual cel-js signatures to register — one per arity for an overloaded
|
|
41
|
-
* function.
|
|
42
|
-
*
|
|
41
|
+
* function. When omitted, `deriveSignatures(signature)` is used: if the
|
|
42
|
+
* signature contains `type?`-marked optional params (e.g. `fn(string?): T`),
|
|
43
|
+
* it auto-expands to one registration per arity. Set `register` explicitly
|
|
44
|
+
* only when the auto-derivation is insufficient. */
|
|
43
45
|
readonly register?: readonly string[];
|
|
44
46
|
readonly category: CelFunctionCategory;
|
|
45
47
|
readonly summary: string;
|
|
@@ -423,8 +425,7 @@ export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
|
|
|
423
425
|
// (default "UTC"); epoch values are absolute and take none.
|
|
424
426
|
{
|
|
425
427
|
name: "nowIso",
|
|
426
|
-
signature: "nowIso(
|
|
427
|
-
register: ["nowIso(): string", "nowIso(string): string"],
|
|
428
|
+
signature: "nowIso(string?): string",
|
|
428
429
|
category: "time",
|
|
429
430
|
summary: "Current time as ISO-8601; UTC by default, or in the given IANA timezone.",
|
|
430
431
|
deterministic: false,
|
|
@@ -433,8 +434,7 @@ export const CEL_FUNCTIONS: readonly CelFunctionDoc[] = [
|
|
|
433
434
|
},
|
|
434
435
|
{
|
|
435
436
|
name: "today",
|
|
436
|
-
signature: "today(
|
|
437
|
-
register: ["today(): string", "today(string): string"],
|
|
437
|
+
signature: "today(string?): string",
|
|
438
438
|
category: "time",
|
|
439
439
|
summary: "Current calendar date (YYYY-MM-DD); UTC by default, or in the given IANA timezone.",
|
|
440
440
|
deterministic: false,
|
package/src/cel/environment.ts
CHANGED
|
@@ -38,14 +38,49 @@ const STUB_HANDLERS: CelHandlers = {
|
|
|
38
38
|
* no fields, so terminal access (passing the value through CEL) succeeds but
|
|
39
39
|
* member access raises a CEL error at runtime — matching the analyzer's
|
|
40
40
|
* static check on `x-telo-stream`-marked properties. */
|
|
41
|
+
/** Expand a documented signature that may contain `type?`-marked optional
|
|
42
|
+
* parameters into one cel-js registration signature per arity. For example,
|
|
43
|
+
* `"nowIso(string?): string"` produces `["nowIso(): string",
|
|
44
|
+
* "nowIso(string): string"]`. Required parameters must precede optional ones.
|
|
45
|
+
* Returns `[signature]` unchanged when no `?` is present or the signature
|
|
46
|
+
* cannot be parsed. */
|
|
47
|
+
export function deriveSignatures(signature: string): string[] {
|
|
48
|
+
const m = signature.match(/^(\w+)\((.*?)\):\s*(.+)$/);
|
|
49
|
+
if (!m) return [signature];
|
|
50
|
+
const name = m[1]!;
|
|
51
|
+
const paramsStr = m[2]!.trim();
|
|
52
|
+
const returnType = m[3]!.trim();
|
|
53
|
+
if (!paramsStr.includes("?")) return [signature];
|
|
54
|
+
|
|
55
|
+
const params = paramsStr.split(",").map((p) => p.trim());
|
|
56
|
+
const required: string[] = [];
|
|
57
|
+
const optional: string[] = [];
|
|
58
|
+
for (const p of params) {
|
|
59
|
+
if (p.endsWith("?")) {
|
|
60
|
+
optional.push(p.slice(0, -1));
|
|
61
|
+
} else {
|
|
62
|
+
if (optional.length > 0) return [signature];
|
|
63
|
+
required.push(p);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (optional.length === 0) return [signature];
|
|
67
|
+
|
|
68
|
+
return Array.from({ length: optional.length + 1 }, (_, i) => {
|
|
69
|
+
const allParams = [...required, ...optional.slice(0, i)];
|
|
70
|
+
return `${name}(${allParams.join(", ")}): ${returnType}`;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
41
74
|
export function buildCelEnvironment(handlers: Partial<CelHandlers> = {}): Environment {
|
|
42
75
|
const h: CelHandlers = { ...STUB_HANDLERS, ...handlers };
|
|
43
76
|
let env = new Environment({ unlistedVariablesAreDyn: true, enableOptionalTypes: true });
|
|
44
77
|
for (const fn of CEL_FUNCTIONS) {
|
|
45
78
|
const impl = fn.build(h);
|
|
46
|
-
// `register` lists one cel-js signature per arity (overloaded functions)
|
|
47
|
-
//
|
|
48
|
-
|
|
79
|
+
// `register` lists one cel-js signature per arity (overloaded functions).
|
|
80
|
+
// When absent, `deriveSignatures` expands `type?` optional-param notation
|
|
81
|
+
// into one registration per arity — so `nowIso(string?): string` registers
|
|
82
|
+
// both `nowIso(): string` and `nowIso(string): string` automatically.
|
|
83
|
+
for (const sig of fn.register ?? deriveSignatures(fn.signature)) {
|
|
49
84
|
env = env.registerFunction(sig, impl);
|
|
50
85
|
}
|
|
51
86
|
}
|
package/src/index.ts
CHANGED
package/src/manifest-schemas.ts
CHANGED
|
@@ -6,29 +6,154 @@
|
|
|
6
6
|
* these symbols from its own `manifest-schemas` surface for downstream
|
|
7
7
|
* ergonomics. */
|
|
8
8
|
|
|
9
|
-
/** Schema fragment for a resource
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* ref slot
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
/** Schema fragment for a resource-reference slot. The only form a manifest
|
|
10
|
+
* author writes is the `!ref <name>` (or `!ref <Alias>.<name>`) YAML tag,
|
|
11
|
+
* which parses to a `TaggedSentinel` (engine "ref") whose `source` is the
|
|
12
|
+
* bare resource name. In practice module schemas mark a ref slot with a bare
|
|
13
|
+
* `x-telo-ref` annotation (plus, where the slot only ever holds a reference,
|
|
14
|
+
* `type: object` to reject a stray scalar); the analyzer's reference walker
|
|
15
|
+
* reads `x-telo-ref` to look the name up against that constraint, independent
|
|
16
|
+
* of this fragment. A slot opts into `$ref:
|
|
17
|
+
* "telo://manifest#/$defs/ResourceRef"` only when it wants this exact
|
|
18
|
+
* two-branch shape enforced at the AJV layer too — it is not required, and
|
|
19
|
+
* slots that also accept an inline value (e.g. `inputType` / `outputType`)
|
|
20
|
+
* deliberately do not use it (an inline JSON Schema has no `kind`).
|
|
15
21
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
22
|
+
* Two `anyOf` branches because the value's shape depends on the phase at
|
|
23
|
+
* which it is validated:
|
|
24
|
+
*
|
|
25
|
+
* 1. The raw `!ref` sentinel — what survives to AJV when a cross-module
|
|
26
|
+
* reference can't be resolved in standalone single-file analysis (the
|
|
27
|
+
* imported module isn't loaded). `substituteCelFields` deliberately
|
|
28
|
+
* keeps the sentinel so this branch matches.
|
|
29
|
+
* 2. A resolved reference object — `{kind, name, alias?}` substituted in
|
|
30
|
+
* place of a sentinel (or an inline definition `{kind, ...config}`
|
|
31
|
+
* reached through a local `$ref` that escapes extraction). Both the
|
|
32
|
+
* kernel and the analyzer validate ref slots *after* sentinel
|
|
33
|
+
* resolution, so this is the shape AJV usually sees.
|
|
34
|
+
*
|
|
35
|
+
* The object-form `{kind, name}` reference a user could once type directly
|
|
36
|
+
* is gone: a plain object at a ref slot is only ever an inline definition
|
|
37
|
+
* or the resolver's own substitution, never an author-written reference.
|
|
38
|
+
* That removal is enforced by the analyzer (it rejects an author-written
|
|
39
|
+
* `{kind, name}` before normalization), not by this schema — branch 2
|
|
40
|
+
* cannot distinguish an author's `{kind, name}` from the resolver's. */
|
|
21
41
|
export const ResourceRefSchema = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
title: "Resource reference",
|
|
43
|
+
anyOf: [
|
|
44
|
+
{
|
|
45
|
+
type: "object",
|
|
46
|
+
required: ["__tagged", "engine", "source"],
|
|
47
|
+
properties: {
|
|
48
|
+
__tagged: { const: true },
|
|
49
|
+
engine: { const: "ref" },
|
|
50
|
+
source: { type: "string", minLength: 1 },
|
|
51
|
+
},
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: "object",
|
|
56
|
+
required: ["kind"],
|
|
57
|
+
properties: { kind: { type: "string" } },
|
|
58
|
+
additionalProperties: true,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
30
61
|
};
|
|
31
62
|
|
|
63
|
+
const REF_ANNOTATION = "x-telo-ref";
|
|
64
|
+
|
|
65
|
+
// The legacy base types a reference slot used to pin when references were
|
|
66
|
+
// written as plain strings. Post-migration a reference resolves to an object
|
|
67
|
+
// (the `{kind, name, alias?}` shape, or an unresolved `!ref` sentinel), so a
|
|
68
|
+
// scalar `type` on a ref slot is a stale constraint that would reject the
|
|
69
|
+
// resolved value. Object / array `type`s are left alone — they already admit
|
|
70
|
+
// the reference object (and any inline value a slot like `inputType` accepts).
|
|
71
|
+
const LEGACY_REF_SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
72
|
+
|
|
73
|
+
// JSON Schema keywords whose values are themselves subschemas. Split by shape so
|
|
74
|
+
// the ref-slot normalizer recurses only into schema positions — never into
|
|
75
|
+
// data-bearing keywords (`default`, `const`, `enum`, `examples`), where a stray
|
|
76
|
+
// `x-telo-ref` key would be data, not an annotation.
|
|
77
|
+
const SUBSCHEMA_SINGLE = [
|
|
78
|
+
"additionalProperties",
|
|
79
|
+
"additionalItems",
|
|
80
|
+
"contains",
|
|
81
|
+
"not",
|
|
82
|
+
"if",
|
|
83
|
+
"then",
|
|
84
|
+
"else",
|
|
85
|
+
"propertyNames",
|
|
86
|
+
"unevaluatedItems",
|
|
87
|
+
"unevaluatedProperties",
|
|
88
|
+
] as const;
|
|
89
|
+
const SUBSCHEMA_LIST = ["allOf", "anyOf", "oneOf", "prefixItems"] as const;
|
|
90
|
+
const SUBSCHEMA_MAP = [
|
|
91
|
+
"properties",
|
|
92
|
+
"patternProperties",
|
|
93
|
+
"$defs",
|
|
94
|
+
"definitions",
|
|
95
|
+
"dependentSchemas",
|
|
96
|
+
] as const;
|
|
97
|
+
|
|
98
|
+
/** Deep-clone `schema`, dropping the stale scalar `type` constraint from every
|
|
99
|
+
* reference-slot node — one carrying an `x-telo-ref` string annotation.
|
|
100
|
+
*
|
|
101
|
+
* A reference slot's value is always a `!ref` sentinel or its resolved
|
|
102
|
+
* `{kind, name, alias?}` object (never a bare string, post-migration). Older
|
|
103
|
+
* published modules still pin `type: "string"` on these slots — the encoding
|
|
104
|
+
* references took when they were written as plain strings — which now rejects
|
|
105
|
+
* the resolved object. Removing only the scalar `type` lets the analyzer and
|
|
106
|
+
* kernel accept references uniformly across module versions during the
|
|
107
|
+
* migration away from `{kind, name}` / string references, without disturbing
|
|
108
|
+
* slots that legitimately accept an inline object (e.g. `inputType` /
|
|
109
|
+
* `outputType`, which take a Telo.Type reference *or* an inline JSON schema).
|
|
110
|
+
* The `x-telo-ref` constraint itself (which kind the reference must satisfy) is
|
|
111
|
+
* checked separately by the analyzer's reference walker, which reads the
|
|
112
|
+
* original schema — not this validation-only copy. */
|
|
113
|
+
export function normalizeRefSlots(schema: unknown): unknown {
|
|
114
|
+
if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
|
|
115
|
+
return schema;
|
|
116
|
+
}
|
|
117
|
+
const node = schema as Record<string, unknown>;
|
|
118
|
+
const out: Record<string, unknown> = { ...node };
|
|
119
|
+
// Reference slot with a stale scalar `type` (legacy string-ref encoding):
|
|
120
|
+
// drop the constraint so the resolved reference object / sentinel validates.
|
|
121
|
+
if (
|
|
122
|
+
typeof node[REF_ANNOTATION] === "string" &&
|
|
123
|
+
typeof node.type === "string" &&
|
|
124
|
+
LEGACY_REF_SCALAR_TYPES.has(node.type)
|
|
125
|
+
) {
|
|
126
|
+
delete out.type;
|
|
127
|
+
}
|
|
128
|
+
for (const key of SUBSCHEMA_SINGLE) {
|
|
129
|
+
const value = node[key];
|
|
130
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
131
|
+
out[key] = normalizeRefSlots(value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const key of SUBSCHEMA_LIST) {
|
|
135
|
+
const value = node[key];
|
|
136
|
+
if (Array.isArray(value)) out[key] = value.map(normalizeRefSlots);
|
|
137
|
+
}
|
|
138
|
+
// `items` is either a single subschema or a tuple of subschemas.
|
|
139
|
+
if (Array.isArray(node.items)) {
|
|
140
|
+
out.items = node.items.map(normalizeRefSlots);
|
|
141
|
+
} else if (node.items && typeof node.items === "object") {
|
|
142
|
+
out.items = normalizeRefSlots(node.items);
|
|
143
|
+
}
|
|
144
|
+
for (const key of SUBSCHEMA_MAP) {
|
|
145
|
+
const value = node[key];
|
|
146
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
147
|
+
const mapped: Record<string, unknown> = {};
|
|
148
|
+
for (const [name, sub] of Object.entries(value as Record<string, unknown>)) {
|
|
149
|
+
mapped[name] = normalizeRefSlots(sub);
|
|
150
|
+
}
|
|
151
|
+
out[key] = mapped;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
32
157
|
/** Stable URI under which the shared manifest root schema is registered
|
|
33
158
|
* with module-side AJV instances. Module YAMLs reach the fragments via
|
|
34
159
|
* `$ref: "telo://manifest#/$defs/<Name>"`. The URI is the contract;
|