diff --git a/package.json b/package.json index 5b617ca5..9171ddf2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tempo-monorepo", - "version": "2.2.2", + "version": "2.2.3", "private": true, "description": "Magma Computing Monorepo", "repository": { diff --git a/packages/library/package.json b/packages/library/package.json index 6cf917d5..293355b4 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -1,6 +1,6 @@ { "name": "@magmacomputing/library", - "version": "2.2.2", + "version": "2.2.3", "description": "Shared utility library for Tempo", "author": "Magma Computing Solutions", "license": "MIT", diff --git a/packages/tempo/.vitepress/config.ts b/packages/tempo/.vitepress/config.ts index 52c2505a..646da620 100644 --- a/packages/tempo/.vitepress/config.ts +++ b/packages/tempo/.vitepress/config.ts @@ -18,15 +18,16 @@ export default defineConfig({ nav: [ { text: 'Guide', link: '/README' }, { text: 'API', link: '/doc/tempo.api' }, - { text: 'Releases', link: '/doc/releases/versions' } + { text: 'Releases', link: '/doc/releases/' } ], sidebar: [ { text: 'Getting Started', items: [ { text: 'Introduction', link: '/README' }, + { text: 'Cookbook', link: '/doc/tempo.cookbook' }, { text: 'Migration Guide', link: '/doc/migration-guide' }, - { text: 'Version History', link: '/doc/releases/versions' } + { text: 'Release Notes', link: '/doc/releases/' } ] }, { @@ -34,11 +35,21 @@ export default defineConfig({ items: [ { text: 'Configuration', link: '/doc/tempo.config' }, { text: 'Modularity', link: '/doc/tempo.modularity' }, - { text: 'Shorthand Engine', link: '/doc/tempo.shorthand' }, + { text: 'Layout Patterns', link: '/doc/tempo.layout' }, { text: 'Terms System', link: '/doc/tempo.term' }, { text: 'Ticker Plugin', link: '/doc/tempo.ticker' } ] }, + { + text: 'Advanced Reference', + items: [ + { text: 'API Reference', link: '/doc/tempo.api' }, + { text: 'Types System', link: '/doc/tempo.types' }, + { text: 'Shorthand Engine', link: '/doc/tempo.shorthand' }, + { text: 'Weekday Engine', link: '/doc/tempo.weekday' }, + { text: 'Debugging', link: '/doc/tempo.debugging' } + ] + }, { text: 'Architecture & Internals', items: [ @@ -48,13 +59,30 @@ export default defineConfig({ { text: 'Performance Benchmarks', link: '/doc/tempo.benchmarks' } ] }, + { + text: 'Utility Library', + items: [ + { text: 'Library Overview', link: '/doc/tempo.library' }, + { text: 'Enumerators', link: '/doc/tempo.enumerators' }, + { text: 'Serializers', link: '/doc/tempo.serializers' }, + { text: 'Decorators', link: '/doc/tempo.decorators' }, + { text: 'Advanced Promises (Pledge)', link: '/doc/tempo.pledge' }, + ] + }, { text: 'Ecosystem', items: [ { text: 'Contribution Guide', link: '/CONTRIBUTING' }, { text: 'Comparison', link: '/doc/comparison' }, + { text: 'Tempo vs Temporal', link: '/doc/tempo-vs-temporal' }, { text: 'Project Vision', link: '/doc/vision' } ] + }, + { + text: 'Services & Support', + items: [ + { text: 'Professional Services', link: '/doc/commercial' } + ] } ], socialLinks: [ diff --git a/packages/tempo/CHANGELOG.md b/packages/tempo/CHANGELOG.md index 77e87f66..202980c9 100644 --- a/packages/tempo/CHANGELOG.md +++ b/packages/tempo/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.3] - 2026-04-20 + +### Added +- **Modular Parse Engine**: Successfully decoupled internal parsing logic into `ParseModule`, enabling standalone parsing support and reducing core class complexity. +- **Carousel Accessibility**: Added ARIA roles, labels, and keyboard controls (Arrow keys) to the documentation carousel to improve screen reader and keyboard user experience. +- **Layout Snippets**: Introduced layout patterns with snippet-based customization for more flexible date formatting. + +### Changed +- **HMR Resilience**: Hardened the development-mode registry workaround by binding the original `Map.has` method and moving extension registration before class initialization, resolving "read only property" errors during hot reloads. +- **Modular Registration**: Enforced standard `defineInterpreterModule` clash guards for all core modules (Parse, Mutate, Duration) to prevent registry collisions. + +### Fixed +- **Resource Management**: Fixed interval leaks in the documentation clock by implementing proper cleanup for the fallback heartbeat timer during unmounting and visibility changes. +- **Initialization Stability**: Added a sentinel guard and optimized `initPromise` handling to prevent redundant error logging and failed awaits during page visibility transitions. +- **Mutation Engine Hardening**: Corrected preserves `state.options` and the `mutateDepth` recursion guard across all instance creation paths in `MutateModule`. +- **Fluent Chaining Fallbacks**: Hardened `until()` and `since()` calls with explicit host-instance fallbacks to preserve fluent chaining when modules are missing in "catch" mode. + ## [2.2.2] - 2026-04-18 ### Fixed diff --git a/packages/tempo/LICENSE b/packages/tempo/LICENSE new file mode 100644 index 00000000..dd7db2d9 --- /dev/null +++ b/packages/tempo/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Magma Computing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tempo/README.md b/packages/tempo/README.md index 7fbd7e6f..4d3cf3af 100644 --- a/packages/tempo/README.md +++ b/packages/tempo/README.md @@ -5,7 +5,7 @@ Tempo logo -

Tempo

+

Tempo

The Professional Date-Time Library for the Temporal API

@@ -63,13 +63,15 @@ Ideal for organizations looking to move away from **Moment.js**, **Day.js**, or For those building complex, time-sensitive systems (such as financial platforms, scheduling engines, or global logistics trackers) that demand the precision of Temporal combined with a premium, type-safe developer experience. ## 📦 Installation +### 💻 on the Server (or Bundler) +For Node.js, Bun, Deno, or projects using a bundler (Vite, Webpack, etc.), install via npm: + ```bash npm install @magmacomputing/tempo ``` -### 💻 on the Server Tempo is a native ESM package. In Node.js (20+), simply import the class. -Node.js, Bun and Deno support native ESM out of the box. +Node.js, Bun, and Deno support native ESM out of the box. ```javascript import { Tempo } from '@magmacomputing/tempo'; @@ -79,34 +81,58 @@ console.log(t.format('{dd} {mon} {yyyy}')); ``` ### 🌐 in the Browser (Import Maps) -Since Tempo is a native ESM package, you can use it directly in modern browsers using `importmap`: +Since Tempo is a native ESM package, you can use it directly in modern browsers using `importmap`. The **bundle** entrypoint includes all standard modules pre-registered, but requires a separate `Temporal` polyfill for current browser environments. ```html ``` ### 📦 in the Browser (Script Tag) -For environments without `importmap` support or simple prototypes, use the bundled version: +For environments without `importmap` support or simple prototypes, use the global bundle. This automatically attaches the `Tempo` class to the `window` object. ```html - + ``` +### 🧪 Advanced: Granular ESM (Lite Build) +For maximum performance, you can use the lean **Core** engine and opt-in to specific modules. This prevents loading unused logic and keeps your production bundle minimal. + +```html + + +``` + --- ## 📚 Documentation diff --git a/packages/tempo/bin/setup.polyfill.ts b/packages/tempo/bin/setup.polyfill.ts deleted file mode 100644 index 58d8dfab..00000000 --- a/packages/tempo/bin/setup.polyfill.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Test/Dev Polyfill Bootstrap - * - * This file loads the @js-temporal/polyfill into - * globalThis so that the passive assertion in - * temporal.polyfill.ts succeeds. - * - * This is the "bring your own polyfill" entry point - * for development and testing environments that do - * not yet have native Temporal support. - */ -import { Temporal } from '@js-temporal/polyfill'; - -if (typeof globalThis.Temporal === 'undefined') { - Object.defineProperty(globalThis, 'Temporal', { - value: Temporal, - enumerable: false, - configurable: true, - writable: true, - }); -} diff --git a/packages/tempo/bin/temporal-polyfill.ts b/packages/tempo/bin/temporal-polyfill.ts new file mode 100644 index 00000000..e94faa40 --- /dev/null +++ b/packages/tempo/bin/temporal-polyfill.ts @@ -0,0 +1,10 @@ +import { Temporal } from '@js-temporal/polyfill'; + +if (typeof globalThis.Temporal === 'undefined') { + Object.defineProperty(globalThis, 'Temporal', { + value: Temporal, + enumerable: false, + configurable: true, + writable: true, + }); +} diff --git a/packages/tempo/doc/releases/index.md b/packages/tempo/doc/releases/index.md new file mode 100644 index 00000000..cecc728c --- /dev/null +++ b/packages/tempo/doc/releases/index.md @@ -0,0 +1,15 @@ +# 🚀 Releases + +Explore the evolution of Tempo through its version history. + +- [Version 2.x (Current)](./v2.x) - Modular architecture, Shorthand engine, and Ticker stability. +- [Version 1.x (Legacy)](./v1.x) - Initial public release and Temporal polyfill integration. +- [Version 0.x (Legacy)](./v0.x) - Initial release. + +--- + +## Release Strategy +Tempo follows [Semantic Versioning](https://semver.org/). +- **Major**: Breaking changes and architectural shifts. +- **Minor**: New features and significant enhancements. +- **Patch**: Bug fixes and performance optimizations. diff --git a/packages/tempo/doc/releases/v0.x.md b/packages/tempo/doc/releases/v0.x.md new file mode 100644 index 00000000..01dfdd40 --- /dev/null +++ b/packages/tempo/doc/releases/v0.x.md @@ -0,0 +1,31 @@ +# 📜 Version 0.x History + +## [v0.3.0] - 2025-07-31 +### New Features + +Introduced an immutable property decorator and a deep-freeze utility for objects and arrays. +Added a secure, default configuration module for date/time parsing. +Extended global browser support for the Temporal API via polyfill. +### Bug Fixes + +Corrected the enumerable decorator to ensure proper closure. +### Refactor + +Centralised and secured date/time parsing constants and layouts. +Improved typing and immutability for enums and configuration objects. +Updated logging and state management to use object-based constants instead of enums. +### Documentation + +Enhanced inline documentation and added detailed usage examples for enum utilities. +### Chores + +Updated development dependencies to latest versions. +Reformatted TypeScript configuration for clarity. + +## [v0.2.0] - 2024-10-30 +### New Features + +Updated the version of the Tempo class to 0.2.0, ensuring users have access to the latest enhancements. +### Bug Fixes + +Resolved versioning discrepancies in the Default configuration object. \ No newline at end of file diff --git a/packages/tempo/doc/releases/v1.x.md b/packages/tempo/doc/releases/v1.x.md new file mode 100644 index 00000000..a860c2ad --- /dev/null +++ b/packages/tempo/doc/releases/v1.x.md @@ -0,0 +1,59 @@ +# 📜 Version 1.x History + +## [v1.1.3] - 2026-03-31 +### New Features + +Browser: persistent storage wrapper, multi‑tap gesture helper, geolocation + maps/address utilities, simple alert/prompt/confirm helpers. +Server: sandboxed temp‑file API, HTTP helpers with timeouts, Base64 encode/decode, JWT payload decoder. +Tempo v2.0.0: lazy initialization, unified term math/format tokens (#{...}), anchor/term utilities and faster startup. +### Documentation + +Expanded API/type docs, architecture, benchmarks, migration and cookbook guidance. +### Branding + +Package scope renamed to @magmacomputing/*; new build/release scripts and updated TypeScript/test configs. + +## [v1.1.2] - 2026-03-23 +### New Features + +Unified plugin system via Tempo.load() for plugins, term plugins, and discovery configuration. +Made Pledge thenable and awaitable via .then() method support. +Enhanced makeTemplate() with safe placeholder substitution instead of code evaluation. +### Bug Fixes + +Improved asCurrency() to properly coerce string inputs. +Fixed constructor error handling to throw instead of returning empty objects. +### Documentation + +Added Target Audience section describing intended users and migration scenarios. +Expanded Node.js documentation with native subpath import details. + +## [v1.1.1] - 2026-03-22 +### New Features + +Plugin-based extension architecture for extending Tempo functionality +Automatic plugin discovery via static Tempo.discover() method +New subpath exports for enums, serialisation, Pledge utility, and ticker plugin +### Changed + +Ticker functionality now available as optional plugin; requires explicit installation +Selective immutability for decorated classes instead of full object freeze +Core methods (format, add, set) are now protected from overwrites +### Documentation + +Comprehensive overhaul reflecting new plugin architecture +Dedicated Pledge utility documentation added + +## [v1.1.0] - 2026-03-19 +### New Features + +Introduced Tempo.ticker() — reactive clock streams via async-generator (for await...of) or callback subscription with stop control. +### Documentation + +Added a dedicated ticker guide and updated Tempo docs with examples and API reference. +### Tests + +Added unit tests covering callback mode, async-iterator mode and input validation. +### Chores + +Bumped package version to 1.1.0. \ No newline at end of file diff --git a/packages/tempo/doc/releases/versions.md b/packages/tempo/doc/releases/v2.x.md similarity index 74% rename from packages/tempo/doc/releases/versions.md rename to packages/tempo/doc/releases/v2.x.md index ec137411..27a4a5a7 100644 --- a/packages/tempo/doc/releases/versions.md +++ b/packages/tempo/doc/releases/v2.x.md @@ -1,4 +1,22 @@ -# 📜 Version History +# 📜 Version 2.x History + +## [v2.2.3] - 2026-04-20 +### New Features + +- Modular parse engine and standalone parsing support +- Layout patterns with snippet-based customization +- Added release notes pages and added MIT license +### Bug Fixes + +- Preserve parse results across cloned instances +- Fixed timezone/calendar drift during complex mutations +### Documentation + +- Reorganized docs navigation with Cookbook, Advanced Reference, Utility Library, Services & Support +- Expanded API/constructor docs and custom-enum guidance +### Chores + +- Improved module-loading diagnostics and general doc formatting cleanup ## [v2.2.2] - 2026-04-18 ### 🛡️ Registry Stabilization @@ -19,10 +37,22 @@ - Harmonized branding across README and documentation sites with "Tempo Blue" styling. - Fixed routing and asset resolution for GitHub Pages deployments using VitePress base-path helpers. +### 🧬 Mutation Engine & State Preservation +- Stabilized the mutation engine (`.add()`, `.set()`) to correctly accumulate and preserve parse results across instance clones. +- Implemented robust parse result propagation in `MutateModule` using the internal `state.matches` registry. +- Standardized cross-module state access using the `[sym.$Internal]` symbol, enabling safe cloning without violating private encapsulation. +- Resolved timezone and calendar drift during complex mutations by enforcing authoritative state-merging in the mutation pipeline. + +### 🧩 Modular Parse Engine +- Successfully decoupled internal parsing logic into `ParseModule`, reducing core class complexity. +- Implemented a hybrid static `Tempo.parse` configuration/method pattern to maintain full backward compatibility while supporting modular delegation. +- Hardened the `interpret` utility to ensure reliable method dispatching even when modules are loaded lazily or via HMR. + ### ⚙️ CI/CD & Tooling - Upgraded GitHub Actions pipeline to **Node.js 24** to resolve deprecation warnings. - Restored separate "Full" and "Core" test suite isolation in local and workspace Vitest configurations. - Standardized documentation build order to ensure artifacts are compiled before generation. +- Achieved **100% Test Pass Rate** (384/384) across all environments and distribution bundles. ## [v2.1.3] - 2026-04-18 ### New Features diff --git a/packages/tempo/doc/tempo-vs-temporal.md b/packages/tempo/doc/tempo-vs-temporal.md index 99687bfa..aa9b516b 100644 --- a/packages/tempo/doc/tempo-vs-temporal.md +++ b/packages/tempo/doc/tempo-vs-temporal.md @@ -67,7 +67,6 @@ From that point, the plugin is available to new Tempo instances. See the section on [plugin](tempo.term.md) for more information. - ```typescript const t = new Tempo(); const isWeekend = t.term.isWeekend; // through plugin diff --git a/packages/tempo/doc/tempo.api.md b/packages/tempo/doc/tempo.api.md index ed42e15b..5d996cbe 100644 --- a/packages/tempo/doc/tempo.api.md +++ b/packages/tempo/doc/tempo.api.md @@ -9,6 +9,25 @@ This document provides a comprehensive technical reference for the `Tempo` class --- +## 🏗️ Constructor + +You can instantiate `Tempo` in several ways: + +- **`new Tempo()`**: Defaults to current time ("now"). +- **`new Tempo(dateTime)`**: Parses a date-time value. +- **`new Tempo(dateTime, options)`**: Parses with specific configuration. +- **`new Tempo(options)`**: Defaults to "now" with specific configuration. + +### Valid `dateTime` Types: +- **`string`**: ISO 8601, natural language ("tomorrow", "next Friday"), or custom patterns. +- **`number`**: Unix timestamps in milliseconds (default) or microseconds. +- **`BigInt`**: Unix timestamps in nanoseconds. +- **`Date`**: Standard JavaScript `Date` object. +- **`Tempo`**: Clones another Tempo instance. +- **`Temporal.*`**: Any native Temporal object (ZonedDateTime, PlainDate, etc.). + +--- + ## 🏗️ Static Methods ### `Tempo.init(options?: Tempo.Options)` @@ -39,7 +58,6 @@ Compares two `Tempo` instances or date-time values for sorting. - **Returns:** `Tempo.Duration` - **Example:** `Tempo.duration('P1Y')` or `Tempo.duration({ months: 2 })` - ### `Tempo.now()` Returns the current Unix epoch in nanoseconds as a `BigInt`. @@ -178,3 +196,9 @@ Returns a `Temporal.PlainDateTime` representation. - `fmt`: Registry of pre-calculated strings for all standard formats. (Note: These are enumerable for easy discovery). - `config`: The effective configuration for this specific instance (Note: `scope`, `anchor`, and `value` are excluded from the public object). - `parse`: The parsing rules and lineage for this instance. + +--- + +> [!TIP] +> **Looking for the full technical details?** +> For an exhaustive, auto-generated reference of every property, internal type, and class member, see our [Full Technical API Reference](./api/README.md). diff --git a/packages/tempo/doc/tempo.constructor.md b/packages/tempo/doc/tempo.constructor.md deleted file mode 100644 index 9d952acd..00000000 --- a/packages/tempo/doc/tempo.constructor.md +++ /dev/null @@ -1,32 +0,0 @@ -You instantiate a Tempo in a number of ways. - -via the Tempo constructor() -a. new Tempo() -b. new Tempo(dateTime) -c. new Tempo(dateTime, options) -d. new Tempo(options) - -or via the static method Tempo methods -a. Tempo.now() -b. Tempo.from(dateTime, options) - - -The {dateTime} argument is one of the following types: -- `string`: ISO strings, natural language relative strings, or custom patterns. -- `number`: Unix timestamps in milliseconds (default) or microseconds. -- `BigInt`: Unix timestamps in nanoseconds. -- `Date`: Standard JavaScript Date object. -- `Tempo`: Another Tempo instance (clones the instance). -- `Function`: A dynamic resolver (max depth 5; returns any valid DateTime). -- `Temporal.*`: Various Temporal objects (ZonedDateTime, PlainDate, etc.). -- `void` | `null`: Defaults to the current time ("now"). - -The {options} argument (in either the first or second parameter) is a JSON object that refines this instance. -(see 'Options') - ---- - -Tempo will interpret the {dateTime} depending on its type: -- **string**: The parsing engine attempts to match the string against a known set of patterns (see [Parsing](file:///home/michael/Project/tempo/doc/tempo.layout.md)). -- **number**: First checks if it is a match against a known layout (see [Layouts](file:///home/michael/Project/tempo/doc/tempo.layout.md)). If not, it is Interpreted as a Unix timestamp. The precision (ms or us) is configurable via `Tempo.init()`. -- **Temporal objects**: Directly converted to a `Tempo` instance, preserving time zone and calendar if applicable. \ No newline at end of file diff --git a/packages/tempo/doc/tempo.enumerators.md b/packages/tempo/doc/tempo.enumerators.md index c7103dfa..31e3bee2 100644 --- a/packages/tempo/doc/tempo.enumerators.md +++ b/packages/tempo/doc/tempo.enumerators.md @@ -4,8 +4,6 @@ Tempo uses a custom `enumify` utility to define enumerations rather than relying This guide explains how they are defined, how you use them as a consumer of the `Tempo` library, and why this design pattern was chosen. ---- - ## 1. How Tempo Enums are Defined Tempo's core enumerators (like Weekdays, Months, Seasons) are built using the exported `enumify` function. @@ -34,8 +32,6 @@ After defining the enumify object, simple TypeScript helper aliases pull out the export type SEASON = ValueOf; // Type: 'summer' | 'autumn' | 'winter' | 'spring' ``` ---- - ## 2. Using Enums Outside of Tempo For consumers of the library, these enumerations are exposed cleanly as **static getters** on the core `Tempo` class. They are also available as a namespace object from the 'barrel' (index.ts) export `enums`. @@ -49,6 +45,24 @@ import { Tempo } from '@magmacomputing/tempo'; const direction = Tempo.COMPASS.North; // 'north' const monthIndex = Tempo.MONTH.Feb; // 2 (since 'All' was index 0) ``` + +## 3. Creating Custom Enums + +You can utilize the same `enumify` engine for your own application logic by importing it from the library subpath. This is particularly useful for maintaining consistent data patterns and iteration capabilities throughout your project. + +### Example Usage + +```typescript +import { enumify } from '@magmacomputing/tempo/library'; + +// 1. Define your Enum +export const STATUS = enumify(['Pending', 'Active', 'Resolved', 'Archived']); + +// 2. Use the built-in methods +const allKeys = STATUS.keys(); // ['Pending', 'Active', 'Resolved', 'Archived'] +const isActive = STATUS.has('Active'); // true +const value = STATUS.Resolved; // 2 +``` or alternatively, directly from the 'enums' export in the package.json ```typescript import { enums } from '@magmacomputing/tempo/enums'; @@ -78,9 +92,7 @@ const keyName = Tempo.MONTH.keyOf(2); // 'Feb' const customStrings = Tempo.WEEKDAY.map(([key, val]) => `${key} is day ${val}`); ``` ---- - -## 3. How They Are Used Inside Tempo +## 4. How They Are Used Inside Tempo Internally, the `Tempo` logic relies heavily on these enumerators. This gives the parsing and formatting engines guaranteed type-safety and robust lookup dictionaries. @@ -88,9 +100,7 @@ For instance, the `.format()` logic can map tokens efficiently, and parser confi The overarching design ensures the library stays strongly typed, internally consistent, and protected against accidental runtime mutation via `Object.freeze()`. ---- - -## 4. `enumify` vs. TypeScript `enum` (The Trade-Offs) +## 5. `enumify` vs. TypeScript `enum` (The Trade-Offs) TypeScript's native `enum` is one of the few TS features that generates structural runtime JavaScript, and it has known friction points in the JavaScript community. diff --git a/packages/tempo/doc/tempo.layout.md b/packages/tempo/doc/tempo.layout.md index 63b694cc..7a865dfc 100644 --- a/packages/tempo/doc/tempo.layout.md +++ b/packages/tempo/doc/tempo.layout.md @@ -2,14 +2,54 @@ Tempo's parsing engine is driven by regular expression patterns and named capture-groups. By understanding and extending these layouts, you can teach Tempo to understand entirely new terminology, formats, and relative units. -## Default Patterns +## What is a Snippet? -Tempo comes out-of-the-box with patterns to understand: -- **ISO 8601** (`2024-05-20T10:00:00Z`) -- **Dates** (`20-May`, `May 20`, `04/01/2026`) -- **Times** (`10am`, `14:30:00.123`) -- **Relative Events** (`today`, `tomorrow`, `yesterday`) -- **Relative Periods** (`morning`, `noon`, `afternoon`) +A **Snippet** is a pre-defined regex pattern that can be combined with other snippets to create a **Layout**. + +## What is a Layout? + +A **Layout** is a string that combines pre-defined **Snippets** and strings. When you provide a layout to `Tempo`, it is translated into an anchored, case-insensitive Regular Expression used to match and extract date-time values. + +## Available Snippets + +Snippets represent specific date or time units. When arranged in a layout, they form the blueprint for the parser: + +| Snippet | Description | Regex Match (approx) | +| :--- | :--- | :--- | +| `{yy}` | Year (2 or 4 digits) | `([0-9]{2})?[0-9]{2}` | +| `{mm}` | Month (01-12, Jan-Dec, etc.) | `01-12` or names | +| `{dd}` | Day (01-31) | `01-31` | +| `{hh}` | Hour (00-24) | `00-24` | +| `{mi}` | Minute (prefixed by `:`) | `:[0-5][0-9]` | +| `{ss}` | Second (prefixed by `:`) | `:[0-5][0-9]` | +| `{ff}` | Fraction (prefixed by `.`) | `\.[0-9]{1,9}` | +| `{wkd}` | Weekday (Mon-Sun) | Name strings | +| `{tzd}` | Time zone offset | `Z` or `±hh:mm` | +| `{sep}` | Separator character | `/`, `-`, `.`, `,`, or ` ` | +| `{mod}` | Modifier and count | `+`, `-`, `next`, `prev` | +| `{unt}` | Time units | `year(s)`, `day(s)`, etc. | +| `{evt}` | Event alias | `xmas`, `nye`, etc. | +| `{per}` | Period alias | `midnight`, `noon`, etc. | +| `{sfx}` | Time-pattern suffix | `T {tm} Z` | +| `{brk}` | Zone/Calendar brackets | `[UTC][u-ca=iso8601]` | +| `{www}` | Short weekday name | `Mon`, `Tue`, etc. | +| `{nbr}` | Numeric value or word | `1`, `two`, etc. | +| `{afx}` | Relative affix | `ago`, `hence`, `from now` | + +### Composite Snippets + +- `{dt}`: Matches a date (e.g., `{dd}{sep}{mm}`) OR an event alias `{evt}`. +- `{tm}`: Matches a time (e.g., `{hh}{mi}`) OR a period alias `{per}`. + +## Built-in Layouts + +| Key | Layout | Description | +| :--- | :--- | :--- | +| `dtm` | `({dt}){sfx}?{brk}?` | Calendar/event and clock/period | +| `dmy` | `{www}?{dd}{sep}?{mm}({sep}{yy})?{sfx}?{brk}?` | Day-month(-year) | +| `mdy` | `{www}?{mm}{sep}?{dd}({sep}{yy})?{sfx}?{brk}?` | Month-day(-year) | +| `ymd` | `{www}?{yy}{sep}?{mm}({sep}{dd})?{sfx}?{brk}?` | Year-month(-day) | +| `unt` | `{nbr}{sep}?{unt}{sep}?{afx}` | Relative duration | ## Customizing Layouts @@ -17,32 +57,34 @@ You can supply your own parsing tokens to Tempo globally via `Tempo.init()` or l ```typescript Tempo.init({ - event: { - 'birthday': '20 May', - 'xmas': '25 Dec' + layout: { + 'myCustomFormat': '{dd}{sep}?{mm}{sep}?{yy}' } }); -const t = new Tempo('xmas'); // resolves to 25 Dec of the current year +const t = new Tempo('20-05-2024'); // Parsed using 'myCustomFormat' +``` + +### Instance-Specific Layout + +```typescript +const t = new Tempo('20240520', { layout: '{yy}{mm}{dd}' }); ``` ## Advanced Capture Groups -When delving into Tempo's internal Regex patterns, the following named capture groups are utilized by the engine: +When crafting raw regex, the following capture groups are used by the engine: - `yy`, `mm`, `dd`: Year, Month, Day digits - `hh`, `mi`, `ss`, `ff`: Hour, Minute, Second, Fractional digits - `mer`: Meridiem (`am`, `pm`) - `evt`: Event string offset - `per`: Period string offset - `unt`: Relative unit (e.g., `days`, `weeks`) -- `mod`, `nbr`, `afx`, `sfx`: Modifiers, numbers, affixes, and suffixes for relative computations (e.g. `2 days ago`, `next Friday`) --- -## Need a Custom Layout? - -Tempo's layout engine can interpret almost any date or time imaginable, but crafting robust regular expressions with strict named capture-groups requires precision. +## Professional Services -If your project involves specialized terminology, complex financial calendars, medical intervals, or legacy application log formats, the **Magma Computing** team offers professional services to design, build, and comprehensively test custom `Tempo` Layouts optimized precisely for your business needs. +If your project involves specialized terminology, complex financial calendars, or legacy application log formats, the **Magma Computing** team offers professional services to design and test custom `Tempo` Layouts optimized for your business needs. -Contact us at [Magma Computing](https://github.com/magmacomputing) to discuss extending Tempo for your organization. +Contact us at [Magma Computing](https://github.com/magmacomputing). diff --git a/packages/tempo/doc/tempo.library.md b/packages/tempo/doc/tempo.library.md index 1a831afd..5daef2e8 100644 --- a/packages/tempo/doc/tempo.library.md +++ b/packages/tempo/doc/tempo.library.md @@ -55,4 +55,5 @@ Tempo provides a specialized wrapper around `Promise.withResolvers()` called `Pl * **Resource Management:** Implements `Symbol.dispose` to automatically reject pending promises when they go out of scope, preventing deadlocks or memory leaks. 👉 **[Read the full Pledge Guide](./tempo.pledge.md)** for advanced usage with callbacks, debugging tags, and lifecycle management. ---- + + diff --git a/packages/tempo/doc/tempo.pattern.md b/packages/tempo/doc/tempo.pattern.md deleted file mode 100644 index 8150f781..00000000 --- a/packages/tempo/doc/tempo.pattern.md +++ /dev/null @@ -1,156 +0,0 @@ -# Custom Patterns - -`Tempo` will create a Regular Expressions from **Layout** strings. It will use these patterns to attempt to match and extract date-time values from an input-string. - -## What is a Snippet? - -A **Snippet** is a pre-defined regex pattern that can be combined with other snippets to create a **Layout**. - -## What is a Layout? - -A **Layout** is a string that combines pre-defined **Snippets** and strings. When you provide a layout to `Tempo`, it is translated into an anchored, case-insensitive Regular Expression used to match and extract date-time values. - -## Available Snippets - -Snippets are simple regex patterns that can be composed into a layout. They represent specific date or time units: - -| Snippet | Description | Regex Match (approx) | -| :--- | :--- | :--- | -| `{yy}` | Year (2 or 4 digits) | `([0-9]{2})?[0-9]{2}` | -| `{mm}` | Month (01-12, Jan-Dec, January-December) | `01-12` or names | -| `{dd}` | Day (01-31) | `01-31` | -| `{hh}` | Hour (00-24) | `00-24` | -| `{mi}` | Minute (prefixed by `:`) | `:[0-5][0-9]` | -| `{ss}` | Second (prefixed by `:`) | `:[0-5][0-9]` | -| `{ff}` | Fraction (prefixed by `.`) | `\.[0-9]{1,9}` | -| `{wkd}` | Weekday (Mon-Sun, Monday-Sunday) | Name strings | -| `{tzd}` | Time zone offset | `Z` or `±hh:mm` | -| `{mer}` | Meridiem (AM/PM) | `am` or `pm` | -| `{sep}` | Separator character | `/`, `-`, `.`, `,`, or ` ` | -| `{mod}` | Modifier and optional count | `+`, `-`, `<`, `>`, `next`, `prev`, etc. | -| `{nbr}` | Generic number or names (0-10) | `[0-9]+` / `zero-ten` | -| `{unt}` | Time units (year, month, week, etc.) | `year(s)`, `day(s)`, etc. | -| `{afx}` | Affix modifier | `ago`, `hence`, or `from now` | -| `{sfx}` | Time suffix | Matches `T` or a space followed by a time pattern | -| `{brk}` | Timezone/calendar brackets | `[Europe/London]`, `[u-ca=iso8601]` | - -### Composite Snippets - -Some snippets are auto-built from others: - -- `{evt}`: Matches any defined **Event** alias (e.g., `xmas`, `nye`). -- `{per}`: Matches any defined **Period** alias (e.g., `midnight`, `noon`). -- `{dt}`: Matches a date (e.g., `{dd}{sep}{mm}`) OR an event alias `{evt}`. -- `{tm}`: Matches a time (e.g., `{hh}{mi}`) OR a period alias `{per}`. - -> [!IMPORTANT] -> **Component-Aware Resolution**: To ensure that custom aliases don't accidentally overwrite explicit date or time parts in your input, `#parseGroups` is type-aware: -> - **Events**: If an Event function returns a `Tempo` or `Temporal` object, it is converted to a `PlainDate` string (date-only) before parsing. -> - **Periods**: If a Period function returns a `Tempo` or `Temporal` object, it is converted to a `PlainTime` string (time-only) before parsing. -> -> This ensures that an Event result (like `tomorrow`) only modifies the date-component, and a Period result (like `morning`) only modifies the time-component, preserving any other explicitly provided fields. - -## Built-in Layouts - -Snippets are wrapped in curly braces `{}` and can be combined to create a layout. - -| Key | Layout | Description | -| :--- | :--- | :--- | -| `dt` | `{dt}` | Calendar or event | -| `tm` | `{tm}` | Clock or period | -| `wkd` | `'{mod}?{wkd}{afx}?{sfx}?'` | Weekday name | -| `dtm` | `({dt}){sfx}?{brk}?` | Calendar/event and clock/period | -| `dmy` | `{www}?{dd}{sep}?{mm}({sep}{yy})?{sfx}?{brk}?` | Day-month(-year) | -| `mdy` | `{www}?{mm}{sep}?{dd}({sep}{yy})?{sfx}?{brk}?` | Month-day(-year) | -| `ymd` | `{www}?{yy}{sep}?{mm}({sep}{dd})?{sfx}?{brk}?` | Year-month(-day) | -| `unt` | `{nbr}{sep}?{unt}{sep}?{afx}` | Relative duration | -| `evt` | `{evt}` | Event only | -| `per` | `{per}` | Period only | - -## Creating a Layout - -To create a layout, arrange the snippets in the order they appear in your input string. - -### Example: `YYYYMMDD` -If you have a string like `20240520`, your layout could be: -`{yy}{sep}?{mm}{sep}?{dd}` - -### Example: `MMM-DD-YYYY` -For a string like `May-20-2024`, your layout could be: -`{mm}{sep}?{dd}{sep}?{yy}` or `{dd}{sep}?{mm}{sep}?{yy}` -Either layout will match the string, as Tempo is timeZone-aware and will attempt to use whichever pattern returns a result. - -## Using Custom Layouts - -You can register custom layouts globally or use them just for a specific instance. - -### Global Registration - -Use `Tempo.init()` to add layouts that should be available to all new instances. -> [!NOTE] -> Assigning a 'name' to a Layout is optional and is auto-generated if not provided. -> It is used internally-only to identify the layout when parsing a string. - -```typescript -Tempo.init({ - layout: { - 'myCustomFormat': '{dd}{sep}?{mm}{sep}?{yy}' - } -}); - -const t = new Tempo('20-05-2024'); // Parsed using 'myCustomFormat' -``` - -### Instance-Specific Layout - -Pass a layout directly to the `Tempo` constructor. - -```typescript -// Using a string -const t1 = new Tempo('20240520', { layout: '{yy}{mm}{dd}' }); - -// Using an array for a multiple layouts to match against a dateTime string -const t2 = new Tempo('Monday, 20 May 2024', { - layout: ['{wkd}{sep}?{dd}{sep}?{mm}{sep}?{yy}', '{dd}{sep}?{mm}{sep}?{yy}'] -}) -``` - -## Advanced: Regex Layouts - -If the built-in snippets aren't enough, you can provide a raw Regular Expression or a mixture: - -```typescript -const t = new Tempo('Year 2024 Day 20', { - layout: 'Year {yy}, Day {dd}' -}) -``` - -To aid in designing a new Layout, use the static `Tempo.regexp()` method. -It will return a Regular Expression that can be used to debug the layout against a string. - -```typescript -let regex = Tempo.regexp('{yy}{sep}?{mm}{sep}?{dd}'); -let match = regex.exec('20240520')?.groups; -// { yy: '2024', mm: '05', dd: '20' } -``` - -To aid in designing a new Snippet, use the static `Tempo.regexp()` method, with the snippet as the second argument. -```typescript -// first create Symbols for the snippet keys -const innerSym = Tempo.getSymbol('inner_test'); -const outerSym = Tempo.getSymbol('outer_test'); - -// create the snippet -const snippet = { - [innerSym]: /(?bar)/, - [outerSym]: /(?foo{inner_test}baz)/, -} - -// create the regex -let regex = Tempo.regexp('{outer_test}', snippet); -let match = regex.exec('foobarbaz')?.groups; -// { outer: 'foobarbaz', inner: 'bar' } -``` - -> [!NOTE] -> All layouts are automatically anchored with `^` and `$` and set to case-insensitive (`i`) when processed by the parsing engine. diff --git a/packages/tempo/doc/tempo.pledge.md b/packages/tempo/doc/tempo.pledge.md index 6b9627e8..09837956 100644 --- a/packages/tempo/doc/tempo.pledge.md +++ b/packages/tempo/doc/tempo.pledge.md @@ -7,7 +7,7 @@ The `Pledge` utility is a specialized wrapper around `Promise.withResolvers()` d A `Pledge` provides direct access to its state and resolution methods. ```typescript -import { Pledge } from '@magmacomputing/tempo'; +import { Pledge } from '@magmacomputing/tempo/library'; // 1. Instantiate const p = new Pledge('DataFetch'); diff --git a/packages/tempo/doc/tempo.serializers.md b/packages/tempo/doc/tempo.serializers.md index a116bc39..fd471b03 100644 --- a/packages/tempo/doc/tempo.serializers.md +++ b/packages/tempo/doc/tempo.serializers.md @@ -14,7 +14,7 @@ The serializers are heavily utilized under the hood but are also exposed for rob Serializes variables, primitives, and rich objects into string-safe representations. Unlike standard JSON, it detects complex types and translates them into identifiable single key:value structures (e.g., `{"$BigInt":"123"}`). ```typescript -import { stringify } from '@magmacomputing/tempo'; +import { stringify } from '@magmacomputing/tempo/library'; const richData = new Map([ ['time', new Date()], @@ -30,7 +30,7 @@ const safeString = stringify(richData); The inverse of `stringify`. It rebuilds an object from its stringified representation, parsing the specialized `{ $Type: value }` signatures back into their native JavaScript object instances. ```typescript -import { objectify } from '@magmacomputing/tempo'; +import { objectify } from '@magmacomputing/tempo/library'; const restored = objectify(safeString); // restored instance is a true Map, containing true Date, Symbol, and BigInt primitives @@ -40,7 +40,7 @@ const restored = objectify(safeString); Creates a deep-copy of an object by piping it through `stringify` and immediately back through `objectify`. This results in a detached, deeply cloned object that retains its rich types. ```typescript -import { cloneify } from '@magmacomputing/tempo'; +import { cloneify } from '@magmacomputing/tempo/library'; const detachedCopy = cloneify(richData); ``` diff --git a/packages/tempo/doc/tempo.term.md b/packages/tempo/doc/tempo.term.md index 8b57c228..5cbe394d 100644 --- a/packages/tempo/doc/tempo.term.md +++ b/packages/tempo/doc/tempo.term.md @@ -14,8 +14,6 @@ new Tempo('25-Dec-2024').term.season // ← computed on first access, cached > [!TIP] > **Transparent Discovery**: As of **v2.0.1**, all term properties are **enumerable**. Whilst modern `console.log` environments (like Node.js) will typically display these as `[Getter]` to preserve laziness, they *are* visible to property-scanning tools. This means a serializer (like `JSON.stringify`) or a deep-clone utility **will** trigger the eager evaluation of *every* registered term at once. To prevent terminal noise during these events (e.g., for invalid dates), initialize Tempo with **`silent: true`**. ---- - ## What a Term Does A term plugin answers a single question: @@ -31,7 +29,6 @@ Plugin expose two views of that result via the `Tempo.term` object: | `tempo.term.` | A short identifier string (e.g. `'qtr'`, `'szn'`, `'zdc'`) | | `tempo.term.` | The full matching range object, with all metadata fields (e.g. `key`, `day`, `month`, `year`, `sphere`, etc.) | The `` and `` are defined by the plugin author, where the intent of the `` is to provide a short identifier value, and the intent of the `` is to provide the full matching range object. ---- ## Provided Plugin @@ -53,8 +50,6 @@ const t = new Tempo('15-Feb-2025', { sphere: 'south' }); t.term.qtr // → 'Q3' (southern hemisphere) ``` ---- - ### `szn` / `season` — Meteorological Seasons Maps the current date to the appropriate meteorological season. @@ -77,8 +72,6 @@ const t = new Tempo('01-Jul-2025', { sphere: 'south' }); t.term.szn // → 'Winter' (southern hemisphere, different boundary dates) ``` ---- - ### `zdc` / `zodiac` — Astrological Zodiac Determines the Western astrological sign for the date. @@ -95,8 +88,6 @@ t.term.zodiac.CN // → { animal: 'Snake', traits: 'Wise, intuitive', element: 'Wood', yinYang: 'Yin' } ``` ---- - ### `per` / `period` — Daily Time Periods Classifies the time of day into a named period based on a pre-defined range. @@ -119,8 +110,6 @@ t.term.per // → 'midday' t.term.period // → { key: 'midday', hour: 12 } ``` ---- - ## Inspecting Registered Terms The static `Tempo.terms` getter returns a read-only list of all registered plugin: @@ -135,8 +124,6 @@ Tempo.terms // ] ``` ---- - ## Activating Terms In **Tempo Full**, all standard terms are enabled by default. In **Tempo Core**, you have three ways to opt-in: @@ -165,8 +152,6 @@ import { QuarterTerm } from '@magmacomputing/tempo/term/quarter'; Tempo.extend(QuarterTerm); ``` ---- - ## How to Define a Term Plugin A term plugin is ideally created using the **`defineTerm`** factory function provided by the library. This ensures correct type-inference and automatically handles registration during the discovery phase. @@ -219,7 +204,6 @@ export const MySeasonTerm = defineTerm({ }); ``` - ### `Range` fields A `Range` object must include a `key` and any subset of the date-time fields below. @@ -283,8 +267,6 @@ const t = new Tempo('2025-02-15'); console.log(t.term.cfy); // → "FY2024" (because it's before July 2025) ``` ---- - ## 🧭 Writing Math-Aware Terms To unlock Tempo's advanced **Term Traversal** (e.g., `t.add({ '#quarter': 1 })`) and **Ticker Syncing**, a plugin must provide semantic **boundaries** (`start` and `end`). @@ -313,8 +295,6 @@ return keyOnly ? 'MyTerm' : { }; ``` ---- - ## 🕒 Terms in Tickers Any term that provides `start` and `end` boundaries can be used to drive a `Tempo.ticker`. This is ideal for logic that doesn't follow a fixed duration (like seasons or fiscal quarters). @@ -327,8 +307,6 @@ for await (const t of quarterly) { } ``` ---- - ## 🛠️ Developer Guide: Best Practices To ensure a custom `Term` plugin integrates fully with Tempo, follow these guidelines: @@ -339,8 +317,6 @@ To ensure a custom `Term` plugin integrates fully with Tempo, follow these guide 4. **Math Readiness**: Always use `getTermRange` or provide boundaries. Without them, users cannot use your term in `add()`, `set()`, or `ticker()`. 5. **Key consistency**: Ensure the `key` property you return in the `define` function's scope object matches the `key` definition of your plugin. ---- - ## 🧭 Best Practices: Idempotency & Side-Effects > [!IMPORTANT] diff --git a/packages/tempo/doc/tempo.ticker.md b/packages/tempo/doc/tempo.ticker.md index 879bfaf3..bc5f2a5f 100644 --- a/packages/tempo/doc/tempo.ticker.md +++ b/packages/tempo/doc/tempo.ticker.md @@ -208,8 +208,6 @@ try { > [!WARNING] > If you are using `const` or `let` without a `finally` block, an assertion failure will skip the `stop()` call, leaving a live timer in the event loop. Always prefer the `using` keyword or `try...finally` for industrial-grade resource management. ---- - ### `Ticker` Object The object returned by `Tempo.ticker()` (or an instance of the `Ticker` class) implements the following interface: @@ -223,8 +221,6 @@ The object returned by `Tempo.ticker()` (or an instance of the `Ticker` class) i | `[Symbol.asyncDispose]` | Standard async cleanup for `await using` blocks. | | `[Symbol.asyncIterator]` | Standard async iteration support (for `for await` loops). | ---- - ## 📊 Reporting & Registry The `Ticker` class maintains a static registry of all currently active tickers. This is useful for debugging, monitoring, or cleanup checks. @@ -255,8 +251,6 @@ type Snapshot = { } ``` ---- - ## 🎯 One-Shot Ticker (Meeting Alerts) You can use the ticker as a "one-shot" timer for specific events by simply specifying a **seed** value. This is perfect for setting up a single alert (e.g., for a meeting) that cleans itself up immediately after firing. @@ -293,8 +287,6 @@ Tempo.ticker({ > [!WARNING] > While `limit: 1` handles the stop condition automatically, always remember that if you are using long-running tickers without a limit, you **must** use the [Disposer Pattern](#zombie-tickers-warning) or manual `stop()` to avoid memory leaks and zombie processes. ---- - ## 🧭 Advanced: Syncing Multiple Clocks If you need to show multiple timezones on a dashboard, avoid creating multiple tickers. Instead, use a single **Master Ticker** to drive all views. This prevents "drift" between the clocks and is much more efficient. diff --git a/packages/tempo/doc/tempo.types.md b/packages/tempo/doc/tempo.types.md index 2011d199..65bb608d 100644 --- a/packages/tempo/doc/tempo.types.md +++ b/packages/tempo/doc/tempo.types.md @@ -2,8 +2,6 @@ This document provides a reference for the core TypeScript types and interfaces used within the `Tempo` namespace. These types define the valid inputs, configuration options, and manipulation arguments for the library. ---- - ## `Tempo.DateTime` The primary type used for arguments representing a point in time. `Tempo` is extremely flexible and can interpret a wide range of formats. It also provides methods to extract these back as `Temporal` objects (e.g., `toPlainDate()`, `toInstant()`, etc.). @@ -19,8 +17,6 @@ type DateTime = | undefined | null // Interpreted as "now" ``` ---- - ## `Tempo.Options` Configuration options that can be passed to `Tempo.init()` or the `Tempo` constructor. @@ -41,8 +37,6 @@ interface Options { } ``` ---- - ## `Tempo.Add` Used by the `.add()` method to specify a duration to add or subtract. @@ -53,8 +47,6 @@ type Add = Partial>; t.add({ days: 5, hours: -2 }); ``` ---- - ## `Tempo.Set` Used by the `.set()` method to move to a specific unit boundary or date-time alias. @@ -70,8 +62,6 @@ t.set({ event: 'xmas' }); // Relative or absolute event alias t.set({ time: '14:30' }); // Specific time string ``` ---- - ## `Tempo.Unit` Valid date and time unit strings used throughout the API. @@ -84,8 +74,6 @@ type Unit = // ... etc. ``` ---- - ## `Tempo.Until` The argument passed to `.until()` and `.since()`. @@ -99,8 +87,6 @@ t.until('2025-01-01', 'days'); t.since('yesterday', { timeZone: 'UTC' }); ``` ---- - ## `Tempo.Discovery` The contract for global discovery via `Symbol.for($Tempo)`. @@ -115,8 +101,6 @@ interface Discovery { } ``` ---- - ## `Tempo.TermPlugin` The interface for defining custom business-logic plugins. @@ -128,7 +112,6 @@ type TermPlugin = { define: (this: Tempo, keyOnly?: boolean) => any; } ``` ---- ## `Tempo.TickerOptions` Advanced configuration for `Tempo.ticker()`. Extends `Temporal.DurationLike` (plural keys only). diff --git a/packages/tempo/importmap.json b/packages/tempo/importmap.json index 08274be7..6352c328 100644 --- a/packages/tempo/importmap.json +++ b/packages/tempo/importmap.json @@ -5,6 +5,8 @@ "@magmacomputing/tempo/ticker": "./dist/plugin/extend/extend.ticker.js", "@magmacomputing/tempo/duration": "./dist/plugin/module/module.duration.js", "@magmacomputing/tempo/format": "./dist/plugin/module/module.format.js", + "@magmacomputing/tempo/mutate": "./dist/plugin/module/module.mutate.js", + "@magmacomputing/tempo/parse": "./dist/plugin/module/module.parse.js", "@magmacomputing/tempo/plugin": "./dist/plugin/plugin.index.js", "@magmacomputing/tempo/enums": "./dist/tempo.enum.js", "@magmacomputing/tempo/library": "./dist/library.index.js" diff --git a/packages/tempo/index.md b/packages/tempo/index.md index cfc42702..06131393 100644 --- a/packages/tempo/index.md +++ b/packages/tempo/index.md @@ -8,14 +8,38 @@ import { withBase } from 'vitepress' const logoUrl = withBase('/logo.svg') const getStartedUrl = withBase('/README') + +// --- Clock State --- const hDeg = ref(0) const mDeg = ref(0) const sDeg = ref(0) -const timeStr = ref('') +const timeStr = ref('Loading...') const tzStr = ref('') +// --- Carousel State --- +const activeIndex = ref(0) +const isPaused = ref(false) +const transitionEnabled = ref(true) + +const features = [ + { title: 'Zero-Cost', details: 'Lazy evaluation and smart matching ensure instantiation overhead is near-zero.', icon: '⚡' }, + { title: '#friday.last', details: 'Natural language parsing for business cycles. Resolve complex terms with zero configuration.', icon: '🎯' }, + { title: 'Cycle Persistence', details: 'Shift by semantic terms while preserving your relative day-of-period offset.', icon: '🔄' }, + { title: 'Tempo.ticker()', details: 'State-of-the-art timing engine with AsyncGenerator support and auto-adjusting TimeZones.', icon: '⏱️' }, + { title: 'Temporal Inside', details: 'Built on the ECMAScript Temporal API. Inherit the reliability of the future standard.', icon: '🏗️' }, + { title: 'Monorepo Resilient', details: 'Built for stability in complex environments with proxy-protected registries.', icon: '🛡️' }, + { title: 'Tree-Shakable', details: 'Keep your bundle light. Only import the modules you need—from Fiscal calendars to Tickers.', icon: '📦' }, + { title: 'Business Aware', details: 'Native support for fiscal quarters, years, and seasons. Perfect for financial applications.', icon: '📈' } +] + +// 8 features + 3 clones for a seamless 3-card viewport +const displayFeatures = [...features, ...features.slice(0, 3)] + let isMounted = false let ticker = null +let carouselTimer = null +let fallbackIntervalId = null +let initFailed = false function updateHands(h24, m, s) { const h = h24 % 12 @@ -24,38 +48,137 @@ function updateHands(h24, m, s) { sDeg.value = (s / 60) * 360 } -onMounted(async () => { - isMounted = true - - // Dynamically import Tempo + TickerModule - const [{ Tempo }, { TickerModule }] = await Promise.all([ - import('@magmacomputing/tempo'), - import('@magmacomputing/tempo/ticker'), - ]) - - if (!isMounted) return +// One-time library setup +let initPromise = (async () => { + try { + // HMR Safeguard for development only (stripped in production) + let registry, originalHas + if (import.meta.env.DEV) { + const registryKey = Symbol.for('$LibrarySerializerRegistry') + registry = globalThis[registryKey] ??= new Map() + // HMR Workaround: Temporarily bypass registry presence checks to allow class re-hydration + originalHas = registry.has.bind(registry) + registry.has = () => false + } + + const [{ Tempo }, { TickerModule }] = await Promise.all([ + import('@magmacomputing/tempo'), + import('@magmacomputing/tempo/ticker'), + ]) + + if (import.meta.env.DEV) registry.has = originalHas + + if (!Tempo.ticker) Tempo.extend(TickerModule) + Tempo.init() + + return Tempo + } catch (e) { + console.error('Tempo failed to initialize:', e) + initFailed = true + initPromise = undefined + throw e + } +})() + +async function startTicker() { + if (initFailed) return + try { + if (!initPromise) return + const Tempo = await initPromise + if (!isMounted) return + + ticker?.stop() + if (fallbackIntervalId) clearInterval(fallbackIntervalId) + + const sync = (t) => { + const dt = t.toDateTime() + updateHands(dt.hour, dt.minute, dt.second) + timeStr.value = t.format('{www}, {yyyy}-{mmm}-{dd} {hh}:{mi}:{ss}') + tzStr.value = t.tz + } + + sync(new Tempo()) + ticker = Tempo.ticker({ seconds: 1 }, sync) + } catch (e) { + timeStr.value = `Error: ${e.message || 'Unknown'}` + const fallback = () => { + const d = new Date() + updateHands(d.getHours(), d.getMinutes(), d.getSeconds()) + timeStr.value = `Fallback: ${d.toLocaleTimeString()} (${e.message})` + tzStr.value = Intl.DateTimeFormat().resolvedOptions().timeZone + } + fallback() + if (fallbackIntervalId) clearInterval(fallbackIntervalId) + fallbackIntervalId = setInterval(fallback, 1000) + } +} - Tempo.extend(TickerModule) +function startCarousel() { + if (carouselTimer) clearInterval(carouselTimer) + carouselTimer = setInterval(() => { + if (!isPaused.value) { + activeIndex.value++ + // Seamless loop: if we hit the start of the clones, wait for slide then snap + if (activeIndex.value >= features.length) { + setTimeout(() => { + if (!isMounted) return + transitionEnabled.value = false + activeIndex.value = 0 + setTimeout(() => { transitionEnabled.value = true }, 50) + }, 850) + } + } + }, 4000) +} - // Initial update - const now = new Tempo() - updateHands(now.hh, now.mi, now.ss) - timeStr.value = now.format('{www}, {yyyy}-{mmm}-{dd} {hh}:{mi}:{ss}') - tzStr.value = now.tz +function handleVisibility() { + if (document.visibilityState === 'visible') { + startTicker() + startCarousel() + } else { + ticker?.stop() + if (fallbackIntervalId) clearInterval(fallbackIntervalId) + clearInterval(carouselTimer) + carouselTimer = null + } +} - // Continuous ticker - ticker = Tempo.ticker({ seconds: 1 }, (t) => { - updateHands(t.hh, t.mi, t.ss) - timeStr.value = t.format('{www}, {yyyy}-{mmm}-{dd} {hh}:{mi}:{ss}') - tzStr.value = t.tz - }) +onMounted(() => { + isMounted = true + startTicker() + startCarousel() + document.addEventListener('visibilitychange', handleVisibility) }) onUnmounted(() => { isMounted = false ticker?.stop() - ticker = null + if (fallbackIntervalId) clearInterval(fallbackIntervalId) + clearInterval(carouselTimer) + document.removeEventListener('visibilitychange', handleVisibility) }) + +// --- A11y & Keyboard Controls --- +const featureRefs = ref([]) + +function handleKeydown(e) { + if (e.key === 'ArrowLeft') { + e.preventDefault() + if (activeIndex.value > 0) activeIndex.value-- + focusActiveCard() + } else if (e.key === 'ArrowRight') { + e.preventDefault() + if (activeIndex.value < features.length - 1) activeIndex.value++ + focusActiveCard() + } +} + +function focusActiveCard() { + setTimeout(() => { + const el = featureRefs.value[activeIndex.value] + if (el) el.focus() + }, 100) +}
@@ -102,18 +225,39 @@ onUnmounted(() => {
-
-
-

Zero-Cost Constructor

-

Lazy evaluation and smart matching ensure instantiation overhead is near-zero, even with massive plugin lists.

-
-
-

Relational Math

-

Shift by semantic terms (Quarters, Seasons, Periods) while preserving your relative cycle offset.

+