@orkestrel/template 0.0.6 → 0.0.8
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 +8 -6
- package/dist/src/core/index.cjs +59 -37
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +130 -57
- package/dist/src/core/index.d.ts +130 -57
- package/dist/src/core/index.js +60 -38
- package/dist/src/core/index.js.map +1 -1
- package/package.json +14 -15
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { ContractShape } from '@orkestrel/contract';
|
|
2
|
-
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
3
|
-
import { EmitterHooks } from '@orkestrel/emitter';
|
|
4
|
-
import { EmitterInterface } from '@orkestrel/emitter';
|
|
5
|
-
import { FieldPath } from '@orkestrel/contract';
|
|
1
|
+
import type { ContractShape } from '@orkestrel/contract';
|
|
2
|
+
import type { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
3
|
+
import type { EmitterHooks } from '@orkestrel/emitter';
|
|
4
|
+
import type { EmitterInterface } from '@orkestrel/emitter';
|
|
5
|
+
import type { FieldPath } from '@orkestrel/contract';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Creates a
|
|
8
|
+
* Creates a working {@link TemplateInterface} from a {@link TemplateOptions}
|
|
9
|
+
* bag, backed by the `Template` class.
|
|
9
10
|
*
|
|
10
11
|
* @param options - The template's `name` / `content`, an optional `id`
|
|
11
12
|
* (defaults to a generated UUID), `placeholders`, catalog metadata, and
|
|
@@ -13,18 +14,31 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
13
14
|
* @returns A working {@link TemplateInterface}
|
|
14
15
|
* @throws {@link TemplateError} Thrown when `options.placeholders` declares a duplicate `name` or an empty `path` (coded `INVALID`)
|
|
15
16
|
*
|
|
16
|
-
* @example
|
|
17
|
+
* @example Create a template and a registry
|
|
17
18
|
* ```ts
|
|
18
|
-
* import { createTemplate } from '@
|
|
19
|
+
* import { createTemplate, createTemplateManager } from '@orkestrel/template'
|
|
19
20
|
*
|
|
20
21
|
* const greeting = createTemplate({ name: 'greeting', content: 'Hi {{name}}' })
|
|
21
22
|
* greeting.fill({ name: 'Ada' }) // 'Hi Ada'
|
|
23
|
+
*
|
|
24
|
+
* const templates = createTemplateManager({
|
|
25
|
+
* templates: [
|
|
26
|
+
* { id: 'greeting', name: 'greeting', content: 'Hi {{name}}', category: 'mail' },
|
|
27
|
+
* { id: 'farewell', name: 'farewell', content: 'Bye {{name}}', category: 'mail' },
|
|
28
|
+
* { id: 'alert', name: 'alert', content: 'Alert: {{reason}}', category: 'ops' },
|
|
29
|
+
* ],
|
|
30
|
+
* })
|
|
31
|
+
* templates.fill('greeting', { name: 'Ada' }) // 'Hi Ada'
|
|
32
|
+
* templates.find({ category: 'mail' }).map((one) => one.id) // ['greeting', 'farewell']
|
|
33
|
+
* templates.has('alert') // true
|
|
34
|
+
* templates.has('missing') // false
|
|
22
35
|
* ```
|
|
23
36
|
*/
|
|
24
37
|
export declare function createTemplate(options: TemplateOptions): TemplateInterface;
|
|
25
38
|
|
|
26
39
|
/**
|
|
27
|
-
* Creates a
|
|
40
|
+
* Creates a working {@link TemplateManagerInterface}, optionally seeded with
|
|
41
|
+
* the templates the options carry, backed by the `TemplateManager` class.
|
|
28
42
|
*
|
|
29
43
|
* @param options - Optional initial `templates` seed collection and
|
|
30
44
|
* manager-wide `missing` / `locale` fill defaults, emitter `on` hooks, and
|
|
@@ -44,10 +58,16 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
44
58
|
*/
|
|
45
59
|
export declare function createTemplateManager(options?: TemplateManagerOptions): TemplateManagerInterface;
|
|
46
60
|
|
|
47
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Holds `'en-US'`, the default `locale` for `Template#fill` /
|
|
63
|
+
* `TemplateManager#fill` when unspecified.
|
|
64
|
+
*/
|
|
48
65
|
export declare const DEFAULT_LOCALE = "en-US";
|
|
49
66
|
|
|
50
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Holds `'error'`, the default `missing` policy for `Template#fill` /
|
|
69
|
+
* `TemplateManager#fill` when unspecified.
|
|
70
|
+
*/
|
|
51
71
|
export declare const DEFAULT_MISSING_POLICY: MissingPolicy;
|
|
52
72
|
|
|
53
73
|
/**
|
|
@@ -55,14 +75,14 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
55
75
|
* `Template#fill` and `Template#validate`.
|
|
56
76
|
*
|
|
57
77
|
* @remarks
|
|
58
|
-
* Global-flagged, two-alternative pattern: a match of the
|
|
78
|
+
* Global-flagged, two-alternative pattern: a match of the first alternative
|
|
59
79
|
* (`\{{` — a literal backslash followed by `{{`) means "emit a literal
|
|
60
80
|
* `{{`" — the escape hatch for content that must show `{{` without
|
|
61
81
|
* triggering substitution. A match that instead populates capture group 1
|
|
62
82
|
* (`\{{([^{}]+?)\}\}`) means "substitute the named token" — group 1 is the
|
|
63
|
-
*
|
|
83
|
+
* untrimmed token text between the braces; every call site trims it
|
|
64
84
|
* (`token.trim()`) before using it as a lookup name, so `'{{ name }}'` still
|
|
65
|
-
* resolves `'name'`. The pattern
|
|
85
|
+
* resolves `'name'`. The pattern deliberately does not wrap the token in
|
|
66
86
|
* `\s*` — an unclosed `'{{' + ' '.repeat(n)` with no closing `}}` would
|
|
67
87
|
* otherwise force the regex engine into catastrophic backtracking over the
|
|
68
88
|
* whitespace run (O(n^2)); trimming after the match keeps the same
|
|
@@ -81,13 +101,13 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
81
101
|
* never re-scanned. Each token resolves through `resolveToken`, the one rule
|
|
82
102
|
* `Template#validate` also applies: the matching declared
|
|
83
103
|
* {@link TemplatePlaceholder} (exact `name`) supplies its `path` (falling
|
|
84
|
-
* back to the token split on `.`);
|
|
104
|
+
* back to the token split on `.`); any path segment in `UNSAFE_FIELD_SEGMENTS`
|
|
85
105
|
* makes the token unresolved without ever calling `resolveField` (a
|
|
86
106
|
* prototype-pollution guard). A resolved value formats through `formatValue`; an
|
|
87
107
|
* unresolved value falls back to the placeholder's `fallback` when declared;
|
|
88
108
|
* otherwise `options.missing` governs — `'literal'` re-emits the original
|
|
89
109
|
* `{{name}}` text, `'empty'` emits `''`, and `'error'` emits `''` for every
|
|
90
|
-
* token but collects
|
|
110
|
+
* token but collects every unresolved required token (an undeclared token, or
|
|
91
111
|
* a declared token with `required !== false`) and throws one
|
|
92
112
|
* {@link TemplateError} coded `MISSING` listing them all, in first-appearance
|
|
93
113
|
* order, once the scan completes. An escaped `\{{` emits a literal `{{`.
|
|
@@ -199,9 +219,9 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
199
219
|
* @remarks
|
|
200
220
|
* A prototype-pollution guard shared by `fillTemplate` and `Template#validate`
|
|
201
221
|
* so the two stay in lockstep: `path` normalizes to a segment array (a bare
|
|
202
|
-
* string `path` becomes a single-segment array); if
|
|
222
|
+
* string `path` becomes a single-segment array); if any segment appears in
|
|
203
223
|
* `UNSAFE_FIELD_SEGMENTS` (`'__proto__'`, `'constructor'`, `'prototype'`), the
|
|
204
|
-
* lookup is refused and `undefined` is returned
|
|
224
|
+
* lookup is refused and `undefined` is returned without ever calling
|
|
205
225
|
* `resolveField` — a path like `['__proto__', 'polluted']` can never reach
|
|
206
226
|
* the record's actual prototype chain through this function. Every other
|
|
207
227
|
* path resolves through `@orkestrel/contract`'s `resolveField`.
|
|
@@ -254,7 +274,7 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
254
274
|
|
|
255
275
|
/**
|
|
256
276
|
* Represents a named, versionable template — `{{name}}` tokens in `content`,
|
|
257
|
-
* filled against a values record.
|
|
277
|
+
* filled against a values record — implementing `TemplateInterface` exactly.
|
|
258
278
|
*
|
|
259
279
|
* @remarks
|
|
260
280
|
* `missing` / `locale` seed this instance's default {@link TemplateFillOptions},
|
|
@@ -321,7 +341,7 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
321
341
|
* token rule `fill` also applies — a declared {@link TemplatePlaceholder}
|
|
322
342
|
* sharing its `name` supplies `path` (falling back to the token split on
|
|
323
343
|
* `.`), and the value resolves through `resolveSafeField`. The token is `missing`
|
|
324
|
-
* only when the value is unresolved
|
|
344
|
+
* only when the value is unresolved, no `fallback` is declared, and the
|
|
325
345
|
* placeholder is required (`required !== false`, including undeclared
|
|
326
346
|
* tokens). `extra` lists every `values` key with no declared placeholder.
|
|
327
347
|
*
|
|
@@ -381,15 +401,16 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
381
401
|
}
|
|
382
402
|
|
|
383
403
|
/**
|
|
384
|
-
* Represents an error thrown by the template layer
|
|
404
|
+
* Represents an error thrown by the template layer — a machine-readable
|
|
405
|
+
* {@link TemplateErrorCode} and an optional `context` record naming the
|
|
406
|
+
* offending id or placeholder name.
|
|
385
407
|
*
|
|
386
408
|
* @remarks
|
|
387
409
|
* Thrown for: a required placeholder staying unresolved under the `error`
|
|
388
410
|
* {@link MissingPolicy} (`MISSING`), an unknown template id
|
|
389
411
|
* (`NOTFOUND`), `createTemplate` handed invalid data (`INVALID`), and
|
|
390
412
|
* `TemplateManagerInterface#register` handed an id already present without
|
|
391
|
-
* `options.replace` (`CONFLICT`).
|
|
392
|
-
* offending id / name.
|
|
413
|
+
* `options.replace` (`CONFLICT`).
|
|
393
414
|
*/
|
|
394
415
|
export declare class TemplateError extends Error {
|
|
395
416
|
readonly code: TemplateErrorCode;
|
|
@@ -434,17 +455,13 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
434
455
|
export declare type TemplateFillValues = Readonly<Record<string, unknown>>;
|
|
435
456
|
|
|
436
457
|
/**
|
|
437
|
-
* Declares the template contract —
|
|
458
|
+
* Declares the template contract a consumer holds — the readonly template
|
|
459
|
+
* record and the `definition`, `fill`, `validate`, and `parameters` calls
|
|
460
|
+
* over it.
|
|
438
461
|
*
|
|
439
462
|
* @remarks
|
|
440
|
-
* `
|
|
441
|
-
*
|
|
442
|
-
* honoring `options.missing` for unresolved required placeholders. `validate`
|
|
443
|
-
* reports which required placeholders are unresolved (`missing`) and which
|
|
444
|
-
* supplied `values` keys are unused (`extra`) without producing output.
|
|
445
|
-
* `parameters` projects this template's placeholders to the open
|
|
446
|
-
* tool-parameters record shape (`schemaToParameters`'s return type from
|
|
447
|
-
* `@orkestrel/contract`).
|
|
463
|
+
* `Template` implements this contract exactly, so the class exposes these
|
|
464
|
+
* members and no other public behavior.
|
|
448
465
|
*/
|
|
449
466
|
export declare interface TemplateInterface {
|
|
450
467
|
readonly id: string;
|
|
@@ -455,28 +472,53 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
455
472
|
readonly description?: string;
|
|
456
473
|
readonly category?: string;
|
|
457
474
|
readonly tags?: readonly string[];
|
|
475
|
+
/** Returns the plain, JSON-serializable template data. */
|
|
458
476
|
definition(): TemplateDefinition;
|
|
477
|
+
/**
|
|
478
|
+
* Substitutes every `{{name}}` token in `content` against `values`.
|
|
479
|
+
*
|
|
480
|
+
* @remarks
|
|
481
|
+
* `options.missing` governs an unresolved required placeholder, defaulting
|
|
482
|
+
* to the instance's own {@link MissingPolicy}.
|
|
483
|
+
*/
|
|
459
484
|
fill(values?: TemplateFillValues, options?: TemplateFillOptions): string;
|
|
485
|
+
/**
|
|
486
|
+
* Reports which required placeholders would stay unresolved, and which
|
|
487
|
+
* `values` keys go unused.
|
|
488
|
+
*
|
|
489
|
+
* @remarks
|
|
490
|
+
* The unresolved names arrive as `missing` and the unused keys as `extra`,
|
|
491
|
+
* and neither reading produces output.
|
|
492
|
+
*/
|
|
460
493
|
validate(values?: TemplateFillValues): TemplateValidationResult;
|
|
494
|
+
/**
|
|
495
|
+
* Projects the declared placeholders to the open tool-parameters record
|
|
496
|
+
* shape.
|
|
497
|
+
*
|
|
498
|
+
* @remarks
|
|
499
|
+
* The shape is `schemaToParameters`'s return type from
|
|
500
|
+
* `@orkestrel/contract`.
|
|
501
|
+
*/
|
|
461
502
|
parameters(): Readonly<Record<string, unknown>> | undefined;
|
|
462
503
|
}
|
|
463
504
|
|
|
464
505
|
/**
|
|
465
|
-
* Represents the template registry — a self-owning, id-keyed record-holder for
|
|
466
|
-
* {@link TemplateInterface} instances a consumer registers, looks up,
|
|
467
|
-
* and validates by id
|
|
468
|
-
*
|
|
506
|
+
* Represents the template registry — a self-owning, id-keyed record-holder for
|
|
507
|
+
* the {@link TemplateInterface} instances a consumer registers, looks up,
|
|
508
|
+
* fills, and validates by id — implementing `TemplateManagerInterface`
|
|
509
|
+
* exactly.
|
|
469
510
|
*
|
|
470
511
|
* @remarks
|
|
471
|
-
*
|
|
512
|
+
* Singular and plural accessors, the batch `remove` overloads, and ownership
|
|
513
|
+
* of the emitter all sit here. `register` accepts either a constructed {@link TemplateInterface} (kept
|
|
472
514
|
* as-is, including its own `missing` / `locale` defaults) or a plain
|
|
473
515
|
* {@link TemplateOptions} bag — constructed into a `Template` with this
|
|
474
516
|
* manager's `missing` / `locale` defaults applied wherever the bag omits
|
|
475
517
|
* them. A duplicate `id` throws a {@link TemplateError} coded `CONFLICT`
|
|
476
518
|
* unless `options.replace` is `true`, in which case the existing entry is
|
|
477
|
-
* overwritten. `options.templates`
|
|
478
|
-
*
|
|
479
|
-
*
|
|
519
|
+
* overwritten. `options.templates` seeds the registry at construction without
|
|
520
|
+
* emitting `register` — only calls to `register` after construction emit.
|
|
521
|
+
* The batch `remove(ids)` form removes every present id and returns
|
|
480
522
|
* `true` only when every listed id was present.
|
|
481
523
|
*
|
|
482
524
|
* @example
|
|
@@ -524,8 +566,8 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
524
566
|
*/
|
|
525
567
|
templates(): readonly TemplateInterface[];
|
|
526
568
|
/**
|
|
527
|
-
* Filters registered templates by name
|
|
528
|
-
* field must match
|
|
569
|
+
* Filters registered templates by `name`, `category`, and `tag` — every
|
|
570
|
+
* supplied field must match.
|
|
529
571
|
*
|
|
530
572
|
* @param query - The {@link TemplateQuery} to filter by; omit for every registered template
|
|
531
573
|
* @returns The matching templates
|
|
@@ -619,36 +661,67 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
619
661
|
};
|
|
620
662
|
|
|
621
663
|
/**
|
|
622
|
-
* Declares the
|
|
623
|
-
* singular
|
|
664
|
+
* Declares the registry contract a consumer holds — a self-owning, id-keyed
|
|
665
|
+
* record-holder with singular and plural accessors over the templates it
|
|
666
|
+
* registers.
|
|
624
667
|
*
|
|
625
668
|
* @remarks
|
|
626
|
-
* `
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
* each needs a template to proceed. `remove()` removes every registered
|
|
632
|
-
* template. `remove`'s batch form removes every present id and reports
|
|
633
|
-
* `true` only when all listed ids were present. `destroy` tears the registry
|
|
634
|
-
* down — it drops every registered template, destroys the owned emitter, emits
|
|
635
|
-
* nothing, and is idempotent.
|
|
669
|
+
* `TemplateManager` implements this contract exactly, so the class exposes
|
|
670
|
+
* these members and no other public behavior. The by-id calls split on what
|
|
671
|
+
* an unknown id means to each: `template` reports its absence as `undefined`,
|
|
672
|
+
* while `fill`, `validate`, and `parameters` throw a {@link TemplateError}
|
|
673
|
+
* coded `NOTFOUND`, because each needs a template to proceed.
|
|
636
674
|
*/
|
|
637
675
|
export declare interface TemplateManagerInterface {
|
|
638
676
|
readonly emitter: EmitterInterface<TemplateManagerEventMap>;
|
|
639
677
|
readonly count: number;
|
|
678
|
+
/**
|
|
679
|
+
* Registers one template, or overwrites the entry sharing its id when
|
|
680
|
+
* `options.replace` is `true`, and emits `register`.
|
|
681
|
+
*
|
|
682
|
+
* @remarks
|
|
683
|
+
* The template arrives as a constructed {@link TemplateInterface} or as a
|
|
684
|
+
* plain {@link TemplateOptions} bag, which is constructed internally. A
|
|
685
|
+
* duplicate id without `options.replace` throws a {@link TemplateError}
|
|
686
|
+
* coded `CONFLICT`.
|
|
687
|
+
*/
|
|
640
688
|
register(template: TemplateInterface | TemplateOptions, options?: TemplateRegisterOptions): TemplateInterface;
|
|
689
|
+
/** Returns one registered template by id, or `undefined` when the id is unregistered. */
|
|
641
690
|
template(id: string): TemplateInterface | undefined;
|
|
691
|
+
/** Lists every registered template. */
|
|
642
692
|
templates(): readonly TemplateInterface[];
|
|
693
|
+
/** Filters registered templates by `name`, `category`, and `tag` — every supplied field must match. */
|
|
643
694
|
find(query?: TemplateQuery): readonly TemplateInterface[];
|
|
695
|
+
/** Reports whether a template with the given id is registered. */
|
|
644
696
|
has(id: string): boolean;
|
|
697
|
+
/**
|
|
698
|
+
* Removes the listed templates by id, one template by id, or every
|
|
699
|
+
* registered template, emitting `remove` once per removed template.
|
|
700
|
+
*
|
|
701
|
+
* @remarks
|
|
702
|
+
* The batch form removes every present id and reports `true` only when
|
|
703
|
+
* every listed id was present, so one absent id turns the answer `false`
|
|
704
|
+
* while the present ids still remove. The no-argument form removes every
|
|
705
|
+
* registered template and reports nothing.
|
|
706
|
+
*/
|
|
645
707
|
remove(ids: readonly string[]): boolean;
|
|
646
708
|
remove(id: string): boolean;
|
|
647
709
|
remove(): void;
|
|
710
|
+
/** Removes every registered template, emitting `clear`. */
|
|
648
711
|
clear(): void;
|
|
712
|
+
/**
|
|
713
|
+
* Tears the registry down: drops every registered template and destroys the
|
|
714
|
+
* owned emitter. Idempotent.
|
|
715
|
+
*
|
|
716
|
+
* @remarks
|
|
717
|
+
* Teardown emits nothing, because the emitter is being released.
|
|
718
|
+
*/
|
|
649
719
|
destroy(): void;
|
|
720
|
+
/** Fills a registered template by id. */
|
|
650
721
|
fill(id: string, values?: TemplateFillValues, options?: TemplateFillOptions): string;
|
|
722
|
+
/** Validates values against a registered template by id. */
|
|
651
723
|
validate(id: string, values?: TemplateFillValues): TemplateValidationResult;
|
|
724
|
+
/** Projects a registered template's parameters by id. */
|
|
652
725
|
parameters(id: string): Readonly<Record<string, unknown>> | undefined;
|
|
653
726
|
}
|
|
654
727
|
|
|
@@ -759,9 +832,9 @@ import { FieldPath } from '@orkestrel/contract';
|
|
|
759
832
|
}
|
|
760
833
|
|
|
761
834
|
/**
|
|
762
|
-
* Lists the prototype-pollution-unsafe field-path segments
|
|
763
|
-
*
|
|
764
|
-
* unresolved.
|
|
835
|
+
* Lists the prototype-pollution-unsafe field-path segments `'__proto__'`,
|
|
836
|
+
* `'constructor'`, and `'prototype'` — a fill lookup refuses to resolve a path
|
|
837
|
+
* containing one of them, treating the placeholder as unresolved.
|
|
765
838
|
*/
|
|
766
839
|
export declare const UNSAFE_FIELD_SEGMENTS: readonly string[];
|
|
767
840
|
|