Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
17 changes: 6 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tempo-monorepo",
"version": "2.10.0",
"version": "2.10.1",
"private": true,
"description": "Magma Computing Monorepo",
"repository": {
Expand All @@ -17,6 +17,7 @@
"clean": "tsc -b --clean",
"version:sync": "node -e \"require('child_process').execSync('npm version ' + process.env.npm_package_version + ' -w @magmacomputing/tempo -w @magmacomputing/library --no-git-tag-version', {stdio:'inherit'})\"",
"repl": "npm run repl --workspace=@magmacomputing/tempo",
"repl:dist": "npm run repl:dist --workspace=@magmacomputing/tempo",
"core": "npm run core --workspace=@magmacomputing/tempo",
"docs:dev": "npm run docs:dev --workspace=@magmacomputing/tempo",
"docs:build": "npm run docs:build --workspace=@magmacomputing/tempo",
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.10.0",
"version": "2.10.1",
"description": "Shared utility library for Tempo",
"author": "Magma Computing Solutions",
"license": "MIT",
Expand Down
20 changes: 19 additions & 1 deletion packages/library/src/common/proxy.library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { sym } from '#library/symbol.library.js';
import { allObject } from '#library/reflection.library.js';
import { deepFreeze } from '#library/utility.library.js';
import { unwrap } from '#library/primitive.library.js';
import { isFunction, isSymbol, isDefined } from '#library/assertion.library.js';
import { isString, isFunction, isSymbol, isDefined } from '#library/assertion.library.js';
import { registerType, type Constructor } from '#library/type.library.js';

const boundMethodCache = new WeakMap<Function, WeakMap<object, Function>>();
Expand Down Expand Up @@ -172,3 +172,21 @@ export function delegator<K extends string | symbol>(keys: K[] | Record<K, any>,
const keyList = Array.isArray(keys) ? keys : Reflect.ownKeys(keys) as K[];
return factory({} as any, { keys: keyList, onGet: fn as any, frozen: true });
}

/**
* ## indexedArray
* Augments a standard array with a Proxy-based lookup delegate.
* Allows index/enumerable array methods to function natively (e.g. map, filter, [0], length),
* while redirecting non-numeric string keys to a custom finder function to lookup items.
*/
export function indexedArray<T extends object>(
list: T[],
finder: (key: string) => T | undefined,
readonly = true
): T[] & Record<string, T> {
return delegate(list, (key) => {
return (isString(key) && key !== 'length' && !(key in Array.prototype) && isNaN(Number(key)))
? finder(key)
: undefined;
}, readonly) as any;
}
8 changes: 7 additions & 1 deletion packages/library/src/common/storage.library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function getStorage<T>(key?: string, dflt?: T): T | undefined {
}

return isString(store)
? objectify<T>(store) // rebuild object from its stringified representation
? objectify<T>(store) // rebuild object from its stringified representation
: dflt;
}

Expand All @@ -87,6 +87,12 @@ export function setStorage<T>(key: string, val?: T) {
: (delete context.global.process.env[key])
break;

case CONTEXT.Deno:
set
? context.global.Deno.env.set(key, stash)
: context.global.Deno.env.delete(key);
break;

case CONTEXT.GoogleAppsScript:
set
? context.global.PropertiesService?.getUserProperties().setProperty(key, stash)
Expand Down
34 changes: 15 additions & 19 deletions packages/tempo/bin/resolve-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ const DIST_DIR = path.resolve('dist');
const LIB_SRC_DIR = path.resolve('../library/dist/common');
const LIB_DEST_DIR = path.resolve(DIST_DIR, 'lib');

const LIC_SRC_DIR = path.resolve('../../../tempo-plugin/packages/@core/dist');
const __dirname = path.dirname(new URL(import.meta.url).pathname);
const LIC_SRC_DIR = path.resolve(__dirname, '../../../../tempo-plugin/internal/@core/dist');

if (!fs.existsSync(LIC_SRC_DIR)) {
console.error(`\n⚠️ ERROR: External license directory not found: ${LIC_SRC_DIR}`);
console.error(`⚠️ Cannot build premium module without proprietary @core types.\n`);
process.exit(1);
}

const LIC_DEST_DIR = path.resolve(DIST_DIR, 'lic');

console.log('Resolving type definitions...');
Expand All @@ -38,25 +46,13 @@ usedModules.forEach(mod => {
});

// 4. Copy licensing core types
if (fs.existsSync(LIC_SRC_DIR)) {
if (!fs.existsSync(LIC_DEST_DIR)) fs.mkdirSync(LIC_DEST_DIR, { recursive: true });
const licFiles = fs.readdirSync(LIC_SRC_DIR).filter(f => f.endsWith('.d.ts'));
licFiles.forEach(file => {
fs.copyFileSync(path.join(LIC_SRC_DIR, file), path.join(LIC_DEST_DIR, file));
});
} else {
console.warn(`\n⚠️ WARNING: External license directory not found: ${LIC_SRC_DIR}`);
console.warn(`⚠️ Creating fallback minimal types in ${LIC_DEST_DIR}\n`);
if (!fs.existsSync(LIC_DEST_DIR)) fs.mkdirSync(LIC_DEST_DIR, { recursive: true });
const fallbackSrc = path.join(DIST_DIR, 'support', 'support.license.d.ts');
if (fs.existsSync(fallbackSrc)) {
fs.copyFileSync(fallbackSrc, path.join(LIC_DEST_DIR, 'index.d.ts'));
} else {
fs.writeFileSync(path.join(LIC_DEST_DIR, 'index.d.ts'), 'export {};\n');
}
}
if (!fs.existsSync(LIC_DEST_DIR)) fs.mkdirSync(LIC_DEST_DIR, { recursive: true });
const licFiles = fs.readdirSync(LIC_SRC_DIR).filter(f => f.endsWith('.d.ts'));
licFiles.forEach(file => {
fs.copyFileSync(path.join(LIC_SRC_DIR, file), path.join(LIC_DEST_DIR, file));
});

// 4. Walk through all .d.ts files in dist/ to rewrite aliases
// 5. Walk through all .d.ts files in dist/ to rewrite aliases
function walk(dir: string) {
const files = fs.readdirSync(dir);
for (const file of files) {
Expand Down
9 changes: 8 additions & 1 deletion packages/tempo/doc/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ If you are choosing a date library today, you are likely looking at **Day.js**,
| **Foundation** | **Native Temporal** | Native Temporal | Legacy `Date` | Legacy `Intl` + `Date` | Legacy `Date` |
| **Precision** | **Nanoseconds** | Nanoseconds | Milliseconds | Milliseconds | Milliseconds |
| **Parsing** | **Human-Centric** | Strict ISO Only | Strict / Plugin | Strict | Modular / Strict |
| **Formatting** | **Smart Tokens & Getters** | Verbose `Intl` Only | Token-Based (Moment style) | Token-Based (Unicode) | Token-Based (Unicode helper) |
| **Business Logic** | **Terms System** | Manual Math | Manual Math | Manual Math | Manual Math |
| **Time Zones** | **First-Class** | First-Class | Plugin-based | Built-in | Separate Lib |
| **Future-Proof** | **100% (Native)** | 100% (Native) | Deprecated/Legacy | Legacy Bridge | Legacy Bridge |
Expand All @@ -22,7 +23,7 @@ If you are choosing a date library today, you are likely looking at **Day.js**,
## 💎 Why Tempo Wins

### 1. The "Terms" Engine (Business Intelligence)
Most libraries stop at "adding 2 days." Tempo introduces the **Terms** system, allowing you to encode domain-specific logic (Fiscal Quarters, Meteorological Seasons, Academic terms, Zodiac Signs) directly into the tempo `term` object.
Most libraries stop at "adding 2 days." Tempo introduces the **Terms** system, allowing you to encode domain-specific logic (Fiscal Quarters, Meteorological Seasons, Academic terms, Zodiac Signs) directly into the Tempo `term` object.
> *Competition:* You have to write custom utility functions and import them everywhere.

### 2. Human-Centric Parsing
Expand All @@ -36,6 +37,12 @@ Native `Date` (and thus Day.js/Luxon/date-fns) is limited to milliseconds. For h
### 4. Zero "Leaky Abstractions"
When you use a legacy library, you are often fighting the weirdness of the 1995 `Date` object (like months being 0-indexed). Tempo is built on `Temporal`, which was designed from the ground up to be mathematically sound and developer-friendly.

### 5. Intelligent Formatting & Shorthands
While native Temporal is highly precise, formatting dates for a UI in native Temporal is extremely verbose. It relies entirely on the native `Intl` API, requiring you to construct complex option objects or use long `.toLocaleString()` boilerplate just to output simple strings.

Tempo solves this by offering a **Smart Token Engine** (using `{yyyy}-{mm}-{dd}` placeholders) alongside built-in, highly optimized format getters like `.fmt.date`, `.fmt.time`, or `.fmt.dateTime`. In addition, Tempo automatically memoizes internal formatters under the hood, delivering top-tier performance without developer boilerplate.
> *Competition:* Raw Temporal requires verbose `Intl` configurations; legacy libraries require large plugins or separate helper imports.

---

## Which should you choose?
Expand Down
17 changes: 16 additions & 1 deletion packages/tempo/doc/releases/v2.x.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# 📜 Version 2.x History

## [v2.10.0] = 2026-05-20
## [v2.10.11] - 2026-05-23
### New Features

- Added support for license key discovery via global browser context
- Enhanced license object output with formatted timestamps
- Improved terms registry with better key-based indexing
- Introduced Free Showcase Astronomical Seasons premium plugin option

### Documentation

- Expanded license application guidance for browser, Node.js, and micro-frontend environments
- Added network synchronization and offline behavior documentation
- Clarified logStamp format customization examples
- Improved relative time and duration string documentation

## [v2.10.0] - 2026-05-20
### New Features

- Added licensing system with JWT validation and revocation checks
Expand Down
54 changes: 45 additions & 9 deletions packages/tempo/doc/tempo-vs-temporal.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ date.toLocaleString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }

**Tempo 🚀**
```javascript
const t = new Tempo();
const t = new Tempo('2026-01-24T12:00:00');

// Use the format method to create custom formats, or use the pre-built getters (on the 'fmt' property)
t.format('{dd} {mmm} {yyyy}'); // Output: "24 Jan 2026"
Expand All @@ -67,11 +67,11 @@ const fiscalQuarter = `Q${Math.ceil(month / 3)}`; // Manual math
Tempo solves this elegantly using the **Terms** plugin system. Terms are lazy-loaded plugins that evaluate the current date against semantic boundaries without adding memory bloat.

```javascript
const t = new Tempo();
const t = new Tempo('2026-01-24T12:00:00', { sphere: 'north' });

// Built-in complex Terms via the standard plugin
t.term.qtr; // → 'Q1' (Calculates fiscal quarter)
t.term.szn; // → 'Summer' (Calculates meteorological season, respecting hemisphere)
t.term.szn; // → 'Winter' (Calculates meteorological season, respecting hemisphere)
```

For more information on adding your own business logic, see the [Terms Guide](tempo.term.md).
Expand All @@ -80,6 +80,21 @@ For more information on adding your own business logic, see the [Terms Guide](te

Calculating the difference between two dates in native Temporal is mathematically sound, but it strictly returns a `Temporal.Duration` object. Tempo gives you the flexibility to return a `Duration` object, a precise floating-point number, or a human-readable string.

Tempo also provides a built-in **log-stamp** format for dropping a compact, sortable timestamp into a log entry:

```javascript
const t = new Tempo('2026-05-20T13:55:19.623319620');
t.fmt.logStamp; // → "20260520T135519.623319620"
// ^^^^^^^^ ^^^^^^ ^^^^^^^^^
// date time sub-seconds (nanosecond precision)
```

This format (`Tempo.FORMAT.logStamp`) is configurable via `Tempo.init`:
```javascript
Tempo.init({ formats: { logStamp: '{yyyy}-{mm}-{dd} {hh}:{mi}:{ss}' } });
new Tempo('2026-05-20T13:55:19.623319620').fmt.logStamp; // → "2026-05-20 13:55:19"
```

Comment thread
magmacomputing marked this conversation as resolved.
**Native Temporal 🐢**
```javascript
const now = Temporal.Now.plainDateTimeISO();
Expand All @@ -91,18 +106,39 @@ const duration = now.until(target); // Returns a complex Duration object
Tempo understands natural language targets and can format the resulting difference flexibly. `t.until()` and `t.since()` have distinct return types:

```javascript
const t = new Tempo();
const t = new Tempo('2026-05-20T11:35:33');
const t2 = new Tempo('2026-05-22T11:35:33');

// t.until(target, unit) → number (precise floating-point)
t.until('afternoon', 'minutes'); // → 302.57749424408334
t.until('xmas', 'days'); // → 289
// t.until(target, unit) → number
t.until('afternoon', 'minutes'); // → 84.45 (fractional: 'afternoon' has a fixed time, e.g. 13:00)
t.until('xmas', 'days'); // → 219 (whole number — see note below)
t.until('xmas', 'weeks'); // → 31.28 (fractional — weeks don't divide evenly into days)
t.until(t2, 'hours'); // → 48 (targets can also be other Tempo instances)
t.until(Temporal.Now.plainDateISO(), 'days'); // → 1 (same day = 1 day)

// t.until(target) → Tempo.Duration object (with .iso, .years, .days, … fields)
t.until('xmas'); // → { iso: "P289DT14H22M9.102S", years: 0, months: 9, days: 14, ... }
t.until('xmas'); // → { iso: "P219DT0H0M0S", years: 0, months: 7, days: 4, ... }

// t.since(target, unit) → human-readable string via Intl.RelativeTimeFormat
t.since('yesterday', 'days'); // → "1d ago"

// t.since(target) → ISO 8601 Duration string
t.since('yesterday afternoon'); // → "-P1DT9H32M19.402S"
```
```

> **💡 Date-only targets inherit the current time.**
> When a target resolves to a **date without a time component** (e.g. `'xmas'`, `'tomorrow'`, `'next friday'`),
> Tempo copies the current time-of-day from the anchor into the target. This means:
>
> - `t.until('xmas', 'days')` → a **whole number** — the time components cancel out exactly.
> - `t.until('xmas', 'hours')` → a **whole number** — same reason.
> - `t.until('xmas', 'weeks')` → **fractional** — 219 days does not divide evenly into weeks.
>
> This matches natural-language intuition: *"How many days until Christmas?"* expects `219`, not `219.43`.
> If you need the precise elapsed duration including sub-day components, omit the unit:
> ```javascript
> t.until('xmas'); // → { days: 219, hours: 0, minutes: 0, seconds: 0, ... }
> ```
>
> Targets with an **explicit time** (e.g. `'afternoon'`, `'9am'`, `'2026-12-25T08:00'`) always produce
> fractional values because the target time differs from the anchor's current time-of-day.
16 changes: 8 additions & 8 deletions packages/tempo/doc/tempo.cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ A collection of recipes for solving common date and time challenges using Tempo.
## Table of Contents
1. [The Basics](#the-basics)
2. [Parsing Challenges](#parsing-challenges)
3. [Manipulation & Calculations](#manipulation--calculations)
4. [Timezones & Locales](#timezones--locales)
5. [Business Logic & Terms](#business-logic--terms)
3. [Manipulation and Calculations](#manipulation-and-calculations)
4. [Timezones and Locales](#timezones-and-locales)
5. [Business Logic and Terms](#business-logic-and-terms)
6. [Interoperability](#interoperability)

---
Expand Down Expand Up @@ -37,7 +37,7 @@ if (t.isValid) {
}
```

### Global Configuration & Initialization
### Global Configuration and Initialization
You can initialize global defaults that apply to all future `Tempo` instances.
```typescript
Tempo.init({
Expand Down Expand Up @@ -108,7 +108,7 @@ new Tempo(1716163200000000000n); // Nanoseconds

---

## Manipulation & Calculations
## Manipulation and Calculations

### Add or Subtract Time
Tempo instances are immutable; `add()` returns a new instance.
Expand Down Expand Up @@ -143,7 +143,7 @@ const qtrMid = new Tempo().set({ mid: '#qtr' });

---

## Timezones & Locales
## Timezones and Locales

### Convert Time to Another Zone
```typescript
Expand Down Expand Up @@ -175,7 +175,7 @@ for (const entry of logEntries) {

---

## Business Logic & Terms
## Business Logic and Terms

### Is it the weekend?
```typescript
Expand Down Expand Up @@ -297,7 +297,7 @@ const tempo = new Tempo(new Date());
```typescript
const zdt = new Tempo().toDateTime(); // Temporal.ZonedDateTime
const instant = new Tempo().toInstant(); // Temporal.Instant
const pDate = new Tempo().toPlainDate(); // Temporal.PlainDate
const pdt = new Tempo().toPlainDate(); // Temporal.PlainDate
```

### Sorting an array of Tempos
Expand Down
Loading
Loading