12 KiB
12 KiB
Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/primitives/templates.
Tasks: enterprise-templates
1. Scaffold
- 1.1 Create
packages/universal/templates/withpackage.json,tsconfig.json,tsconfig.lib.json,vite.config.ts,vitest.config.ts,README.mdskeleton - 1.2 Register coverage thresholds at 100/100/100/100 in
vitest.config.ts; provider = istanbul - 1.3 Add cross-cutting guard tests in
tests/: dep-graph, no-disallowed-imports, tree-shake - 1.4 Add package to root
CLAUDE.md,README.md, andSTRUCTURE.yml
2. Error types
- 2.1 Implement
src/errors/template-error.ts—TemplateErrorbase extendsSuaveplanError;defineCodesforTEMPLATE.PARSE_ERROR,TEMPLATE.MISSING_VARIABLE,TEMPLATE.PLUGIN_CONFLICT,TEMPLATE.RENDER_ERROR - 2.2 Implement
src/errors/plugin-error.ts—PluginErrorextendsTemplateErrorwithpluginNameandmatchfields; codeTEMPLATE.PLUGIN_ERROR - 2.3 Implement
src/errors/max-depth-error.ts—MaxDepthErrorextendsTemplateErrorwithdepthandmaxDepthfields; codeTEMPLATE.MAX_DEPTH - 2.4 Implement
src/errors/error-codes.ts— singledefineCodescall registering allTEMPLATE.*codes; auto-registered via side-effect import insrc/index.ts - 2.5 Tests: error construction, code narrowing, field access,
instanceofhierarchy checks - 2.6 Co-located
.mdfor each error file
3. Types and Zod schemas
- 3.1 Implement
src/types/template.types.ts— all type and enum exports:TemplateContext,PluginPriority,ParseState,PluginMatch,PluginResult,SKIP_PLUGIN,Plugin<TContext>,PluginPreset<TContext>,TemplateEngineOptions,ParseOptions,CompiledTemplate<TContext> - 3.2 Implement
src/types/schemas.ts—TemplateEngineOptionsSchema,ParseOptionsSchema,PluginPrioritySchemausing Zod; all schemas exported for consumer validation - 3.3 Tests: schema parse/safeParse on valid and invalid inputs; type inference smoke tests
- 3.4 Co-located
.mdfor types and schemas files
4. ParseState
- 4.1 Implement
src/engine/parse-state.ts:createParseState(maxDepth?)→ParseStatecloneParseState(state)→ParseState— deep-clones counters, cache, custom mapsclearCounters(state)→voidgetCounter(state, name)→numberresetCounter(state, name)→voidincrementDepth(state)→void— internal helper used by engine
- 4.2 Unit tests: initial state shape, counter CRUD, clone independence, depth tracking
- 4.3 Co-located
src/engine/parse-state.md
5. TemplateEngine core
- 5.1 Implement
src/engine/template-engine.ts—TemplateEngine<TContext>class:constructor(options?: TemplateEngineOptions)— validates options viaTemplateEngineOptionsSchema.use(plugin)→this— registers plugin; detects name conflicts; re-sorts by priority descending; emitstemplate.plugin.calls.totalinstrument setup.usePreset(preset)→this.remove(name)→this.getPlugins()→readonly Plugin<TContext>[].parse(template, context, options?)→string— initializes plugins, callsparseInternal, runs cleanup; recordstemplate.render.totalandtemplate.render.duration_ms; wraps errors inTemplateError; honoursthrowOnErroroption.parseAsync(template, context, options?)→Promise<string>— async variant; same instrumentation.compile(template)→CompiledTemplate<TContext>— returns{ source, parse, parseAsync }- Internal
parseInternal/parseInternalAsync— depth-guard, plugin application loop withSKIP_PLUGINhandling - Internal
applyPlugin/applyPluginAsync— collect all matches first, apply replacements in reverse-index order
- 5.2 Unit tests: plugin registration order, priority sorting, conflict detection,
throwOnError: falsepath,onErrorcallback,compilereuse, depth guard, sync async parity - 5.3 Co-located
src/engine/template-engine.md
6. Variable resolution plugins
- 6.1 Implement
src/plugins/variables/dot-notation-plugin.ts—createDotNotationPlugin()→Plugin:- Pattern:
{path.to.value}usinggetNestedValue - Priority:
PluginPriority.LOW - Returns
SKIP_PLUGINwhen path not found (does not throw)
- Pattern:
- 6.2 Implement
src/plugins/variables/array-index-plugin.ts—createArrayIndexPlugin()→Plugin:- Pattern:
{name[index]}— zero-based array element lookup - Priority:
PluginPriority.LOW - Returns
SKIP_PLUGINwhen array or index not found
- Pattern:
- 6.3 Unit tests: happy path, missing path (SKIP_PLUGIN), nested arrays, type coercion to string
- 6.4 Co-located
.mdfor each plugin file
7. Transform plugins
- 7.1 Implement
src/plugins/transform/case-transform-plugin.ts—createCaseTransformPlugin()→Plugin:- Pattern:
{value|upper},{value|lower},{value|title},{value|camel},{value|snake},{value|kebab} - Priority:
PluginPriority.MEDIUM - Value is resolved from context via dot-notation before transform is applied
- Pattern:
- 7.2 Implement
src/plugins/transform/regex-plugin.ts—createRegexPlugin(patterns)→Plugin:- Accepts array of
{ pattern: RegExp; replacement: string | ((match: string) => string) }entries - Priority:
PluginPriority.MEDIUM - Applied in order; caller is responsible for pattern safety
- Accepts array of
- 7.3 Implement
src/plugins/transform/string-split-plugin.ts—createStringSplitPlugin()→Plugin:- Pattern:
{value|split:sep:index} - Priority:
PluginPriority.MEDIUM - Returns empty string when index out of range
- Pattern:
- 7.4 Unit tests: all case variants, regex replace + callback, split happy path, out-of-range index
- 7.5 Co-located
.mdfor each plugin file
8. Dynamic plugins
- 8.1 Implement
src/plugins/dynamic/counter-plugin.ts—createCounterPlugin()→Plugin:- Pattern:
{_counter:name:start:pad}—startdefault 1,paddefault 0 - Priority:
PluginPriority.HIGH - Counter stored in
ParseState.counters; increments each match; zero-pads topadwidth
- Pattern:
- 8.2 Implement
src/plugins/dynamic/datetime-plugin.ts—createDateTimePlugin(opts?)→Plugin:opts?: { clock?: Clock; defaultPattern?: string }- Pattern:
{_datetime:pattern}— usesformatDate;patterndefaults toYYYY-MM-DDwhen not supplied - Priority:
PluginPriority.HIGH Clockfrom@suaveplan/types; falls back toDate.now()when no clock injected
- 8.3 Implement
src/plugins/dynamic/uuid-plugin.ts—createUuidPlugin(opts?)→Plugin:opts?: { rng?: Rng }- Patterns:
{_uuid}→ UUID v4;{_nanoid:length}→ nano ID of given length - Priority:
PluginPriority.HIGH Rngfrom@suaveplan/types; falls back tocrypto.randomUUID/Math.randomwhen not injected
- 8.4 Unit tests: counter increment and padding, counter state isolation per
ParseState, datetime withFakeClock, uuid determinism with fakeRng, nanoid length - 8.5 Co-located
.mdfor each plugin file
9. Control flow plugins
- 9.1 Implement
src/plugins/control/conditional-plugin.ts—createConditionalPlugin()→Plugin:- Pattern:
{condition}...{/condition}— condition key resolved from context; truthy keeps block, falsy removes it - Priority:
PluginPriority.CRITICAL - Supports dot-notation conditions:
{user.isAdmin}...{/user.isAdmin} - Nested conditionals supported up to
ParseState.maxDepth
- Pattern:
- 9.2 Implement
src/plugins/control/loop-plugin.ts—createLoopPlugin()→Plugin:- Pattern:
{loop:items}...{/loop}—itemsresolved to array from context; block rendered once per element with element bound as{item} - Priority:
PluginPriority.CRITICAL - Non-array or missing values render as empty string
- Pattern:
- 9.3 Unit tests: conditional truthy/falsy, nested conditionals, loop over array, loop with empty array, loop with non-array, loop inner variable access
- 9.4 Co-located
.mdfor each plugin file
10. Utilities
- 10.1 Implement
src/utils/escape.ts:escapeRegex(str)→stringisEscaped(template, index)→booleanunescapeTemplate(str)→string
- 10.2 Implement
src/utils/format.ts:formatDate(date, pattern)→string— tokens:YYYY MM DD HH mm ss SSSgenerateUuid(rng?)→string— uses injectedRngorcrypto.randomUUIDgenerateNanoid(length?, rng?)→string— URL-safe alphabet; uses injectedRngorMath.randompad(value, length, char?, left?)→string
- 10.3 Implement
src/utils/get-nested-value.ts:getNestedValue(obj, path)→unknownhasNestedPath(obj, path)→booleansetNestedValue(obj, path, value)→void
- 10.4 Unit tests: escape round-trips, formatDate token coverage, UUID format validation, nanoid length/alphabet, nested get/has/set CRUD
- 10.5 Co-located
.mdfor each utility file
11. Presets
- 11.1 Implement
src/presets/minimal.ts—minimalPreset()→PluginPreset: dot-notation + array-index - 11.2 Implement
src/presets/standard.ts—standardPreset()→PluginPreset: dot-notation, array-index, case-transform, counter, datetime, uuid - 11.3 Implement
src/presets/full.ts—fullPreset()→PluginPreset: standard + regex + string-split + conditional;fullPresetWithLoops()→PluginPreset: full + loop - 11.4 Unit tests: each preset registers expected plugin names in correct priority order; preset plugins are independent instances (not shared state)
- 11.5 Co-located
.mdfor each preset file
12. OTel instrumentation
- 12.1 Implement
src/instrumentation/metrics.ts:- Counter
template.render.total— attributes:outcome(success|error) - Histogram
template.render.duration_ms - Counter
template.plugin.calls.total— attributes:plugin_name,outcome(success|skip|error) - All instruments obtained from
@opentelemetry/apimeter; no-op when no exporter registered
- Counter
- 12.2 Wire instrumentation into
TemplateEngine.parse(),parseAsync(), andapplyPlugin()/applyPluginAsync() - 12.3 Tests: no-op without exporter (metrics silently swallowed); instrument names and attribute keys match spec
- 12.4 Co-located
src/instrumentation/metrics.md
13. Property-based tests and benchmarks
- 13.1
src/__tests__/property/idempotent-render.property.test.ts— fast-check: re-parsing rendered output with same context returns same string (dot-notation + standard plugins, no counter/uuid) - 13.2
src/__tests__/property/counter-monotone.property.test.ts— fast-check: counter value never decreases across successive.parse()calls within aParseState - 13.3
src/__tests__/property/nested-value.property.test.ts— fast-check:getNestedValueaftersetNestedValueround-trips for safe dot-paths - 13.4
src/__benchmarks__/render.bench.ts— 100-variable dot-notation template, 10 000 iterations (mitata) - 13.5
src/__benchmarks__/plugin-overhead.bench.ts— single no-op plugin on 1-token template, 100 000 iterations (mitata) - 13.6
src/__benchmarks__/format.bench.ts—formatDateandgetNestedValue5-level deep, 100 000 iterations each (mitata) - 13.7 Publish baseline benchmark numbers in
README.md
14. Docs
- 14.1 Co-located
.mdfor every implementation file (≥200 words, both simple and advanced examples, full API reference section) - 14.2 Rewrite
README.mdwith: public API table, preset comparison table, plugin authoring guide, OTel attribute reference, injectable clock/RNG guide, benchmark numbers - 14.3
bun run docs:check --filter=@suaveplan/templates≥ 90%
15. Verify and publish
- 15.1
bunx turbo lint typecheck test --filter=@suaveplan/templatesgreen - 15.2
bunx turbo test:coverage --filter=@suaveplan/templatesat 100/100/100/100 - 15.3
bunx turbo build --filter=@suaveplan/templatesgreen - 15.4
bun scripts/publish.ts --package @suaveplan/templatesto Verdaccio at 0.0.0 - 15.5 Fold spec delta into
openspec/specs/templates/spec.md - 15.6 Archive OpenSpec change to
openspec/archive/2026-04-19-enterprise-templates/