- Investigate Code-Smells: Review complex methods (like
#parse) for high cyclomatic complexity and potential refactoring.
We should refactor our core "securing" mechanisms to return our throwing Proxies instead of just freezing the object. This ensures that every "Immutable" object in the Tempo ecosystem—from the main Tempo instance to the tiny ResolvedRange objects—will loudly complain if someone tries to mutate them in the REPL.
- Pervasive Hard-Freeze Proxies
This plan refactors the library's immutability system to use throwing Proxies instead of native
Object.freeze. This ensures that all immutable objects provide a consistent "throw-on-mutation" experience, even in non-strict environments like the Node.js REPL. - User Review Required
Important
Transitioning from Object.freeze to Proxies for all immutable objects is a significant architectural shift. While it provides the best possible UX for developers, it may have minor performance implications and identity-check considerations (e.g., proxy === target is false). However, given Tempo's focus on safety and developer experience, this is the recommended path.
- Proposed Changes
- [Component] Library (Utility & Class Decorators)
- [MODIFY] utility.library.ts
- Update
secure()to wrap the final frozen object in aproxify()call. - This ensures that resolving a term (like
t.term.qtr) returns a Proxy that throws on mutation. - [MODIFY] class.library.ts
- Update the
@Immutabledecorator to returnproxify(this)from the constructor. - This ensures that every
Tempoinstance is a Proxy that throws on mutation. - [Component] Library (Proxy System)
- [MODIFY] proxy.library.ts
- Refine error messages to distinguish between "Frozen Objects" (instances) and "Read-Only Delegators" (terms/formats).
- Ensure all mutation traps (including
definePropertyanddeleteProperty) are robustly covered. - Verification Plan
- Manual Verification (REPL)
- Start the REPL:
npm run repl. - Verify the following all throw explicit
TypeErrors:delete t1.termt1.term = {}t1.term.qtr = 1delete t1.term.quarter.month
Today the only reordering supported is the pairwise mdyLayouts swap for locale-based dmy↔mdy flipping.
There is no way for a developer to reorder layouts arbitrarily (e.g. promote wkd above tm, or demote rel entirely).
Proposed: a layoutOrder option that accepts an ordered array of layout names and rebuilds the parse sequence accordingly.
Tempo.init({
layoutOrder: ['wkd', 'dt', 'dtm', 'tmd', 'tm', 'ymd', 'mdy', 'dmy', 'off', 'rel']
})- The internal layout map is already built from
Object.entries()so the infrastructure is ready. - Any name not listed would retain its relative position at the end (non-destructive by default).
- Could be supplied at static-init level or per-instance level (same as
layout/snippet). mdyLayoutsswap would still apply on top, after the custom order is set.
Today every layout pattern is tested against every input string in order, with no early-outs based on the nature of the input. Proposed: a fast upfront classification of the raw input that gates which layout subsets are even tried.
Critical constraint — numeric fall-through must be preserved: The parse pipeline already has two upstream numeric escapes that happen before the layout loop and must never be disrupted:
isIntegerLike(trim)— matches strings ending inn(e.g.1234567890n) → treated as BigInt nanoseconds and returned immediately, bypassing all layouts.type === 'Number'(i.e. the input was supplied as a JSnumber) — goes through the layout loop but is interpreted by the composer as epoch milliseconds if no layout matches.
Any input-class pre-filtering must therefore be applied only after those two escapes have already
passed, and must not gate-out purely-numeric inputs from reaching the layout loop — because a numeric
string like '20260424' is a valid compact date and must be tried against layout patterns first.
The epoch-millisecond fallback is the last resort, not a skip condition.
Example input classes and their natural consequences:
| Input class | Layouts to skip |
|---|---|
| Purely numeric | event, period, wkd, rel — but NOT the layout loop itself |
| Contains only letters | hms, dmy6, mdy6, ymd6, off |
| Contains a colon | Bias time layouts first |
Contains ago/hence |
Jump straight to rel |
| Exactly 6 digits | Only try hms, dmy6, mdy6, ymd6 |
| Exactly 8 digits | Only try compact date layouts |
Benefits:
- Performance: fewer regex executions per parse call, particularly valuable for hot loops (Ticker, bulk formatting).
- Correctness: eliminates the
{hh}partial-match latent trap for letter-containing inputs. - The layout loop remains authoritative — this is transparent pre-filtering, not a replacement.
Implementation note: input-class detection would run once per parse call using simple
/^[0-9]+$/, /[a-zA-Z]/, .length checks before the layout loop. Could be expressed
as a small decision table or a set of bit-flags (e.g. hasDigits, hasLetters, hasColon,
hasSeparator) computed in a single pass over the input string.
The parse and format standalone split is already a major win. Additional high-ROI splits still available:
- Parse Pipeline Planner module: owns input classification, pre-filter policy, and candidate-layout selection.
- Layout Order Resolver module:
owns base order, locale
mdyLayoutsswaps, and futurelayoutOrderhandling. - Pattern Compiler + Cache module: owns snippet/layout expansion, regex compilation, and cache invalidation.
- Config Option Mutation Engine module: owns option-application switchboard (especially parse-related options).
- Alias Resolution Engine module: owns event/period collision policy and snippet rebinding into layout-aware groups.
Secondary extractions (lower urgency, still useful):
- Guard Builder module: owns token ingestion and fast-fail guard rebuild lifecycle.
- Parse Result Normalizer module: owns match accumulation and parse-result shaping/trace output.
Goal: execute all of the above, but spread refactors to minimize regression risk and preserve API stability.
- Extract Layout Order Resolver (pure ordering logic only).
- Add test matrix for ordering determinism:
- base order unchanged
- mdy swap pairs still deterministic
- custom-inserted layouts maintain stable relative order
- Add telemetry/debug hook for resolved layout order (debug mode only).
Exit criteria:
- No behavioral change except internally equivalent ordering.
- Existing full test suite green.
- Extract Parse Pipeline Planner (candidate selection interface only).
- Implement
layoutOrderoption through Layout Order Resolver. - Ensure
layoutOrdercomposes safely withmdyLayouts(order then swap). - Add docs and migration notes for ordering customization.
Exit criteria:
layoutOrderworks for both global init and local instance options.- Legacy behavior preserved when
layoutOrderis omitted.
- Enable input-class pre-filtering in planner.
- Preserve numeric safety constraints:
isIntegerLike(trim)remains an early BigInt nanosecond escape.- Number-input epoch-millisecond fallback remains last-resort behavior.
- Numeric strings still run through layout matching first.
- Add perf benchmarks before/after for regex-attempt count and constructor parse latency.
Exit criteria:
- Equal parse correctness against regression suite.
- Measurable reduction in unnecessary pattern checks.
Purpose: Evaluate the value and feasibility of extracting all logic related to match accumulation and parse-result shaping/trace output into a dedicated module.
Boundaries & Responsibilities:
- Would own the process of normalizing parse results and shaping trace/debug output.
- Would expose APIs for result normalization and trace formatting.
- Should integrate with the main engine’s parse and debug systems.
Assessment Steps:
- Identify all result normalization and trace output logic in
tempo.class.tsand helpers. - Determine if the logic is sufficiently complex or reused to justify extraction.
- If justified, outline module boundaries and migration steps similar to previous extractions.
- If not, document reasons for keeping logic inline.
Potential Affected Files:
src/tempo.class.tssrc/support/tempo.util.tssrc/tempo.type.ts
Risks & Mitigations:
- Risk: Over-extraction of simple logic. Mitigation: Only extract if complexity or reuse warrants.
- Risk: Integration issues with parse/trace systems. Mitigation: Careful interface design and incremental refactor.
Expected Improvements (if extracted):
- Cleaner separation of result normalization logic.
- Easier to test and update parse-result shaping and trace output.
Purpose: Modularize all logic related to event/period alias resolution, collision policy, and snippet rebinding into layout-aware groups for clarity, maintainability, and extensibility.
Boundaries & Responsibilities:
- Accepts event/period definitions and manages alias mapping and collision detection.
- Handles rebinding of snippets into layout-aware groups.
- Exposes clear APIs for resolving aliases and reporting collisions.
- Integrates with the main engine to ensure correct event/period resolution during parsing.
Migration Steps:
- Identify and extract all alias resolution logic from
tempo.class.tsand related helpers into a new module (e.g.,engine.alias.ts). - Define clear interfaces for alias registration, lookup, and collision reporting.
- Refactor the main engine and plugin system to use the new module’s APIs.
- Add/expand unit tests for alias resolution, collision handling, and rebinding.
- Document the new module’s API and update internal references.
Affected Files:
src/tempo.class.ts(extraction and refactor)src/support/tempo.util.ts(if helpers are involved)src/tempo.type.ts(type updates if needed)
Risks & Mitigations:
- Risk: Incorrect alias resolution or missed collisions. Mitigation: Add focused unit tests and regression tests.
- Risk: Integration issues with plugin/event/period systems. Mitigation: Incremental refactor and thorough testing.
Expected Improvements:
- Cleaner separation of concerns for alias logic.
- Easier to extend and maintain event/period handling.
- Improved testability and reliability of alias resolution.
Purpose: Modularize all logic related to snippet/layout expansion, regex compilation, and cache invalidation.
Outline (to be expanded):
- Identify all code responsible for pattern expansion and regex compilation.
- Define clear module boundaries and interfaces for the compiler and cache.
- Move related logic from the main engine/class to the new module.
- Implement cache invalidation and update mechanisms.
- Add focused unit tests for the new module.
- Update documentation and internal references.
Purpose: Modularize all logic related to snippet/layout expansion, regex compilation, and pattern cache management for clarity, testability, and maintainability.
Boundaries & Responsibilities:
- Accepts layout/snippet definitions and returns compiled RegExp objects.
- Handles recursive expansion of layout placeholders (e.g.,
{yy},{mm}) using snippet registries. - Manages a cache of compiled patterns for performance.
- Exposes cache invalidation/refresh methods for dynamic config changes.
- Provides a clear interface for the rest of the Tempo engine to request compiled patterns.
Migration Steps:
- Extract
compileRegExp,setPatterns, and related helpers fromtempo.util.tsinto a new module (e.g.,pattern.compiler.ts). - Move or wrap memoization/caching logic (from
function.library.ts) as needed for pattern compilation. - Refactor
tempo.class.tsand other consumers to use the new module’s interface. - Ensure all pattern/snippet/layout definitions in
tempo.default.tsare compatible with the new module. - Add/expand unit tests for pattern expansion, compilation, and cache behavior.
- Document the new module’s API and update internal references.
Affected Files:
src/support/tempo.util.ts(extraction)src/tempo.class.ts(refactor to use new module)src/support/tempo.default.ts(ensure compatibility)src/tempo.type.ts(type updates if needed)library/src/common/function.library.ts(cache/memoization logic)
Risks & Mitigations:
- Risk: Subtle bugs in recursive expansion or cache invalidation. Mitigation: Add focused unit tests and regression tests.
- Risk: Performance regressions if cache is not used correctly. Mitigation: Benchmark before/after and optimize cache usage.
Expected Improvements:
- Lower cyclomatic complexity in the main engine.
- Easier to test and reason about pattern expansion and compilation.
- Clearer cache management and invalidation.
To balance safety and efficiency:
- Release the two major extractions (Pattern Compiler + Cache, Alias Resolution Engine) as separate point-releases for focused testing and easier rollback.
- Batch the assessment/documentation steps (Guard Builder, Parse Result Normalizer, affected files/modules, improvements/risks) into a single follow-up release if they are lightweight.
This approach allows for incremental progress, clear regression points, and manageable review cycles.
The recommended execution sequence begins here:
- Extract Layout Order Resolver.
- Extract Parse Pipeline Planner shell (no filtering yet).
- Wire
layoutOrderoption to resolver. - Add input-class pre-filtering behind a guarded internal flag.
Implementation guardrails:
- Keep each step behavior-preserving unless explicitly feature-gated.
- Land with dedicated tests in each step before moving to the next.
- Avoid combining planner + filtering + compiler extraction in one release.