@telorun/templating 0.7.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 +7 -15
- 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 +1 -1
- package/src/index.ts +1 -0
- package/src/manifest-schemas.ts +144 -19
package/README.md
CHANGED
|
@@ -58,8 +58,8 @@ imports:
|
|
|
58
58
|
Http: std/http-server@0.9.0
|
|
59
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/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
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;
|