Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/primitives/templates.
# Tasks: enterprise-templates
## 1. Scaffold
- [x] 1.1 Create `packages/universal/templates/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton
- [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul
- [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed-imports, tree-shake
- [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml`
## 2. Error types
- [x] 2.1 Implement `src/errors/template-error.ts` — `TemplateError` base extends `SuaveplanError`; `defineCodes` for `TEMPLATE.PARSE_ERROR`, `TEMPLATE.MISSING_VARIABLE`, `TEMPLATE.PLUGIN_CONFLICT`, `TEMPLATE.RENDER_ERROR`
- [x] 2.2 Implement `src/errors/plugin-error.ts` — `PluginError` extends `TemplateError` with `pluginName` and `match` fields; code `TEMPLATE.PLUGIN_ERROR`
- [x] 2.3 Implement `src/errors/max-depth-error.ts` — `MaxDepthError` extends `TemplateError` with `depth` and `maxDepth` fields; code `TEMPLATE.MAX_DEPTH`
- [x] 2.4 Implement `src/errors/error-codes.ts` — single `defineCodes` call registering all `TEMPLATE.*` codes; auto-registered via side-effect import in `src/index.ts`
- [x] 2.5 Tests: error construction, code narrowing, field access, `instanceof` hierarchy checks
- [x] 2.6 Co-located `.md` for each error file
## 3. Types and Zod schemas
- [x] 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>`
- [x] 3.2 Implement `src/types/schemas.ts` — `TemplateEngineOptionsSchema`, `ParseOptionsSchema`, `PluginPrioritySchema` using Zod; all schemas exported for consumer validation
- [x] 3.3 Tests: schema parse/safeParse on valid and invalid inputs; type inference smoke tests
- [x] 3.4 Co-located `.md` for types and schemas files
## 4. ParseState
- [x] 4.1 Implement `src/engine/parse-state.ts`:
- `createParseState(maxDepth?)` → `ParseState`
- `cloneParseState(state)` → `ParseState` — deep-clones counters, cache, custom maps
- `clearCounters(state)` → `void`
- `getCounter(state, name)` → `number`
- `resetCounter(state, name)` → `void`
- `incrementDepth(state)` → `void` — internal helper used by engine
- [x] 4.2 Unit tests: initial state shape, counter CRUD, clone independence, depth tracking
- [x] 4.3 Co-located `src/engine/parse-state.md`
## 5. TemplateEngine core
- [x] 5.1 Implement `src/engine/template-engine.ts` — `TemplateEngine<TContext>` class:
- `constructor(options?: TemplateEngineOptions)` — validates options via `TemplateEngineOptionsSchema`
- `.use(plugin)` → `this` — registers plugin; detects name conflicts; re-sorts by priority descending; emits `template.plugin.calls.total` instrument setup
- `.usePreset(preset)` → `this`
- `.remove(name)` → `this`
- `.getPlugins()` → `readonly Plugin<TContext>[]`
- `.parse(template, context, options?)` → `string` — initializes plugins, calls `parseInternal`, runs cleanup; records `template.render.total` and `template.render.duration_ms`; wraps errors in `TemplateError`; honours `throwOnError` option
- `.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 with `SKIP_PLUGIN` handling
- Internal `applyPlugin` / `applyPluginAsync` — collect all matches first, apply replacements in reverse-index order
- [x] 5.2 Unit tests: plugin registration order, priority sorting, conflict detection, `throwOnError: false` path, `onError` callback, `compile` reuse, depth guard, sync async parity
- [x] 5.3 Co-located `src/engine/template-engine.md`
## 6. Variable resolution plugins
- [x] 6.1 Implement `src/plugins/variables/dot-notation-plugin.ts` — `createDotNotationPlugin()` → `Plugin`:
- Pattern: `{path.to.value}` using `getNestedValue`
- Priority: `PluginPriority.LOW`
- Returns `SKIP_PLUGIN` when path not found (does not throw)
- [x] 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_PLUGIN` when array or index not found
- [x] 6.3 Unit tests: happy path, missing path (SKIP_PLUGIN), nested arrays, type coercion to string
- [x] 6.4 Co-located `.md` for each plugin file
## 7. Transform plugins
- [x] 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
- [x] 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
- [x] 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
- [x] 7.4 Unit tests: all case variants, regex replace + callback, split happy path, out-of-range index
- [x] 7.5 Co-located `.md` for each plugin file
## 8. Dynamic plugins
- [x] 8.1 Implement `src/plugins/dynamic/counter-plugin.ts` — `createCounterPlugin()` → `Plugin`:
- Pattern: `{_counter:name:start:pad}` — `start` default 1, `pad` default 0
- Priority: `PluginPriority.HIGH`
- Counter stored in `ParseState.counters`; increments each match; zero-pads to `pad` width
- [x] 8.2 Implement `src/plugins/dynamic/datetime-plugin.ts` — `createDateTimePlugin(opts?)` → `Plugin`:
- `opts?: { clock?: Clock; defaultPattern?: string }`
- Pattern: `{_datetime:pattern}` — uses `formatDate`; `pattern` defaults to `YYYY-MM-DD` when not supplied
- Priority: `PluginPriority.HIGH`
- `Clock` from `@suaveplan/types`; falls back to `Date.now()` when no clock injected
- [x] 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`
- `Rng` from `@suaveplan/types`; falls back to `crypto.randomUUID` / `Math.random` when not injected
- [x] 8.4 Unit tests: counter increment and padding, counter state isolation per `ParseState`, datetime with `FakeClock`, uuid determinism with fake `Rng`, nanoid length
- [x] 8.5 Co-located `.md` for each plugin file
## 9. Control flow plugins
- [x] 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`
- [x] 9.2 Implement `src/plugins/control/loop-plugin.ts` — `createLoopPlugin()` → `Plugin`:
- Pattern: `{loop:items}...{/loop}` — `items` resolved 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
- [x] 9.3 Unit tests: conditional truthy/falsy, nested conditionals, loop over array, loop with empty array, loop with non-array, loop inner variable access
- [x] 9.4 Co-located `.md` for each plugin file
## 10. Utilities
- [x] 10.1 Implement `src/utils/escape.ts`:
- `escapeRegex(str)` → `string`
- `isEscaped(template, index)` → `boolean`
- `unescapeTemplate(str)` → `string`
- [x] 10.2 Implement `src/utils/format.ts`:
- `formatDate(date, pattern)` → `string` — tokens: `YYYY MM DD HH mm ss SSS`
- `generateUuid(rng?)` → `string` — uses injected `Rng` or `crypto.randomUUID`
- `generateNanoid(length?, rng?)` → `string` — URL-safe alphabet; uses injected `Rng` or `Math.random`
- `pad(value, length, char?, left?)` → `string`
- [x] 10.3 Implement `src/utils/get-nested-value.ts`:
- `getNestedValue(obj, path)` → `unknown`
- `hasNestedPath(obj, path)` → `boolean`
- `setNestedValue(obj, path, value)` → `void`
- [x] 10.4 Unit tests: escape round-trips, formatDate token coverage, UUID format validation, nanoid length/alphabet, nested get/has/set CRUD
- [x] 10.5 Co-located `.md` for each utility file
## 11. Presets
- [x] 11.1 Implement `src/presets/minimal.ts` — `minimalPreset()` → `PluginPreset`: dot-notation + array-index
- [x] 11.2 Implement `src/presets/standard.ts` — `standardPreset()` → `PluginPreset`: dot-notation, array-index, case-transform, counter, datetime, uuid
- [x] 11.3 Implement `src/presets/full.ts` — `fullPreset()` → `PluginPreset`: standard + regex + string-split + conditional; `fullPresetWithLoops()` → `PluginPreset`: full + loop
- [x] 11.4 Unit tests: each preset registers expected plugin names in correct priority order; preset plugins are independent instances (not shared state)
- [x] 11.5 Co-located `.md` for each preset file
## 12. OTel instrumentation
- [x] 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/api` meter; no-op when no exporter registered
- [x] 12.2 Wire instrumentation into `TemplateEngine.parse()`, `parseAsync()`, and `applyPlugin()` / `applyPluginAsync()`
- [x] 12.3 Tests: no-op without exporter (metrics silently swallowed); instrument names and attribute keys match spec
- [x] 12.4 Co-located `src/instrumentation/metrics.md`
## 13. Property-based tests and benchmarks
- [x] 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)
- [x] 13.2 `src/__tests__/property/counter-monotone.property.test.ts` — fast-check: counter value never decreases across successive `.parse()` calls within a `ParseState`
- [x] 13.3 `src/__tests__/property/nested-value.property.test.ts` — fast-check: `getNestedValue` after `setNestedValue` round-trips for safe dot-paths
- [x] 13.4 `src/__benchmarks__/render.bench.ts` — 100-variable dot-notation template, 10 000 iterations (mitata)
- [x] 13.5 `src/__benchmarks__/plugin-overhead.bench.ts` — single no-op plugin on 1-token template, 100 000 iterations (mitata)
- [x] 13.6 `src/__benchmarks__/format.bench.ts` — `formatDate` and `getNestedValue` 5-level deep, 100 000 iterations each (mitata)
- [x] 13.7 Publish baseline benchmark numbers in `README.md`
## 14. Docs
- [x] 14.1 Co-located `.md` for every implementation file (≥200 words, both simple and advanced examples, full API reference section)
- [x] 14.2 Rewrite `README.md` with: public API table, preset comparison table, plugin authoring guide, OTel attribute reference, injectable clock/RNG guide, benchmark numbers
- [x] 14.3 `bun run docs:check --filter=@suaveplan/templates` ≥ 90%
## 15. Verify and publish
- [x] 15.1 `bunx turbo lint typecheck test --filter=@suaveplan/templates` green
- [x] 15.2 `bunx turbo test:coverage --filter=@suaveplan/templates` at 100/100/100/100
- [x] 15.3 `bunx turbo build --filter=@suaveplan/templates` green
- [x] 15.4 `bun scripts/publish.ts --package @suaveplan/templates` to Verdaccio at 0.0.0
- [x] 15.5 Fold spec delta into `openspec/specs/templates/spec.md`
- [x] 15.6 Archive OpenSpec change to `openspec/archive/2026-04-19-enterprise-templates/`