@metaobjectsdev/render 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +6 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +6 -1
- package/dist/render.js.map +1 -1
- package/dist/verify.d.ts +8 -0
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +38 -0
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/render.ts +13 -1
- package/src/verify.ts +47 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { render, type RenderOptions } from "./render.js";
|
|
2
2
|
export { type Provider, InMemoryProvider } from "./provider.js";
|
|
3
3
|
export { ESCAPERS, type RenderFormat } from "./escapers.js";
|
|
4
|
-
export { verify, ERR_VAR_NOT_ON_PAYLOAD, ERR_PARTIAL_UNRESOLVED, ERR_REQUIRED_SLOT_UNUSED, type PayloadField, type VerifyError, type VerifyOptions, } from "./verify.js";
|
|
4
|
+
export { verify, ERR_VAR_NOT_ON_PAYLOAD, ERR_PARTIAL_UNRESOLVED, ERR_REQUIRED_SLOT_UNUSED, ERR_OUTPUT_TAG_MISSING, type PayloadField, type VerifyError, type VerifyOptions, } from "./verify.js";
|
|
5
5
|
//# 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,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { render } from "./render.js";
|
|
2
2
|
export { InMemoryProvider } from "./provider.js";
|
|
3
3
|
export { ESCAPERS } from "./escapers.js";
|
|
4
|
-
export { verify, ERR_VAR_NOT_ON_PAYLOAD, ERR_PARTIAL_UNRESOLVED, ERR_REQUIRED_SLOT_UNUSED, } from "./verify.js";
|
|
4
|
+
export { verify, ERR_VAR_NOT_ON_PAYLOAD, ERR_PARTIAL_UNRESOLVED, ERR_REQUIRED_SLOT_UNUSED, ERR_OUTPUT_TAG_MISSING, } from "./verify.js";
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAiB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAiB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,GAIvB,MAAM,aAAa,CAAC"}
|
package/dist/render.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export interface RenderOptions {
|
|
|
17
17
|
* variant throws — instead of silently rendering nothing.
|
|
18
18
|
*/
|
|
19
19
|
verify?: PayloadField[];
|
|
20
|
+
/**
|
|
21
|
+
* Output budget in characters. Rendered length is data-dependent (only knowable
|
|
22
|
+
* after rendering), so this is a render-time guard: a result longer than
|
|
23
|
+
* `maxChars` throws. (Token budgets are out of scope — model-specific tokenizer.)
|
|
24
|
+
*/
|
|
25
|
+
maxChars?: number;
|
|
20
26
|
}
|
|
21
27
|
/** Deterministic, logic-less render: (template + payload + provider) → string. */
|
|
22
28
|
export declare function render(o: RenderOptions): string;
|
package/dist/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAoBlF,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAoBlF,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kFAAkF;AAClF,wBAAgB,MAAM,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CA+B/C"}
|
package/dist/render.js
CHANGED
|
@@ -35,11 +35,16 @@ export function render(o) {
|
|
|
35
35
|
const escaper = ESCAPERS[o.format ?? "text"];
|
|
36
36
|
const prev = Mustache.escape;
|
|
37
37
|
Mustache.escape = (v) => escaper(typeof v === "string" ? v : String(v));
|
|
38
|
+
let result;
|
|
38
39
|
try {
|
|
39
|
-
|
|
40
|
+
result = Mustache.render(expanded, o.payload, {});
|
|
40
41
|
}
|
|
41
42
|
finally {
|
|
42
43
|
Mustache.escape = prev;
|
|
43
44
|
}
|
|
45
|
+
if (o.maxChars !== undefined && result.length > o.maxChars) {
|
|
46
|
+
throw new Error(`render exceeded maxChars budget: ${result.length} > ${o.maxChars}`);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
44
49
|
}
|
|
45
50
|
//# sourceMappingURL=render.js.map
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,wBAAwB,EAAqB,MAAM,aAAa,CAAC;AAElF,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,OAAO,GAAG,2BAA2B,CAAC;AAE5C,8EAA8E;AAC9E,wEAAwE;AACxE,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,SAAS,MAAM,CAAC,IAAY,EAAE,QAAkB,EAAE,IAAuB;IACvE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,GAAW,EAAE,EAAE;QACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzF,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,SAAS,KAAK,GAAG,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;QACnE,OAAO,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare const ERR_VAR_NOT_ON_PAYLOAD = "ERR_VAR_NOT_ON_PAYLOAD";
|
|
|
5
5
|
export declare const ERR_PARTIAL_UNRESOLVED = "ERR_PARTIAL_UNRESOLVED";
|
|
6
6
|
/** A declared @requiredSlots slot is never referenced by the template (warning). */
|
|
7
7
|
export declare const ERR_REQUIRED_SLOT_UNUSED = "ERR_REQUIRED_SLOT_UNUSED";
|
|
8
|
+
/** A declared @requiredTags output tag is absent from the template text. */
|
|
9
|
+
export declare const ERR_OUTPUT_TAG_MISSING = "ERR_OUTPUT_TAG_MISSING";
|
|
8
10
|
/**
|
|
9
11
|
* A plain field-tree node mirroring an `object.value` view-object's field walk.
|
|
10
12
|
* `fields` present → a context-pushing field (object / array-of-object); absent
|
|
@@ -24,6 +26,12 @@ export interface VerifyOptions {
|
|
|
24
26
|
provider?: Provider;
|
|
25
27
|
/** Slots that MUST be referenced; an unused one is reported as a warning. */
|
|
26
28
|
requiredSlots?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Output tags the rendered text is contracted to contain. Each must appear as
|
|
31
|
+
* both an opening form (`<tag` followed by `>` or whitespace, so attributes are
|
|
32
|
+
* allowed) and a closing `</tag>` — across the body AND resolved partials.
|
|
33
|
+
*/
|
|
34
|
+
requiredTags?: string[];
|
|
27
35
|
}
|
|
28
36
|
/**
|
|
29
37
|
* Walk a Mustache template's tokens against a payload field tree, returning a
|
package/dist/verify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,6BAA6B,CAAC;AACnE,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAE/D;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAyDD;;;;GAIG;AACH,wBAAgB,MAAM,CACpB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,YAAY,EAAE,EACtB,IAAI,CAAC,EAAE,aAAa,GACnB,WAAW,EAAE,CAuFf"}
|
package/dist/verify.js
CHANGED
|
@@ -15,6 +15,8 @@ export const ERR_VAR_NOT_ON_PAYLOAD = "ERR_VAR_NOT_ON_PAYLOAD";
|
|
|
15
15
|
export const ERR_PARTIAL_UNRESOLVED = "ERR_PARTIAL_UNRESOLVED";
|
|
16
16
|
/** A declared @requiredSlots slot is never referenced by the template (warning). */
|
|
17
17
|
export const ERR_REQUIRED_SLOT_UNUSED = "ERR_REQUIRED_SLOT_UNUSED";
|
|
18
|
+
/** A declared @requiredTags output tag is absent from the template text. */
|
|
19
|
+
export const ERR_OUTPUT_TAG_MISSING = "ERR_OUTPUT_TAG_MISSING";
|
|
18
20
|
const MAX_DEPTH = 32;
|
|
19
21
|
function find(fields, name) {
|
|
20
22
|
return fields.find((f) => f.name === name);
|
|
@@ -41,6 +43,24 @@ function resolve(stack, path) {
|
|
|
41
43
|
function parse(text) {
|
|
42
44
|
return Mustache.parse(text);
|
|
43
45
|
}
|
|
46
|
+
// An opening tag is `<tag` immediately followed by `>` or XML whitespace, so
|
|
47
|
+
// attributes are allowed (`<answer foo="1">`) but a longer name is not over-matched
|
|
48
|
+
// (`<answers>` does not satisfy `answer`).
|
|
49
|
+
const TAG_OPEN_DELIMS = new Set([">", " ", "\t", "\n", "\r"]);
|
|
50
|
+
function hasOpenTag(text, tag) {
|
|
51
|
+
const needle = `<${tag}`;
|
|
52
|
+
for (let i = text.indexOf(needle); i !== -1; i = text.indexOf(needle, i + 1)) {
|
|
53
|
+
const next = text[i + needle.length];
|
|
54
|
+
if (next !== undefined && TAG_OPEN_DELIMS.has(next))
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// A closing tag is the exact literal `</tag>`. A self-closing `<tag/>` has no such
|
|
60
|
+
// form, so it never satisfies a required tag — these wrap content a parser reads.
|
|
61
|
+
function hasCloseTag(text, tag) {
|
|
62
|
+
return text.includes(`</${tag}>`);
|
|
63
|
+
}
|
|
44
64
|
/**
|
|
45
65
|
* Walk a Mustache template's tokens against a payload field tree, returning a
|
|
46
66
|
* list of drift errors. Context-sensitive: a section `{{#posts}}…{{/posts}}`
|
|
@@ -51,6 +71,10 @@ export function verify(templateText, fields, opts) {
|
|
|
51
71
|
const provider = opts?.provider;
|
|
52
72
|
const root = fields;
|
|
53
73
|
const referencedAtRoot = new Set();
|
|
74
|
+
// The static text the output-tag check scans: the body plus every
|
|
75
|
+
// provider-resolved partial body, collected during the single walk below
|
|
76
|
+
// (no second resolution pass).
|
|
77
|
+
const staticTexts = [templateText];
|
|
54
78
|
function walk(tokens, stack, seen) {
|
|
55
79
|
const atRoot = stack.length === 1 && stack[0] === root;
|
|
56
80
|
for (const tok of tokens) {
|
|
@@ -103,6 +127,7 @@ export function verify(templateText, fields, opts) {
|
|
|
103
127
|
errors.push({ code: ERR_PARTIAL_UNRESOLVED, path: value });
|
|
104
128
|
break;
|
|
105
129
|
}
|
|
130
|
+
staticTexts.push(text);
|
|
106
131
|
walk(parse(text), stack, [...seen, value]);
|
|
107
132
|
break;
|
|
108
133
|
}
|
|
@@ -116,6 +141,19 @@ export function verify(templateText, fields, opts) {
|
|
|
116
141
|
if (!referencedAtRoot.has(slot))
|
|
117
142
|
errors.push({ code: ERR_REQUIRED_SLOT_UNUSED, path: slot });
|
|
118
143
|
}
|
|
144
|
+
const requiredTags = opts?.requiredTags ?? [];
|
|
145
|
+
if (requiredTags.length > 0) {
|
|
146
|
+
// Scan body + resolved partials as one joined string: the open and close
|
|
147
|
+
// forms are located independently, so a tag may legitimately straddle the
|
|
148
|
+
// boundary. The "\n" separator only blocks a spurious tag spliced together
|
|
149
|
+
// from two fragments (a real tag is always contiguous within one body).
|
|
150
|
+
const haystack = staticTexts.join("\n");
|
|
151
|
+
for (const tag of requiredTags) {
|
|
152
|
+
if (!hasOpenTag(haystack, tag) || !hasCloseTag(haystack, tag)) {
|
|
153
|
+
errors.push({ code: ERR_OUTPUT_TAG_MISSING, path: tag });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
119
157
|
return errors;
|
|
120
158
|
}
|
|
121
159
|
//# sourceMappingURL=verify.js.map
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,sDAAsD;AACtD,EAAE;AACF,wEAAwE;AACxE,+EAA+E;AAC/E,yEAAyE;AAEzE,OAAO,QAAQ,MAAM,UAAU,CAAC;AAGhC,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,8DAA8D;AAC9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,oFAAoF;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Logic-less, deterministic text render engine (Mustache) for MetaObjects templates — provider-resolved partials, format-driven escaping, zero core dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/index.ts
CHANGED
package/src/render.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface RenderOptions {
|
|
|
37
37
|
* variant throws — instead of silently rendering nothing.
|
|
38
38
|
*/
|
|
39
39
|
verify?: PayloadField[];
|
|
40
|
+
/**
|
|
41
|
+
* Output budget in characters. Rendered length is data-dependent (only knowable
|
|
42
|
+
* after rendering), so this is a render-time guard: a result longer than
|
|
43
|
+
* `maxChars` throws. (Token budgets are out of scope — model-specific tokenizer.)
|
|
44
|
+
*/
|
|
45
|
+
maxChars?: number;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
/** Deterministic, logic-less render: (template + payload + provider) → string. */
|
|
@@ -60,9 +66,15 @@ export function render(o: RenderOptions): string {
|
|
|
60
66
|
|
|
61
67
|
const prev = Mustache.escape;
|
|
62
68
|
Mustache.escape = (v: unknown) => escaper(typeof v === "string" ? v : String(v));
|
|
69
|
+
let result: string;
|
|
63
70
|
try {
|
|
64
|
-
|
|
71
|
+
result = Mustache.render(expanded, o.payload, {});
|
|
65
72
|
} finally {
|
|
66
73
|
Mustache.escape = prev;
|
|
67
74
|
}
|
|
75
|
+
|
|
76
|
+
if (o.maxChars !== undefined && result.length > o.maxChars) {
|
|
77
|
+
throw new Error(`render exceeded maxChars budget: ${result.length} > ${o.maxChars}`);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
68
80
|
}
|
package/src/verify.ts
CHANGED
|
@@ -18,6 +18,8 @@ export const ERR_VAR_NOT_ON_PAYLOAD = "ERR_VAR_NOT_ON_PAYLOAD";
|
|
|
18
18
|
export const ERR_PARTIAL_UNRESOLVED = "ERR_PARTIAL_UNRESOLVED";
|
|
19
19
|
/** A declared @requiredSlots slot is never referenced by the template (warning). */
|
|
20
20
|
export const ERR_REQUIRED_SLOT_UNUSED = "ERR_REQUIRED_SLOT_UNUSED";
|
|
21
|
+
/** A declared @requiredTags output tag is absent from the template text. */
|
|
22
|
+
export const ERR_OUTPUT_TAG_MISSING = "ERR_OUTPUT_TAG_MISSING";
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* A plain field-tree node mirroring an `object.value` view-object's field walk.
|
|
@@ -40,6 +42,12 @@ export interface VerifyOptions {
|
|
|
40
42
|
provider?: Provider;
|
|
41
43
|
/** Slots that MUST be referenced; an unused one is reported as a warning. */
|
|
42
44
|
requiredSlots?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Output tags the rendered text is contracted to contain. Each must appear as
|
|
47
|
+
* both an opening form (`<tag` followed by `>` or whitespace, so attributes are
|
|
48
|
+
* allowed) and a closing `</tag>` — across the body AND resolved partials.
|
|
49
|
+
*/
|
|
50
|
+
requiredTags?: string[];
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
const MAX_DEPTH = 32;
|
|
@@ -77,6 +85,26 @@ function parse(text: string): Token[] {
|
|
|
77
85
|
return Mustache.parse(text) as unknown as Token[];
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
// An opening tag is `<tag` immediately followed by `>` or XML whitespace, so
|
|
89
|
+
// attributes are allowed (`<answer foo="1">`) but a longer name is not over-matched
|
|
90
|
+
// (`<answers>` does not satisfy `answer`).
|
|
91
|
+
const TAG_OPEN_DELIMS = new Set([">", " ", "\t", "\n", "\r"]);
|
|
92
|
+
|
|
93
|
+
function hasOpenTag(text: string, tag: string): boolean {
|
|
94
|
+
const needle = `<${tag}`;
|
|
95
|
+
for (let i = text.indexOf(needle); i !== -1; i = text.indexOf(needle, i + 1)) {
|
|
96
|
+
const next = text[i + needle.length];
|
|
97
|
+
if (next !== undefined && TAG_OPEN_DELIMS.has(next)) return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// A closing tag is the exact literal `</tag>`. A self-closing `<tag/>` has no such
|
|
103
|
+
// form, so it never satisfies a required tag — these wrap content a parser reads.
|
|
104
|
+
function hasCloseTag(text: string, tag: string): boolean {
|
|
105
|
+
return text.includes(`</${tag}>`);
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
/**
|
|
81
109
|
* Walk a Mustache template's tokens against a payload field tree, returning a
|
|
82
110
|
* list of drift errors. Context-sensitive: a section `{{#posts}}…{{/posts}}`
|
|
@@ -91,6 +119,10 @@ export function verify(
|
|
|
91
119
|
const provider = opts?.provider;
|
|
92
120
|
const root = fields;
|
|
93
121
|
const referencedAtRoot = new Set<string>();
|
|
122
|
+
// The static text the output-tag check scans: the body plus every
|
|
123
|
+
// provider-resolved partial body, collected during the single walk below
|
|
124
|
+
// (no second resolution pass).
|
|
125
|
+
const staticTexts: string[] = [templateText];
|
|
94
126
|
|
|
95
127
|
function walk(tokens: Token[], stack: Stack, seen: readonly string[]): void {
|
|
96
128
|
const atRoot = stack.length === 1 && stack[0] === root;
|
|
@@ -138,6 +170,7 @@ export function verify(
|
|
|
138
170
|
errors.push({ code: ERR_PARTIAL_UNRESOLVED, path: value });
|
|
139
171
|
break;
|
|
140
172
|
}
|
|
173
|
+
staticTexts.push(text);
|
|
141
174
|
walk(parse(text), stack, [...seen, value]);
|
|
142
175
|
break;
|
|
143
176
|
}
|
|
@@ -153,5 +186,19 @@ export function verify(
|
|
|
153
186
|
if (!referencedAtRoot.has(slot)) errors.push({ code: ERR_REQUIRED_SLOT_UNUSED, path: slot });
|
|
154
187
|
}
|
|
155
188
|
|
|
189
|
+
const requiredTags = opts?.requiredTags ?? [];
|
|
190
|
+
if (requiredTags.length > 0) {
|
|
191
|
+
// Scan body + resolved partials as one joined string: the open and close
|
|
192
|
+
// forms are located independently, so a tag may legitimately straddle the
|
|
193
|
+
// boundary. The "\n" separator only blocks a spurious tag spliced together
|
|
194
|
+
// from two fragments (a real tag is always contiguous within one body).
|
|
195
|
+
const haystack = staticTexts.join("\n");
|
|
196
|
+
for (const tag of requiredTags) {
|
|
197
|
+
if (!hasOpenTag(haystack, tag) || !hasCloseTag(haystack, tag)) {
|
|
198
|
+
errors.push({ code: ERR_OUTPUT_TAG_MISSING, path: tag });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
156
203
|
return errors;
|
|
157
204
|
}
|