@orkestrel/template 0.0.5 → 0.0.6
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 +24 -4
- package/dist/src/core/index.cjs +147 -77
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +701 -598
- package/dist/src/core/index.d.ts +701 -598
- package/dist/src/core/index.js +147 -78
- package/dist/src/core/index.js.map +1 -1
- package/package.json +19 -15
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @orkestrel/template
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A stateful template registry and filler with typed placeholders — `{{name}}`
|
|
4
|
+
tokens in a `content` string, resolved against a values record by a
|
|
5
|
+
single-pass fill engine, and registered and looked up by id through
|
|
6
|
+
`TemplateManager`. Every fill lookup refuses a prototype-pollution-unsafe
|
|
7
|
+
field path: any segment in `UNSAFE_FIELD_SEGMENTS` (`__proto__`,
|
|
8
|
+
`constructor`, `prototype`) is refused before the record is ever read. Part of
|
|
9
|
+
the `@orkestrel` line.
|
|
4
10
|
|
|
5
11
|
## Install
|
|
6
12
|
|
|
@@ -8,17 +14,31 @@ TODO: one-line description. Part of the `@orkestrel` line.
|
|
|
8
14
|
npm install @orkestrel/template
|
|
9
15
|
```
|
|
10
16
|
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Node.js >= 22.12.0
|
|
20
|
+
- Runtime dependencies `@orkestrel/contract` and `@orkestrel/emitter`
|
|
21
|
+
|
|
11
22
|
## Usage
|
|
12
23
|
|
|
13
24
|
```ts
|
|
14
|
-
import { createTemplate } from '@orkestrel/template'
|
|
25
|
+
import { createTemplate, createTemplateManager } from '@orkestrel/template'
|
|
15
26
|
|
|
16
|
-
const
|
|
27
|
+
const greeting = createTemplate({ name: 'greeting', content: 'Hi {{name}}' })
|
|
28
|
+
greeting.fill({ name: 'Ada' }) // 'Hi Ada'
|
|
29
|
+
|
|
30
|
+
const templates = createTemplateManager({ templates: [greeting] })
|
|
31
|
+
templates.fill(greeting.id, { name: 'Grace' }) // 'Hi Grace'
|
|
17
32
|
```
|
|
18
33
|
|
|
34
|
+
An unresolved required placeholder is governed by `TemplateFillOptions.missing`,
|
|
35
|
+
which defaults to `'error'` and throws a `TemplateError` coded `MISSING`.
|
|
36
|
+
`'empty'` substitutes `''` instead, and `'literal'` re-emits the original
|
|
37
|
+
`{{name}}` token.
|
|
38
|
+
|
|
19
39
|
## Guide
|
|
20
40
|
|
|
21
|
-
For the full surface, see [`guides/
|
|
41
|
+
For the full surface, see [`guides/template.md`](guides/template.md).
|
|
22
42
|
|
|
23
43
|
## License
|
|
24
44
|
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -3,8 +3,8 @@ let _orkestrel_contract = require("@orkestrel/contract");
|
|
|
3
3
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
4
4
|
//#region src/core/constants.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* and `Template#validate`.
|
|
6
|
+
* Holds the single-pass `{{name}}` substitution pattern shared by
|
|
7
|
+
* `Template#fill` and `Template#validate`.
|
|
8
8
|
*
|
|
9
9
|
* @remarks
|
|
10
10
|
* Global-flagged, two-alternative pattern: a match of the FIRST alternative
|
|
@@ -23,13 +23,14 @@ let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
|
23
23
|
* instance's mutable `lastIndex` across scans.
|
|
24
24
|
*/
|
|
25
25
|
var FILL_PATTERN = /\\\{\{|\{\{([^{}]+?)\}\}/g;
|
|
26
|
-
/**
|
|
26
|
+
/** Holds the default `missing` policy for `Template#fill` / `TemplateManager#fill` when unspecified. */
|
|
27
27
|
var DEFAULT_MISSING_POLICY = "error";
|
|
28
|
-
/**
|
|
28
|
+
/** Holds the default `locale` for `Template#fill` / `TemplateManager#fill` when unspecified. */
|
|
29
29
|
var DEFAULT_LOCALE = "en-US";
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
* resolve ANY path containing one, treating the placeholder as
|
|
31
|
+
* Lists the prototype-pollution-unsafe field-path segments — a fill lookup
|
|
32
|
+
* refuses to resolve ANY path containing one, treating the placeholder as
|
|
33
|
+
* unresolved.
|
|
33
34
|
*/
|
|
34
35
|
var UNSAFE_FIELD_SEGMENTS = Object.freeze([
|
|
35
36
|
"__proto__",
|
|
@@ -39,7 +40,7 @@ var UNSAFE_FIELD_SEGMENTS = Object.freeze([
|
|
|
39
40
|
//#endregion
|
|
40
41
|
//#region src/core/errors.ts
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* Represents an error thrown by the template layer.
|
|
43
44
|
*
|
|
44
45
|
* @remarks
|
|
45
46
|
* Thrown for: a required placeholder staying unresolved under the `error`
|
|
@@ -60,17 +61,17 @@ var TemplateError = class extends Error {
|
|
|
60
61
|
}
|
|
61
62
|
};
|
|
62
63
|
/**
|
|
63
|
-
*
|
|
64
|
+
* Narrows an unknown caught value to a {@link TemplateError}.
|
|
64
65
|
*
|
|
65
66
|
* @param value - The value to test (typically a `catch` binding)
|
|
66
|
-
* @returns
|
|
67
|
+
* @returns True if `value` is a {@link TemplateError}; false otherwise
|
|
67
68
|
*
|
|
68
69
|
* @example
|
|
69
70
|
* ```ts
|
|
70
71
|
* import { isTemplateError } from '@src/core'
|
|
71
72
|
*
|
|
72
73
|
* try {
|
|
73
|
-
* manager.
|
|
74
|
+
* manager.fill('missing')
|
|
74
75
|
* } catch (error) {
|
|
75
76
|
* if (isTemplateError(error) && error.code === 'NOTFOUND') return
|
|
76
77
|
* }
|
|
@@ -82,13 +83,14 @@ function isTemplateError(value) {
|
|
|
82
83
|
//#endregion
|
|
83
84
|
//#region src/core/helpers.ts
|
|
84
85
|
/**
|
|
85
|
-
*
|
|
86
|
+
* Formats a resolved fill value for substitution into a template's `content`.
|
|
86
87
|
*
|
|
87
88
|
* @remarks
|
|
88
|
-
* A finite number renders with the given locale's thousand grouping (
|
|
89
|
+
* A finite number renders with the given locale's thousand grouping (through
|
|
89
90
|
* `toLocaleString`); every other value — including `null` — String-coerces.
|
|
90
|
-
* `null` therefore renders as the literal string `'null'`,
|
|
91
|
-
*
|
|
91
|
+
* `null` therefore renders as the literal string `'null'`, matching
|
|
92
|
+
* `String(value)` exactly, so a resolved `null` is visible in the output
|
|
93
|
+
* rather than silently empty. An
|
|
92
94
|
* invalid BCP-47 `locale` tag throws a `RangeError` from the underlying
|
|
93
95
|
* `toLocaleString` call when `value` is a finite number — this is a caller
|
|
94
96
|
* error (an invalid locale argument), by design, and is not caught here.
|
|
@@ -110,7 +112,7 @@ function formatValue(value, locale) {
|
|
|
110
112
|
return String(value);
|
|
111
113
|
}
|
|
112
114
|
/**
|
|
113
|
-
*
|
|
115
|
+
* Resolves a field path against a fill-values record, refusing any path that
|
|
114
116
|
* touches a prototype-pollution-unsafe segment.
|
|
115
117
|
*
|
|
116
118
|
* @remarks
|
|
@@ -140,16 +142,55 @@ function resolveSafeField(record, path) {
|
|
|
140
142
|
return (0, _orkestrel_contract.resolveField)(record, path);
|
|
141
143
|
}
|
|
142
144
|
/**
|
|
143
|
-
*
|
|
145
|
+
* Resolves one `{{name}}` token against the declared placeholders and the
|
|
146
|
+
* fill-values record.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* The single implementation of the token rule `fillTemplate` and
|
|
150
|
+
* `Template#validate` both apply, so the two can never drift: the declared
|
|
151
|
+
* {@link TemplatePlaceholder} sharing the token's `name` (exact match)
|
|
152
|
+
* supplies its `path`, falling back to the token split on `.`; the value
|
|
153
|
+
* resolves through `resolveSafeField`, so any segment in
|
|
154
|
+
* `UNSAFE_FIELD_SEGMENTS` yields `undefined` without ever calling
|
|
155
|
+
* `resolveField`; `required` is `true` for an undeclared token and for a
|
|
156
|
+
* declared placeholder whose `required` is not `false`. The token is passed
|
|
157
|
+
* already trimmed. `fallback` is not applied here — it is read from
|
|
158
|
+
* `declared` by each caller, because `fill` substitutes it and `validate`
|
|
159
|
+
* only counts it.
|
|
160
|
+
*
|
|
161
|
+
* @param record - The fill-values record the token resolves against
|
|
162
|
+
* @param placeholders - The declared placeholders the token matches by name
|
|
163
|
+
* @param token - The trimmed token text, without its `{{` / `}}` delimiters
|
|
164
|
+
* @returns The {@link TemplateTokenResolution} for the token
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* import { resolveToken } from '@src/core'
|
|
169
|
+
*
|
|
170
|
+
* resolveToken({ name: 'Ada' }, [], 'name').value // 'Ada'
|
|
171
|
+
* resolveToken({}, [{ name: 'nickname', required: false }], 'nickname').required // false
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
function resolveToken(record, placeholders, token) {
|
|
175
|
+
const declared = placeholders.find((placeholder) => placeholder.name === token);
|
|
176
|
+
return {
|
|
177
|
+
value: resolveSafeField(record, declared?.path ?? token.split(".")),
|
|
178
|
+
declared,
|
|
179
|
+
required: declared === void 0 || declared.required !== false
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Substitutes every `{{name}}` token in `content` in a single pass.
|
|
144
184
|
*
|
|
145
185
|
* @remarks
|
|
146
186
|
* Uses a fresh `RegExp` clone of `FILL_PATTERN` per call (never sharing its
|
|
147
187
|
* `lastIndex`) and a single `String#replace` scan — substituted output is
|
|
148
|
-
* never re-scanned.
|
|
188
|
+
* never re-scanned. Each token resolves through `resolveToken`, the one rule
|
|
189
|
+
* `Template#validate` also applies: the matching declared
|
|
149
190
|
* {@link TemplatePlaceholder} (exact `name`) supplies its `path` (falling
|
|
150
191
|
* back to the token split on `.`); ANY path segment in `UNSAFE_FIELD_SEGMENTS`
|
|
151
192
|
* makes the token unresolved without ever calling `resolveField` (a
|
|
152
|
-
* prototype-pollution guard). A resolved value formats
|
|
193
|
+
* prototype-pollution guard). A resolved value formats through `formatValue`; an
|
|
153
194
|
* unresolved value falls back to the placeholder's `fallback` when declared;
|
|
154
195
|
* otherwise `options.missing` governs — `'literal'` re-emits the original
|
|
155
196
|
* `{{name}}` text, `'empty'` emits `''`, and `'error'` emits `''` for every
|
|
@@ -158,11 +199,11 @@ function resolveSafeField(record, path) {
|
|
|
158
199
|
* {@link TemplateError} coded `MISSING` listing them all, in first-appearance
|
|
159
200
|
* order, once the scan completes. An escaped `\{{` emits a literal `{{`.
|
|
160
201
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* class (`[^{}]`) excludes `{`,
|
|
165
|
-
*
|
|
202
|
+
* Called with no declared `placeholders` and `{ missing: 'empty' }`, this is a
|
|
203
|
+
* bare interpolation over `content` — every token resolves by dotted path
|
|
204
|
+
* against the values record and every unresolved token emits `''`.
|
|
205
|
+
* `FILL_PATTERN`'s token class (`[^{}]`) excludes `{`, so a token containing
|
|
206
|
+
* `{` never matches and the surrounding `{{` stays literal.
|
|
166
207
|
*
|
|
167
208
|
* @param content - The template content carrying `{{name}}` tokens
|
|
168
209
|
* @param values - The values tokens resolve against
|
|
@@ -188,14 +229,12 @@ function fillTemplate(content, values, options) {
|
|
|
188
229
|
const result = content.replace(pattern, (matchText, rawToken) => {
|
|
189
230
|
if (rawToken === void 0) return "{{";
|
|
190
231
|
const token = rawToken.trim();
|
|
191
|
-
const declared =
|
|
192
|
-
const path = declared?.path ?? token.split(".");
|
|
193
|
-
const value = resolveSafeField(record, path);
|
|
232
|
+
const { value, declared, required } = resolveToken(record, placeholders, token);
|
|
194
233
|
if (value !== void 0) return formatValue(value, locale);
|
|
195
234
|
if (declared?.fallback !== void 0) return formatValue(declared.fallback, locale);
|
|
196
235
|
if (missing === "literal") return matchText;
|
|
197
236
|
if (missing === "empty") return "";
|
|
198
|
-
if (
|
|
237
|
+
if (required && !seen.has(token)) {
|
|
199
238
|
seen.add(token);
|
|
200
239
|
missingNames.push(token);
|
|
201
240
|
}
|
|
@@ -204,8 +243,10 @@ function fillTemplate(content, values, options) {
|
|
|
204
243
|
if (missing === "error" && missingNames.length > 0) throw new TemplateError("MISSING", `Missing required placeholder(s): ${missingNames.join(", ")}`, { missing: missingNames });
|
|
205
244
|
return result;
|
|
206
245
|
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/core/shapers.ts
|
|
207
248
|
/**
|
|
208
|
-
*
|
|
249
|
+
* Builds the `@orkestrel/contract` object shape describing a template's
|
|
209
250
|
* declared placeholders.
|
|
210
251
|
*
|
|
211
252
|
* @remarks
|
|
@@ -234,15 +275,17 @@ function placeholderShape(placeholders) {
|
|
|
234
275
|
return (0, _orkestrel_contract.objectShape)(properties);
|
|
235
276
|
}
|
|
236
277
|
//#endregion
|
|
237
|
-
//#region src/core/Template.ts
|
|
278
|
+
//#region src/core/templates/Template.ts
|
|
238
279
|
/**
|
|
239
|
-
*
|
|
240
|
-
* against a values record.
|
|
280
|
+
* Represents a named, versionable template — `{{name}}` tokens in `content`,
|
|
281
|
+
* filled against a values record.
|
|
241
282
|
*
|
|
242
283
|
* @remarks
|
|
243
284
|
* `missing` / `locale` seed this instance's default {@link TemplateFillOptions},
|
|
244
285
|
* overridable per `fill` call. Its `parameters()` contract (built from
|
|
245
|
-
* `placeholders`
|
|
286
|
+
* `placeholders` through `placeholderShape`) compiles once, in the constructor.
|
|
287
|
+
*
|
|
288
|
+
* @throws {@link TemplateError} Thrown when `options.placeholders` declares a duplicate `name` or an empty `path` (coded `INVALID`)
|
|
246
289
|
*
|
|
247
290
|
* @example
|
|
248
291
|
* ```ts
|
|
@@ -251,6 +294,9 @@ function placeholderShape(placeholders) {
|
|
|
251
294
|
* ```
|
|
252
295
|
*/
|
|
253
296
|
var Template = class {
|
|
297
|
+
#missing;
|
|
298
|
+
#locale;
|
|
299
|
+
#contract;
|
|
254
300
|
id;
|
|
255
301
|
name;
|
|
256
302
|
content;
|
|
@@ -259,9 +305,6 @@ var Template = class {
|
|
|
259
305
|
description;
|
|
260
306
|
category;
|
|
261
307
|
tags;
|
|
262
|
-
#missing;
|
|
263
|
-
#locale;
|
|
264
|
-
#contract;
|
|
265
308
|
constructor(options) {
|
|
266
309
|
const placeholders = options.placeholders ?? [];
|
|
267
310
|
const seenNames = /* @__PURE__ */ new Set();
|
|
@@ -283,7 +326,7 @@ var Template = class {
|
|
|
283
326
|
this.#contract = (0, _orkestrel_contract.createContract)(placeholderShape(this.placeholders));
|
|
284
327
|
}
|
|
285
328
|
/**
|
|
286
|
-
*
|
|
329
|
+
* Returns the plain, JSON-serializable data this template carries.
|
|
287
330
|
*
|
|
288
331
|
* @returns The {@link TemplateDefinition} record
|
|
289
332
|
*
|
|
@@ -306,11 +349,12 @@ var Template = class {
|
|
|
306
349
|
};
|
|
307
350
|
}
|
|
308
351
|
/**
|
|
309
|
-
*
|
|
352
|
+
* Substitutes every `{{name}}` token in `content` against `values`.
|
|
310
353
|
*
|
|
311
354
|
* @param values - The values tokens resolve against
|
|
312
355
|
* @param options - Per-call overrides for this instance's `missing` / `locale` defaults
|
|
313
356
|
* @returns The substituted content
|
|
357
|
+
* @throws {@link TemplateError} Thrown when a required placeholder stays unresolved under the `'error'` policy (coded `MISSING`)
|
|
314
358
|
*
|
|
315
359
|
* @example
|
|
316
360
|
* ```ts
|
|
@@ -326,7 +370,7 @@ var Template = class {
|
|
|
326
370
|
});
|
|
327
371
|
}
|
|
328
372
|
/**
|
|
329
|
-
*
|
|
373
|
+
* Reports which required placeholders would stay unresolved, and which
|
|
330
374
|
* `values` keys go unused, without producing output.
|
|
331
375
|
*
|
|
332
376
|
* @remarks
|
|
@@ -335,9 +379,10 @@ var Template = class {
|
|
|
335
379
|
* predicts `fill`'s `'error'`-{@link MissingPolicy} outcome exactly — a
|
|
336
380
|
* token reported here as missing is precisely a token that would throw
|
|
337
381
|
* under `fill(values, { missing: 'error' })`. For each distinct token
|
|
338
|
-
* (first-appearance order, trimmed):
|
|
382
|
+
* (first-appearance order, trimmed): `resolveToken` applies the one shared
|
|
383
|
+
* token rule `fill` also applies — a declared {@link TemplatePlaceholder}
|
|
339
384
|
* sharing its `name` supplies `path` (falling back to the token split on
|
|
340
|
-
* `.`)
|
|
385
|
+
* `.`), and the value resolves through `resolveSafeField`. The token is `missing`
|
|
341
386
|
* only when the value is unresolved AND no `fallback` is declared AND the
|
|
342
387
|
* placeholder is required (`required !== false`, including undeclared
|
|
343
388
|
* tokens). `extra` lists every `values` key with no declared placeholder.
|
|
@@ -366,10 +411,8 @@ var Template = class {
|
|
|
366
411
|
const token = rawToken.trim();
|
|
367
412
|
if (seen.has(token)) continue;
|
|
368
413
|
seen.add(token);
|
|
369
|
-
const declared = this.placeholders
|
|
370
|
-
|
|
371
|
-
const required = declared === void 0 || declared.required !== false;
|
|
372
|
-
if (resolved === void 0 && declared?.fallback === void 0 && required) missing.push(token);
|
|
414
|
+
const { value, declared, required } = resolveToken(record, this.placeholders, token);
|
|
415
|
+
if (value === void 0 && declared?.fallback === void 0 && required) missing.push(token);
|
|
373
416
|
}
|
|
374
417
|
const declaredNames = new Set(this.placeholders.map((placeholder) => placeholder.name));
|
|
375
418
|
const extra = Object.keys(record).filter((key) => !declaredNames.has(key));
|
|
@@ -380,7 +423,7 @@ var Template = class {
|
|
|
380
423
|
};
|
|
381
424
|
}
|
|
382
425
|
/**
|
|
383
|
-
*
|
|
426
|
+
* Projects this template's placeholders to the open tool-parameters record
|
|
384
427
|
* shape.
|
|
385
428
|
*
|
|
386
429
|
* @returns The compiled parameters record, or `undefined` when `schemaToParameters` yields none
|
|
@@ -400,12 +443,12 @@ var Template = class {
|
|
|
400
443
|
}
|
|
401
444
|
};
|
|
402
445
|
//#endregion
|
|
403
|
-
//#region src/core/TemplateManager.ts
|
|
446
|
+
//#region src/core/templates/TemplateManager.ts
|
|
404
447
|
/**
|
|
405
|
-
*
|
|
448
|
+
* Represents the template registry — a self-owning, id-keyed record-holder for the
|
|
406
449
|
* {@link TemplateInterface} instances a consumer registers, looks up, fills,
|
|
407
|
-
* and validates by id
|
|
408
|
-
*
|
|
450
|
+
* and validates by id, with singular/plural accessors, batch `remove`
|
|
451
|
+
* overloads, and emitter ownership.
|
|
409
452
|
*
|
|
410
453
|
* @remarks
|
|
411
454
|
* `register` accepts either a constructed {@link TemplateInterface} (kept
|
|
@@ -416,8 +459,8 @@ var Template = class {
|
|
|
416
459
|
* unless `options.replace` is `true`, in which case the existing entry is
|
|
417
460
|
* overwritten. `options.templates` SEEDS the registry at construction
|
|
418
461
|
* WITHOUT emitting `register` — only calls to `register` after construction
|
|
419
|
-
* emit. The batch `remove(ids)` form
|
|
420
|
-
*
|
|
462
|
+
* emit. The batch `remove(ids)` form removes every present id and returns
|
|
463
|
+
* `true` only when every listed id was present.
|
|
421
464
|
*
|
|
422
465
|
* @example
|
|
423
466
|
* ```ts
|
|
@@ -450,11 +493,11 @@ var TemplateManager = class {
|
|
|
450
493
|
get emitter() {
|
|
451
494
|
return this.#emitter;
|
|
452
495
|
}
|
|
453
|
-
get
|
|
496
|
+
get count() {
|
|
454
497
|
return this.#templates.size;
|
|
455
498
|
}
|
|
456
499
|
/**
|
|
457
|
-
*
|
|
500
|
+
* Registers a template — a constructed {@link TemplateInterface} (kept
|
|
458
501
|
* as-is) or a plain {@link TemplateOptions} bag (constructed into a
|
|
459
502
|
* `Template` with this manager's `missing` / `locale` defaults applied
|
|
460
503
|
* wherever the bag omits them).
|
|
@@ -462,6 +505,7 @@ var TemplateManager = class {
|
|
|
462
505
|
* @param template - The template instance or options to register
|
|
463
506
|
* @param options - `replace` — overwrite an existing entry sharing the same id instead of throwing
|
|
464
507
|
* @returns The registered {@link TemplateInterface}
|
|
508
|
+
* @throws {@link TemplateError} Thrown when the id is already registered and `options.replace` is not `true` (coded `CONFLICT`), or when an options bag declares a duplicate placeholder `name` or an empty `path` (coded `INVALID`)
|
|
465
509
|
*
|
|
466
510
|
* @example
|
|
467
511
|
* ```ts
|
|
@@ -476,19 +520,16 @@ var TemplateManager = class {
|
|
|
476
520
|
return instance;
|
|
477
521
|
}
|
|
478
522
|
/**
|
|
479
|
-
*
|
|
523
|
+
* Returns one registered {@link TemplateInterface} by id.
|
|
480
524
|
*
|
|
481
525
|
* @param id - The template id
|
|
482
|
-
* @returns The registered {@link TemplateInterface}
|
|
483
|
-
* @throws {@link TemplateError} coded `NOTFOUND` when `id` is unknown
|
|
526
|
+
* @returns The registered {@link TemplateInterface}, or `undefined` when `id` is unregistered
|
|
484
527
|
*/
|
|
485
528
|
template(id) {
|
|
486
|
-
|
|
487
|
-
if (instance === void 0) this.#throwNotFound(id);
|
|
488
|
-
return instance;
|
|
529
|
+
return this.#templates.get(id);
|
|
489
530
|
}
|
|
490
531
|
/**
|
|
491
|
-
*
|
|
532
|
+
* Lists every registered template.
|
|
492
533
|
*
|
|
493
534
|
* @returns A snapshot array of every registered {@link TemplateInterface}
|
|
494
535
|
*/
|
|
@@ -496,7 +537,7 @@ var TemplateManager = class {
|
|
|
496
537
|
return [...this.#templates.values()];
|
|
497
538
|
}
|
|
498
539
|
/**
|
|
499
|
-
*
|
|
540
|
+
* Filters registered templates by name / category / tag — every supplied
|
|
500
541
|
* field must match (logical AND).
|
|
501
542
|
*
|
|
502
543
|
* @param query - The {@link TemplateQuery} to filter by; omit for every registered template
|
|
@@ -512,10 +553,10 @@ var TemplateManager = class {
|
|
|
512
553
|
});
|
|
513
554
|
}
|
|
514
555
|
/**
|
|
515
|
-
*
|
|
556
|
+
* Tests whether a template id is registered.
|
|
516
557
|
*
|
|
517
558
|
* @param id - The template id
|
|
518
|
-
* @returns
|
|
559
|
+
* @returns True if `id` is registered; false otherwise
|
|
519
560
|
*/
|
|
520
561
|
has(id) {
|
|
521
562
|
return this.#templates.has(id);
|
|
@@ -533,34 +574,58 @@ var TemplateManager = class {
|
|
|
533
574
|
this.#emitter.emit("remove", instance);
|
|
534
575
|
return true;
|
|
535
576
|
}
|
|
536
|
-
|
|
577
|
+
let all = true;
|
|
537
578
|
for (const id of target) {
|
|
538
579
|
const instance = this.#templates.get(id);
|
|
539
|
-
if (instance === void 0)
|
|
580
|
+
if (instance === void 0) {
|
|
581
|
+
all = false;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
540
584
|
this.#templates.delete(id);
|
|
541
585
|
this.#emitter.emit("remove", instance);
|
|
542
586
|
}
|
|
543
|
-
return
|
|
587
|
+
return all;
|
|
544
588
|
}
|
|
545
|
-
/**
|
|
589
|
+
/** Removes every registered template, emitting `clear`. */
|
|
546
590
|
clear() {
|
|
547
591
|
this.#templates.clear();
|
|
548
592
|
this.#emitter.emit("clear");
|
|
549
593
|
}
|
|
550
594
|
/**
|
|
551
|
-
*
|
|
595
|
+
* Tears down the registry: drops every registered template and destroys the
|
|
596
|
+
* owned emitter. Idempotent.
|
|
597
|
+
*
|
|
598
|
+
* @remarks
|
|
599
|
+
* Teardown is not an observable registry operation and the emitter is being
|
|
600
|
+
* released, so this emits neither `clear` nor `remove`. The emitter is torn
|
|
601
|
+
* down last, after the registry is dropped.
|
|
602
|
+
*
|
|
603
|
+
* @example
|
|
604
|
+
* ```ts
|
|
605
|
+
* const manager = new TemplateManager()
|
|
606
|
+
* manager.destroy()
|
|
607
|
+
* manager.emitter.destroyed // true
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
destroy() {
|
|
611
|
+
this.#templates.clear();
|
|
612
|
+
this.#emitter.destroy();
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Fills a registered template by id.
|
|
552
616
|
*
|
|
553
617
|
* @param id - The template id
|
|
554
618
|
* @param values - The values tokens resolve against
|
|
555
619
|
* @param options - Per-call overrides for the template's `missing` / `locale` defaults
|
|
556
620
|
* @returns The substituted content
|
|
557
621
|
* @throws {@link TemplateError} coded `NOTFOUND` when `id` is unknown
|
|
622
|
+
* @throws {@link TemplateError} Thrown when a required placeholder stays unresolved under the `'error'` policy (coded `MISSING`)
|
|
558
623
|
*/
|
|
559
624
|
fill(id, values, options) {
|
|
560
|
-
return this
|
|
625
|
+
return this.#require(id).fill(values, options);
|
|
561
626
|
}
|
|
562
627
|
/**
|
|
563
|
-
*
|
|
628
|
+
* Validates values against a registered template by id.
|
|
564
629
|
*
|
|
565
630
|
* @param id - The template id
|
|
566
631
|
* @param values - The values to check
|
|
@@ -568,17 +633,17 @@ var TemplateManager = class {
|
|
|
568
633
|
* @throws {@link TemplateError} coded `NOTFOUND` when `id` is unknown
|
|
569
634
|
*/
|
|
570
635
|
validate(id, values) {
|
|
571
|
-
return this
|
|
636
|
+
return this.#require(id).validate(values);
|
|
572
637
|
}
|
|
573
638
|
/**
|
|
574
|
-
*
|
|
639
|
+
* Projects a registered template's parameters by id.
|
|
575
640
|
*
|
|
576
641
|
* @param id - The template id
|
|
577
642
|
* @returns The compiled parameters record, or `undefined` when the template has none
|
|
578
643
|
* @throws {@link TemplateError} coded `NOTFOUND` when `id` is unknown
|
|
579
644
|
*/
|
|
580
645
|
parameters(id) {
|
|
581
|
-
return this
|
|
646
|
+
return this.#require(id).parameters();
|
|
582
647
|
}
|
|
583
648
|
#instantiate(template) {
|
|
584
649
|
if (this.#isInstance(template)) return template;
|
|
@@ -591,19 +656,22 @@ var TemplateManager = class {
|
|
|
591
656
|
#isInstance(template) {
|
|
592
657
|
return "fill" in template && typeof template.fill === "function" && "validate" in template && typeof template.validate === "function" && "parameters" in template && typeof template.parameters === "function";
|
|
593
658
|
}
|
|
594
|
-
#
|
|
595
|
-
|
|
659
|
+
#require(id) {
|
|
660
|
+
const instance = this.#templates.get(id);
|
|
661
|
+
if (instance === void 0) throw new TemplateError("NOTFOUND", `Unknown template id: ${id}`, { id });
|
|
662
|
+
return instance;
|
|
596
663
|
}
|
|
597
664
|
};
|
|
598
665
|
//#endregion
|
|
599
666
|
//#region src/core/factories.ts
|
|
600
667
|
/**
|
|
601
|
-
*
|
|
668
|
+
* Creates a template.
|
|
602
669
|
*
|
|
603
670
|
* @param options - The template's `name` / `content`, an optional `id`
|
|
604
671
|
* (defaults to a generated UUID), `placeholders`, catalog metadata, and
|
|
605
672
|
* `missing` / `locale` fill defaults
|
|
606
673
|
* @returns A working {@link TemplateInterface}
|
|
674
|
+
* @throws {@link TemplateError} Thrown when `options.placeholders` declares a duplicate `name` or an empty `path` (coded `INVALID`)
|
|
607
675
|
*
|
|
608
676
|
* @example
|
|
609
677
|
* ```ts
|
|
@@ -617,12 +685,13 @@ function createTemplate(options) {
|
|
|
617
685
|
return new Template(options);
|
|
618
686
|
}
|
|
619
687
|
/**
|
|
620
|
-
*
|
|
688
|
+
* Creates a template registry.
|
|
621
689
|
*
|
|
622
690
|
* @param options - Optional initial `templates` seed collection and
|
|
623
691
|
* manager-wide `missing` / `locale` fill defaults, emitter `on` hooks, and
|
|
624
692
|
* an `error` handler
|
|
625
693
|
* @returns A working {@link TemplateManagerInterface}
|
|
694
|
+
* @throws {@link TemplateError} Thrown when a seeded `options.templates` bag declares a duplicate placeholder `name` or an empty `path` (coded `INVALID`)
|
|
626
695
|
*
|
|
627
696
|
* @example
|
|
628
697
|
* ```ts
|
|
@@ -652,5 +721,6 @@ exports.formatValue = formatValue;
|
|
|
652
721
|
exports.isTemplateError = isTemplateError;
|
|
653
722
|
exports.placeholderShape = placeholderShape;
|
|
654
723
|
exports.resolveSafeField = resolveSafeField;
|
|
724
|
+
exports.resolveToken = resolveToken;
|
|
655
725
|
|
|
656
726
|
//# sourceMappingURL=index.cjs.map
|