Skip to content

Latest commit

 

History

History
294 lines (225 loc) · 14.8 KB

File metadata and controls

294 lines (225 loc) · 14.8 KB

Tempo v2.0.2 Wishlist (Post-Lockdown)

Architecture & Design

  • Investigate Code-Smells: Review complex methods (like #parse) for high cyclomatic complexity and potential refactoring.

Pervasive Hard-Freeze Proxies

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 a proxify() call.
  • This ensures that resolving a term (like t.term.qtr) returns a Proxy that throws on mutation.
  • [MODIFY] class.library.ts
  • Update the @Immutable decorator to return proxify(this) from the constructor.
  • This ensures that every Tempo instance 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 defineProperty and deleteProperty) are robustly covered.
  • Verification Plan
  • Manual Verification (REPL)
  • Start the REPL: npm run repl.
  • Verify the following all throw explicit TypeErrors:
    • delete t1.term
    • t1.term = {}
    • t1.term.qtr = 1
    • delete t1.term.quarter.month

Parse: layoutOrder option — arbitrary layout sequencing

Today the only reordering supported is the pairwise mdyLayouts swap for locale-based dmymdy 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).
  • mdyLayouts swap would still apply on top, after the custom order is set.

Parse: input-class pre-filtering — conditional layout selection

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 in n (e.g. 1234567890n) → treated as BigInt nanoseconds and returned immediately, bypassing all layouts.
  • type === 'Number' (i.e. the input was supplied as a JS number) — 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.

Further discrete-module candidates (post parse/format/duration/mutation split)

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 mdyLayouts swaps, and future layoutOrder handling.
  • 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.

Release-boundary modularization roadmap

Goal: execute all of the above, but spread refactors to minimize regression risk and preserve API stability.

Release A (next immediate release): low-risk, high-leverage foundations

  • 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.

Release B: planner path and configuration surface

  • Extract Parse Pipeline Planner (candidate selection interface only).
  • Implement layoutOrder option through Layout Order Resolver.
  • Ensure layoutOrder composes safely with mdyLayouts (order then swap).
  • Add docs and migration notes for ordering customization.

Exit criteria:

  • layoutOrder works for both global init and local instance options.
  • Legacy behavior preserved when layoutOrder is omitted.

Release C: conditional pre-filtering optimization

  • 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.

Release D: deeper decomposition cleanup


Parse Result Normalizer Extraction — Assessment Outline

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:

  1. Identify all result normalization and trace output logic in tempo.class.ts and helpers.
  2. Determine if the logic is sufficiently complex or reused to justify extraction.
  3. If justified, outline module boundaries and migration steps similar to previous extractions.
  4. If not, document reasons for keeping logic inline.

Potential Affected Files:

  • src/tempo.class.ts
  • src/support/tempo.util.ts
  • src/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.

Alias Resolution Engine Extraction — Detailed Outline

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:

  1. Identify and extract all alias resolution logic from tempo.class.ts and related helpers into a new module (e.g., engine.alias.ts).
  2. Define clear interfaces for alias registration, lookup, and collision reporting.
  3. Refactor the main engine and plugin system to use the new module’s APIs.
  4. Add/expand unit tests for alias resolution, collision handling, and rebinding.
  5. 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.

Pattern Compiler + Cache Extraction Plan (to be detailed)

Purpose: Modularize all logic related to snippet/layout expansion, regex compilation, and cache invalidation.

Outline (to be expanded):

  1. Identify all code responsible for pattern expansion and regex compilation.
  2. Define clear module boundaries and interfaces for the compiler and cache.
  3. Move related logic from the main engine/class to the new module.
  4. Implement cache invalidation and update mechanisms.
  5. Add focused unit tests for the new module.
  6. Update documentation and internal references.

Pattern Compiler + Cache Extraction — Detailed Outline

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:

  1. Extract compileRegExp, setPatterns, and related helpers from tempo.util.ts into a new module (e.g., pattern.compiler.ts).
  2. Move or wrap memoization/caching logic (from function.library.ts) as needed for pattern compilation.
  3. Refactor tempo.class.ts and other consumers to use the new module’s interface.
  4. Ensure all pattern/snippet/layout definitions in tempo.default.ts are compatible with the new module.
  5. Add/expand unit tests for pattern expansion, compilation, and cache behavior.
  6. 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.

Release D: Recommended Release Strategy

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.

Next sequence kickoff (start now)

The recommended execution sequence begins here:

  1. Extract Layout Order Resolver.
  2. Extract Parse Pipeline Planner shell (no filtering yet).
  3. Wire layoutOrder option to resolver.
  4. 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.