Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ reviews:
# Use "! " at the start of a pattern to EXCLUDE it from the review
- "!**/doc/api/**" # Ignore ONLY the generated TypeDoc API documentation
- "!**/docs/**" # Ignore other generated documentation artifacts
- "!**/.vitepress/cache/**" # Ignore VitePress cache
- "!**/.vitepress/dist/**" # Ignore VitePress build output
- "!**/.vitepress/cache/**" # Ignore VitePress cache
- "!**/.vitepress/dist/**" # Ignore VitePress build output
- "!**/dist/**" # Ignore all build artifacts
- "!**/vendor/**" # Ignore third-party code
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tempo-monorepo",
"version": "2.2.6",
"version": "2.3.0",
"private": true,
"description": "Magma Computing Monorepo",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/library/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@magmacomputing/library",
"version": "2.2.6",
"version": "2.3.0",
"description": "Shared utility library for Tempo",
"author": "Magma Computing Solutions",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/library/src/common/pledge.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ declare module '#library/type.library.js' {
export class Pledge<T> {
#pledge: PromiseWithResolvers<T>;
#status = {} as Pledge.Status<T>;
static #dbg = new Logify('Pledge: ');
static #dbg = new Logify('Pledge');
static #static = {} as Pledge.Constructor;

static STATE = secure({
Expand Down
2 changes: 2 additions & 0 deletions packages/tempo/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default defineConfig({
text: 'Getting Started',
items: [
{ text: 'Introduction', link: '/README' },
{ text: 'Installation', link: '/doc/installation' },
{ text: 'Cookbook', link: '/doc/tempo.cookbook' },
{ text: 'Migration Guide', link: '/doc/migration-guide' },
{ text: 'Release Notes', link: '/doc/releases/' }
Expand All @@ -34,6 +35,7 @@ export default defineConfig({
text: 'Core Concepts',
items: [
{ text: 'Configuration', link: '/doc/tempo.config' },
{ text: 'Smart Parsing', link: '/doc/tempo.parse' },
{ text: 'Modularity', link: '/doc/tempo.modularity' },
{ text: 'Layout Patterns', link: '/doc/tempo.layout' },
{ text: 'Terms System', link: '/doc/tempo.term' },
Expand Down
16 changes: 16 additions & 0 deletions packages/tempo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ 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.3.0] - 2026-04-22

### Added
- **Standalone Parse Support**: Enhanced the `ParseModule` to support standalone parsing of textual dates (including names like "Jan") without requiring a bound host class instance.
- **Backtracking Security**: Implemented suspicious quantifier detection (`Match.backtrack`) in the snippet registry to prevent catastrophic backtracking and malicious regex patterns.
- **Automatic Sphere Sync**: The engine now automatically recalculates the `config.sphere` (hemisphere) state whenever the `timeZone` is updated in the configuration.

### Changed
- **Heading Hierarchy**: Restructured the documentation and README headers to use a sequential H2-based hierarchy for improved accessibility and document flow.

### Fixed
- **Infinite Loop Protection**: Added safety-valve logic to the term resolution engine to prevent infinite loops when traversing large date ranges.
- **Parse Error Resilience**: Hardened the resolution engine to explicitly detect and log `undefined` results from the parser, ensuring `isValid` correctly reflects the parse state and preventing silent UTC fallbacks.
- **Standalone Resilience**: Added optional chaining to all host class references in the term resolver to prevent `TypeError` in standalone contexts.
- **Type-Safe Configuration**: Updated the `Options` type to strictly isolate parse-time-only properties from runtime state.

## [2.2.6] - 2026-04-20

### Added
Expand Down
189 changes: 52 additions & 137 deletions packages/tempo/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<table width="100%">
<table>
<tbody>
<tr>
<td align="left" width="120">
<img src="./img/logo.svg" width="120" alt="Tempo logo">
<td width="100" valign="top">
<img src="./img/logo.svg" width="90" height="90" alt="Tempo Logo">
</td>
<td align="left" valign="middle">
<h1><font color="#3498db">Tempo</font></h1>
<p><strong>The Professional Date-Time Library for the Temporal API</strong></p>
<td valign="middle">
<h1 style="border-bottom: none; margin-bottom: 0;">Tempo</h1>
<p style="font-weight: 600; font-size: 1.1rem; color: #2c3e50; margin-top: 0;">The Professional Date-Time Library for the Temporal API</p>
</td>
</tr>
</tbody>
</table>

<br>

**Tempo** is a premium, high-performance wrapper around the JavaScript `Temporal` API. It provides a modern, **immutable**, and **fluent** interface for date-time manipulation, and flexible parsing. It's designed as a better-performing, type-safe alternative to legacy libraries like **Moment.js**, **Day.js**, and **Luxon**.
**Tempo** is a premium, high-performance wrapper for the ECMAScript `Temporal` API. Designed for professionals, it combines **immutable** state-management with a **fluent**, natural-language engine. It is the modern, type-safe successor to legacy libraries like Moment.js and Luxon.

<div align="center">
<table>


<table align="center">
<tbody>
<tr>
<td align="center"><a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a></td>
Expand All @@ -28,121 +28,72 @@
</tr>
</tbody>
</table>
</div>

## 🚀 Overview
Working with `Date` in JavaScript has historically been painful. The new `Temporal` standard (Stage 4) fixes this, but it can be verbose and strict when parsing strings.

**Tempo** bridges that gap by providing:
- **Flexible Parsing**: Interprets almost any date string, including relative ones like "next Friday".
- **Natural Language**: Supports word-based numbers (0-10) in relative parsing (e.g., "two days ago").
- **Fluent API**: Chainable methods for adding, subtracting, and setting date-times (similar to Moment.js).
- **Formatting**: Use custom tokens to format date-times in a way that is both intuitive and flexible.
- **Plugin**: Extend core functionality safely; all extensions (including the Ticker) are opted-into via side-effect imports or explicit registration, ensuring a lean footprint even in the full package.
- **Terms**: Access complex date ranges (Quarters, Seasons, Zodiacs) easily.
- **Immutable**: Operations (like `set` and `add`) return a new `Tempo` instance, ensuring thread safety and predictability.
## 🤔 Why Tempo?
If you're looking for a **modern date library** that leverages the native power of the browser's `Temporal` API, Tempo is for you.

- **Type Safety**: Built from the ground up with TypeScript.
- **Performance**: High-performance wrapper with minimal overhead.
- **Familiarity**: If you like the fluent syntax of **Moment** or **Day.js**, you'll feel right at home.
- **Future-Proof**: Built on the TC39 `Temporal` standard.
## 🎯 Target Audience

Tempo is built for **modern JavaScript developers** who require a premium, type-safe, and developer-friendly interface over the native Temporal API. It is ideal for those migrating from legacy libraries like **Moment.js**, **Day.js**, or **Luxon**, as well as teams building complex, time-sensitive applications that demand reliability, immutability, and high-performance parsing.
Tempo is designed for a broad spectrum of developers and teams who interact with date and time data in JavaScript:

### 1. Modern JavaScript Developers
For those who want to leverage the power of the native `Temporal` API today but find its raw implementation too verbose or strict for rapid development.

### 2. Teams Migrating from Legacy Libraries
Ideal for organizations looking to move away from **Moment.js**, **Day.js**, or **Luxon** without sacrificing the fluent, chainable API and flexible parsing on which they've come to rely.

### 3. Enterprise Application Architects
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
```

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

## ⚡ Quick Start
```javascript
import { Tempo } from '@magmacomputing/tempo';

const t = new Tempo('next Friday');
console.log(t.format('{dd} {mon} {yyyy}'));
// 🎯 Natural Language Parsing
const event = new Tempo('next Friday 3pm');

// 🔄 Fluent Mutations (Immutable)
const reminder = event.add({ hours: 2 }).set({ minute: 0 });

// ⏳ Comparative Durations
const diff = event.until('next month');
console.log(diff.toString()); // e.g. P3W2D

// 📝 Beautiful Formatting
console.log(event.format('{mon} {day}, {year}')); // e.g. Oct 24, 2026
```

---

## 📦 Installation

```bash
npm install @magmacomputing/tempo # npm
yarn add @magmacomputing/tempo # yarn
pnpm add @magmacomputing/tempo # pnpm
bun add @magmacomputing/tempo # bun
deno add npm:@magmacomputing/tempo # deno
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### 🌐 in the Browser (Import Maps)
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.
<details>
<summary><b>🌐 Browser & Lite Environments</b></summary>

For modern browsers using **Import Maps**:
```html
<script type="importmap">
{
"imports": {
"jsbi": "https://cdn.jsdelivr.net/npm/jsbi@4.3.0/dist/jsbi.mjs",
"@js-temporal/polyfill": "https://cdn.jsdelivr.net/npm/@js-temporal/polyfill@0.5/dist/index.esm.js",
"@magmacomputing/tempo": "https://cdn.jsdelivr.net/npm/@magmacomputing/tempo@2/dist/tempo.bundle.esm.js"
}
}
</script>
<script type="module">
import '@js-temporal/polyfill';
import { Tempo } from '@magmacomputing/tempo';
const t = new Tempo('next friday');
console.log(t.format('{mon} {day}'));
</script>
```

### 📦 in the Browser (Script Tag)
For environments without `importmap` support or simple prototypes, use the global bundle. This automatically attaches the `Tempo` class to the `window` object.

For rapid prototyping without a package manager (UMD):
```html
<script src="https://cdn.jsdelivr.net/npm/@js-temporal/polyfill@0.5/dist/index.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@magmacomputing/tempo@2/dist/tempo.bundle.js"></script>
<script>
const t = new Tempo('tomorrow');
console.log(t.toString());
</script>
```

### 🧪 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.
For granular "Lite" builds, see the [Full Installation Guide](https://magmacomputing.github.io/magma/doc/installation).
</details>

```html
<script type="importmap">
{
"imports": {
"jsbi": "https://cdn.jsdelivr.net/npm/jsbi@4.3.0/dist/jsbi.mjs",
"@js-temporal/polyfill": "https://cdn.jsdelivr.net/npm/@js-temporal/polyfill@0.5/dist/index.esm.js",
"@magmacomputing/tempo/core": "https://cdn.jsdelivr.net/npm/@magmacomputing/tempo@2/dist/core.index.js",
"@magmacomputing/tempo/mutate": "https://cdn.jsdelivr.net/npm/@magmacomputing/tempo@2/dist/plugin/module/module.mutate.js",
"@magmacomputing/library": "https://cdn.jsdelivr.net/npm/@magmacomputing/library@2/dist/common.index.js"
}
}
</script>
<script type="module">
import '@js-temporal/polyfill';
import { Tempo } from '@magmacomputing/tempo/core';
import { MutateModule } from '@magmacomputing/tempo/mutate';

// Opt-in to mutation logic
Tempo.extend(MutateModule);

const t = new Tempo().add({ days: 1 });
console.log(t.toString());
</script>
```
---

> [!TIP]
> **CDN Versioning**: The examples above use pinned versions (`@magmacomputing/tempo@2`, `@magmacomputing/library@2`, `@js-temporal/polyfill@0.5`) for production stability. To use the latest releases, you can omit the version string from every URL (e.g., remove `@2` from all Magma entries and `@0.5` from the polyfill). Ensure all `@magmacomputing/...` entries resolve to the same release to avoid mixed-version loading.
## ✨ Why Tempo?
* **🏗️ Future Standard**: Built natively on the TC39 `Temporal` proposal. Inherit the reliability of the future standard.
* **🗣️ Natural Language**: Resolve complex terms like `#friday.last` or "two days ago" with zero configuration.
* **🔄 Cycle Persistence**: Shift by semantic terms (Quarters, Seasons) while preserving your relative day-of-period offset.
* **⚡ Zero-Cost Parsing**: Lazy evaluation and smart matching ensure instantiation overhead is near-zero.
* **🛡️ Monorepo Resilient**: Built for stability in complex environments with proxy-protected registries.
* **📦 Tree-Shakable**: Keep your bundle light. Only load what you need—from Fiscal calendars to high-performance Tickers.

---

Expand All @@ -154,59 +105,23 @@ For a deeper dive into the API, architecture, and advanced features:
* **[Full API Reference Guide](https://magmacomputing.github.io/magma/doc/tempo.api)** — Detailed technical documentation for every class and method.

---
## 🛠️ Quick Start

```javascript
import { Tempo } from '@magmacomputing/tempo';

// Instantiate
const now = new Tempo();
const birthday = new Tempo('20-May-1990');
const nextWeek = new Tempo('next Monday');

// Manipulate
const later = now.add({ days: 3, hours: 2 });
const startOfMonth = now.set({ start: 'month' });

// Format
console.log(now.format('{dd} {mmm} {yyyy}')); // using custom format with tokens: "24 Jan 2026"
console.log(now.fmt.date); // using pre-built formats: "2026-01-24"
```


## 💬 Contact & Support

If you have a question, find a bug, or want to suggest a new feature:

1. **Bug Reports & Features**: Please open an [Issue](https://github.com/magmacomputing/magma/issues).
2. **Questions & Ideas**: Start a thread in [Discussions](https://github.com/magmacomputing/magma/discussions).
3. **Direct Contact**: You can reach me at `hello@magmacomputing.com.au`.

## 🛡️ Privacy & Transparency

We value your privacy. **Tempo** does not include any runtime telemetry or "phone-home" features.
Tempo will never make network requests from your application.
---

## 🗳️ Feedback & Reactions

How are we doing? Let us know with a simple reaction!
*(This will open a pre-filled GitHub Issue)*

[🚀 Premium!](https://github.com/magmacomputing/magma/issues/new?title=Feedback:%20🚀%20Premium!) &nbsp; | &nbsp;
[⭐ Loving it!](https://github.com/magmacomputing/magma/issues/new?title=Feedback:%20⭐%20Loving%20it!) &nbsp; | &nbsp;
[💡 Needs work](https://github.com/magmacomputing/magma/issues/new?title=Feedback:%20💡%20Needs%20work) &nbsp; | &nbsp;
[🐞 Found a bug](https://github.com/magmacomputing/magma/issues/new?title=Feedback:%20🐞%20Found%20a%20bug)

### ⚡ Quick Reactions
*(Native reactions available in [Discussions](https://github.com/magmacomputing/magma/discussions/categories/feedback))*

[👍 Like](https://github.com/magmacomputing/magma/discussions/categories/feedback) &nbsp; | &nbsp;
[❤️ Love](https://github.com/magmacomputing/magma/discussions/categories/feedback) &nbsp; | &nbsp;
[😄 Haha](https://github.com/magmacomputing/magma/discussions/categories/feedback) &nbsp; | &nbsp;
[😮 Wow](https://github.com/magmacomputing/magma/discussions/categories/feedback) &nbsp; | &nbsp;
[😢 Sad](https://github.com/magmacomputing/magma/discussions/categories/feedback) &nbsp; | &nbsp;
[😡 Angry](https://github.com/magmacomputing/magma/discussions/categories/feedback) &nbsp; | &nbsp;
[💩 Poop](https://github.com/magmacomputing/magma/discussions/categories/feedback)
---

## ⚖️ License

Expand Down
1 change: 1 addition & 0 deletions packages/tempo/bin/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const resetIdle = () => {
console.warn('\n\x1b[33m[Tempo] REPL idle for 1 hour. Safety shutdown triggered.\x1b[0m');
process.exit(0);
}, 3600 * 1000);
idleTimer.unref();
};

process.stdin.on('data', resetIdle);
Expand Down
23 changes: 23 additions & 0 deletions packages/tempo/bin/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { parse } from '#tempo/parse';

// Pre-load core symbols (parse) to the global scope
Object.assign(globalThis, { parse });

console.log(`\n\x1b[38;2;252;194;1m\x1b[1m ⏳ Tempo (parse) \x1b[0m\x1b[38;2;45;212;191mREPL initialized (parse only).\x1b[0m\n`);

/**
* 💡 SMART IDLE: Auto-exit after 1 hour of keyboard inactivity
* Monitors 'stdin' so background Tickers won't keep the session alive if you walk away.
*/
let idleTimer: NodeJS.Timeout;
const resetIdle = () => {
clearTimeout(idleTimer);
idleTimer = setTimeout(() => {
console.warn('\n\x1b[33m[Tempo] REPL idle for 1 hour. Safety shutdown triggered.\x1b[0m');
process.exit(0);
}, 3600 * 1000);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
idleTimer.unref();
};

process.stdin.on('data', resetIdle);
resetIdle();
1 change: 1 addition & 0 deletions packages/tempo/bin/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const resetIdle = () => {
console.warn('\n\x1b[33m[Tempo] REPL idle for 1 hour. Safety shutdown triggered.\x1b[0m');
process.exit(0);
}, 3600 * 1000);
idleTimer.unref();
};

process.stdin.on('data', resetIdle);
Expand Down
2 changes: 1 addition & 1 deletion packages/tempo/demo/3-modular-granular.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h1>⏳ Demo 3: Modular (Granular)</h1>
<script type="importmap">
{
"imports": {
"@magmacomputing/tempo/core": "../dist/tempo.class.js",
"@magmacomputing/tempo/core": "../dist/core.index.js",
"@magmacomputing/tempo/format": "../dist/plugin/module/module.format.js"
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/tempo/demo/4-error-no-plugin.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ <h1>⏳ Demo 4: Expected Error (No Plugin)</h1>
<script type="importmap">
{
"imports": {
"@magmacomputing/tempo/core": "../dist/tempo.class.js"
"@magmacomputing/tempo/core": "../dist/core.index.js"
}
}
</script>
Expand Down
Loading