Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ Thumbs.db
**/.vitepress/cache/
**/.vitepress/dist/
**/doc/api/

# Secrets and credentials
.env
.env.*
.npmrc.local
.npmrc
!.npmrc
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ 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).

## [Unreleased]
## [2.10.0] - 2026-05-11

### Added
- **Licensing Architecture Preparation**: Initial work to support licensed plugins and features.

### Changed
- **Term Collision Enforcement**: Term plugin registration now throws a fatal error on naming collisions (key/scope) to prevent silent configuration failures.

## [2.9.3] - 2026-05-11

### Added
- **Fractional Resolution**: Numeric inputs now support fractional components with nanosecond precision using BigInt math.
- **Hardened AliasContext**: Improved chainable context for functional aliases with full API parity.
- **Epoch Support**: Enhanced detection for 9-10 digit Epoch timestamps.

### Fixed
- **Mutation Safety**: Eliminated side-effects on global configuration during parsing.
- **Normalizer Memory**: Fixed state leakage in alias resolution.

## [2.8.0] - 2026-04-30

Expand Down
2,288 changes: 1,575 additions & 713 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tempo-monorepo",
"version": "2.9.3",
"version": "2.10.0",
"private": true,
"description": "Magma Computing Monorepo",
"repository": {
Expand All @@ -26,16 +26,16 @@
"devDependencies": {
"@js-temporal/polyfill": "^0.5.1",
"@rollup/plugin-node-resolve": "^16.0.3",
"@types/google.maps": "^3.64.0",
"@types/google.maps": "^3.64.1",
"@types/hammerjs": "^2.0.46",
"@types/jquery": "^4.0.0",
"@types/node": "^25.6.2",
"@types/node": "^25.8.0",
"@vitest/ui": "^2.1.9",
"cross-env": "^10.1.0",
"markdown-it-mathjax3": "^4.3.2",
"rollup": "^4.60.3",
"rollup": "^4.60.4",
"tslib": "^2.8.1",
"tsx": "^4.21.0",
"tsx": "^4.22.0",
"typescript": "^6.0.3",
"vitest": "^2.1.9"
},
Expand Down
21 changes: 21 additions & 0 deletions packages/library/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 4 additions & 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.9.3",
"version": "2.10.0",
"description": "Shared utility library for Tempo",
"author": "Magma Computing Solutions",
"license": "MIT",
Expand All @@ -9,6 +9,9 @@
"url": "git+https://github.com/magmacomputing/magma.git",
"directory": "packages/library"
},
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/magmacomputing/magma/tree/main/packages/library#readme",
"bugs": {
"url": "https://github.com/magmacomputing/magma/issues"
Expand Down
7 changes: 4 additions & 3 deletions packages/library/src/common/storage.library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ export function getStorage<T>(key?: string, dflt?: T): T | undefined {

case CONTEXT.NodeJS:
store = context.global.process.env[key];
if (key === '$Tempo' && !store) {
// skip debug log for production/test clean-up
}
break;

case CONTEXT.Deno:
store = context.global.Deno.env.get(key);
break;

case CONTEXT.GoogleAppsScript:
Expand Down
31 changes: 30 additions & 1 deletion packages/library/src/common/utility.library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ import type { Secure, ValueOf } from '#library/type.library.js';

/** General utility functions */

/** fast, unverified decode of a JWT payload */
export const decodeJWT = <T = any>(jwt: string): T | null => {
try {
const part = jwt.split('.')[1];
if (!part) return null;
// 🛡️ Base64URL Normalization: replace -/_ with +/ and add padding
const base64 = part.replace(/-/g, '+').replace(/_/g, '/').padEnd(part.length + (4 - part.length % 4) % 4, '=');
const payload = typeof atob === 'function' ? atob(base64) : Buffer.from(base64, 'base64').toString();
return JSON.parse(payload);
} catch { return null; }
}

/** portable base64 encoder for universal support */
export const base64Encode = (input: string): string => {
if (typeof Buffer !== 'undefined')
return Buffer.from(input).toString('base64');

const bytes = new TextEncoder().encode(input);
let binary = '';

for (let i = 0; i < bytes.byteLength; i++)
binary += String.fromCharCode(bytes[i]);

return btoa(binary);
}

/** analyze the Call Stack to determine calling Function's name */
export const getCaller = () => {
const stackTrace = new Error().stack // only tested in latest FF and Chrome
Expand Down Expand Up @@ -58,6 +84,9 @@ export const getContext = (): Context => {
if (isDefined(global.window?.document))
return { global, type: CONTEXT.Browser };

if (isDefined(global.Deno))
return { global, type: CONTEXT.Deno };

if (isDefined(global.process?.versions?.node))
return { global, type: CONTEXT.NodeJS };

Expand Down Expand Up @@ -85,7 +114,7 @@ export function deepFreeze<const T extends object>(obj: T, options?: { skip?: We
export function deepFreeze<const T extends object>(obj: T, options?: { skip?: WeakSet<object> } | WeakSet<object>, seen: WeakSet<object> = new WeakSet<object>()): Secure<T> {
// Support both old and new signatures for backward compatibility
const skip = (options instanceof WeakSet) ? options : (options?.skip ?? EMPTY_SKIP);

if (isPrimitive(obj) || Object.isFrozen(obj) || seen.has(obj) || skip.has(obj))
return obj as Secure<T>;

Expand Down
1 change: 1 addition & 0 deletions packages/library/src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"composite": true,
"lib": [
"ESNext",
"ESNext.Temporal",
"DOM"
],
"types": [
Expand Down
6 changes: 0 additions & 6 deletions packages/library/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
],
"#server/*": [
"../src/server/*"
],
"#tempo": [
"../../tempo/src/tempo.index.ts"
],
"#tempo/*": [
"../../tempo/src/*"
]
}
},
Expand Down
6 changes: 0 additions & 6 deletions packages/library/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@
],
"#server/*": [
"./src/server/*"
],
"#tempo": [
"../tempo/src/tempo.index.ts"
],
"#tempo/*": [
"../tempo/src/*"
]
}
},
Expand Down
10 changes: 8 additions & 2 deletions packages/tempo/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,16 @@ export default defineConfig({
{ text: 'Parse Planner', link: '/doc/tempo.planner' },
{ text: 'Regional Parsing (MDY)', link: '/doc/tempo.month-day' },
{ text: 'Smart Formatting', link: '/doc/tempo.format' },
{ text: 'Layout Patterns', link: '/doc/tempo.layout' }
]
},
{
text: 'Extensions & Plugins',
items: [
{ text: 'Modularity', link: '/doc/tempo.modularity' },
{ text: 'Layout Patterns', link: '/doc/tempo.layout' },
{ text: 'Terms System', link: '/doc/tempo.term' },
{ text: 'Ticker Plugin', link: '/doc/tempo.ticker' }
{ text: 'Ticker Plugin', link: '/doc/tempo.ticker' },
{ text: 'License Keys', link: '/doc/tempo.license' }
]
},
{
Expand Down
12 changes: 12 additions & 0 deletions packages/tempo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ 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.10.0] - 2026-05-11

### Added
- **Licensing Architecture**: Implemented a standalone "No-Op" licensing engine (`support.license.ts`) in the public core. This ensures the repository is 100% buildable and testable by the community without private dependencies.
- **Automatic Premium Injection**: Optimized the build pipeline (Rollup/Vitest) to automatically detect and inject the proprietary licensing engine from a side-by-side repository during official builds.
- **Portable Encoding**: Migrated `base64Encode` to the shared library for universal, environment-agnostic token handling.

### Changed
- **Hardened Licensing Resolution**: Updated the term resolution pipeline with a dual-identity race-condition guard (JTI + Key) and a late-binding resolution guard to securely handle `Pending` to `Revoked` state transitions.
- **Decoupled CI Resolution**: Eliminated the need for private registries or stubs in GitHub Actions by utilizing the internal No-Op engine for standard test runs.
- **Term Collision Enforcement**: Term plugin registration now throws a fatal error on naming collisions (key/scope) to prevent silent configuration failures.

## [2.9.3] - 2026-05-11

### Added
Expand Down
2 changes: 1 addition & 1 deletion packages/tempo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ For granular "Lite" builds, see the [Full Installation Guide](https://magmacompu

## ✨ Why Tempo?
* **🏗️ Future Standard**: Built natively on the TC39 `Temporal` proposal. Inherit the reliability of the future standard.
* **🗣️ Natural Language**: Resolve complex terms like `#quarter.last` or "two days ago" with zero configuration.
* **🗣️ Natural Language**: Resolve complex terms like "two days ago" with zero configuration.
* **🧠 Functional Aliases**: Extend the parser with custom logic using a powerful resolution context for relative date math.
* **🔄 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.
Expand Down
28 changes: 28 additions & 0 deletions packages/tempo/bin/astro-repl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Tempo, enums } from '#tempo';
import { stringify, objectify, enumify, getType, Pledge } from '#library';
import { AstroTerm } from '/home/michael/Project/tempo-plugin/packages/astro/dist/index.js';
Comment thread
magmacomputing marked this conversation as resolved.
Outdated

const mockToken = 'eyJhbGciOiJSUzI1NiJ9.eyJwZXJtaXNzaW9ucyI6eyJhc3RybyI6eyJleHAiOjE5NTQxMTA1MDMsInVwZGF0ZWRfYXQiOjE3NzkwNjc3MDN9fSwianRpIjoiNTJiMGRiNjQtNTBlYy00YmRhLWFiZWItOTUzOGRmODFiZTgwIiwiaWF0IjoxNzc5MDY3NzAzLCJpc3MiOiJNYWdtYSBDb21wdXRpbmciLCJzdWIiOiJ0ZXN0QGV4YW1wbGUuY29tIiwiZXhwIjoxOTU0MTEwNTAzfQ.NfdBM-V_LhWu0A6DEy9F4lMRMKSs6cFySwGGYQ3RizsuygWHXNf6LfskidZS5F5iwbm4INx5j8UW-Y463pohbjf_PSsGqlit5Qobd9180fwN8iadXkISito7XbaLldRCpsggvPVeXJC64e4EKSB0TNTRs1wQCKqFgmgN-_C5ubybpQlPAEQ1bmHo1sfYRTjqoPI66y7es_40EEJoH7ozzx29OlwtmyrHlkdA026T8o_Z9ny1OppSxMChBKiVqunbv_bfs0ZcG3kz8HAXxPPn4zBgDwI8kwr-BXz-idezDyCCXBfnn_dz3ejozU_ec1RghNsfnWxwtXIn6dJ21SbUKQ';

// Initialize Tempo with a mock license
Tempo.init({ license: mockToken });
Comment thread
magmacomputing marked this conversation as resolved.
Outdated

// Register the Astro plugin term directly
Tempo.extend(AstroTerm);

Object.assign(globalThis, { Tempo, getType, stringify, objectify, enumify, enums, Pledge });

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

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);
idleTimer.unref();
};

process.stdin.on('data', resetIdle);
resetIdle();
36 changes: 35 additions & 1 deletion packages/tempo/bin/resolve-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ 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 LIC_DEST_DIR = path.resolve(DIST_DIR, 'lic');
Comment thread
magmacomputing marked this conversation as resolved.

console.log('Resolving type definitions...');

// 1. Ensure lib directory exists
Expand All @@ -34,6 +37,25 @@ 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');
}
}

// 4. Walk through all .d.ts files in dist/ to rewrite aliases
function walk(dir: string) {
const files = fs.readdirSync(dir);
Expand Down Expand Up @@ -64,6 +86,17 @@ function rewrite(filePath: string) {
replacement = `${prefix || './'}lib/`;
}

// Handle #tempo/license resolution
let licReplacement: string;
const isInsideLic = relToDist.startsWith('lic');
if (isInsideLic) {
licReplacement = './';
} else {
let prefix = '';
for (let i = 0; i < depth; i++) prefix += '../';
licReplacement = `${prefix || './'}lic/`;
}

const updatedContent = content
.replace(/#library\/([^"')]+\.js)/g, (match, libPath) => {
// NOTE: We use path.basename here because the @magmacomputing/library distribution
Expand All @@ -72,7 +105,8 @@ function rewrite(filePath: string) {
const fileName = path.basename(libPath);
return `${replacement}${fileName}`;
})
.replace(/#library(['"])/g, (match, quote) => `${replacement}index.js${quote}`);
.replace(/#library(['"])/g, (match, quote) => `${replacement}index.js${quote}`)
.replace(/#tempo\/license(['"])/g, (match, quote) => `${licReplacement}index.js${quote}`);

if (content !== updatedContent) {
fs.writeFileSync(filePath, updatedContent);
Expand Down
10 changes: 5 additions & 5 deletions packages/tempo/doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Tempo employs two distinct methodologies for protecting its internal state. Thes
| :--- | :--- | :--- |
| **Primary Target** | `Tempo.#term`, `Tempo.#fmt` (Instance State) | `NUMBER`, `FORMAT` (Global Registries) |
| **Scope** | **Instance-Specific**: Unique to every separate `new Tempo()` call. | **Global-Shared**: One single source of truth used by all instances. |
| **Primary Goal** | **Performance**: Avoid computing expensive terms (e.g., `qtr` or `szn`) until they are needed, | **Extensibility**: Allow plugins to safely append new data to registries at runtime. |
| **Primary Goal** | **Performance**: Avoid computing expensive Terms (e.g., `qtr` or `szn`) until they are needed, | **Extensibility**: Allow plugins to safely append new data to registries at runtime. |
| **Mechanism** | `Object.create(proto)` + Prototype Shadowing. | `new Proxy(target)` + Symbol-bypass. |
| **Why this one?** | **Memory Efficiency**: Thousands of instances share the same base prototype. | **Reference Stability**: Shared registries must stay at the same object reference. |

Expand All @@ -59,10 +59,10 @@ Tempo employs two distinct methodologies for protecting its internal state. Thes
Tempo is built with a **"Performance First"** mindset, specifically targeting the overhead of the class constructor. In high-frequency applications (like Tickers or real-time Dashboards), creating thousands of objects must be nearly as cheap as a primitive assignment.

This objective is achieved through two primary architectural pillars:
1. **Lazy Evaluation ([Section 1](#1-lazy-evaluation-shadowing))**: Deferring the expensive work of string parsing and term computation until the first property access.
1. **Lazy Evaluation ([Section 1](#1-lazy-evaluation-shadowing))**: Deferring the expensive work of string parsing and Term computation until the first property access.
2. **Master Guard ([Section 3](#3-master-guard-fast-fail-sync-point))**: Implementing a high-speed "fast-fail" gatekeeper to instantly reject invalid inputs when parsing *is* eventually triggered.

Together, these ensure that `new Tempo()` maintains an $O(1)$ constructor execution time by deferring $O(N)$ full-parse work until the first property access, regardless of how many plugins or custom terms are registered in the global system.
Together, these ensure that `new Tempo()` maintains an $O(1)$ constructor execution time by deferring $O(N)$ full-parse work until the first property access, regardless of how many plugins or custom Terms are registered in the global system.

---

Expand Down Expand Up @@ -94,7 +94,7 @@ The **Instance Shadowing** pattern is designed for massive scale. When a library

### How it works:
- **Stage 0**: All instances initially point to the same base `#term` object containing un-evaluated getters.
- **Stage 1**: When a term (e.g., `.qtr`) is accessed, the value is computed once.
- **Stage 1**: When a Term (e.g., `.qtr`) is accessed, the value is computed once.
- **Stage 2**: Tempo uses a **Generic Lazy Delegator** Proxy (via `getLazyDelegator`) which catches property access and evaluates it on-demand.
- **Result**: The JS engine executes lookups via an optimized Proxy handler, making lookups nearly as fast as raw property access while keeping the state strictly immutable.

Expand Down Expand Up @@ -162,7 +162,7 @@ Tempo maintains system-wide synchronization through a private, Symbol-based hook
### Reactive Registration
When a plugin is imported via a side-effect (`import '@magmacomputing/tempo/ticker'`), it triggers a **`sym.$Register`** hook.
- **Auto-Sync**: The `Tempo` class listens for these hooks and automatically updates its internal registries.
- **Guard Rebuild**: Every time a new term or layout is registered, the **Master Guard** is automatically rebuilt to include the new tokens, ensuring the "Zero-Cost Constructor" always stays up to date.
- **Guard Rebuild**: Every time a new Term or layout is registered, the **Master Guard** is automatically rebuilt to include the new tokens, ensuring the "Zero-Cost Constructor" always stays up to date.

### Disposable Engine (`Symbol.dispose`)
The `Tempo` class implements the explicit resource management pattern.
Expand Down
6 changes: 4 additions & 2 deletions packages/tempo/doc/commercial.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ For mission-critical applications, we provide priority support, security auditin

## 💎 Premium Extensions

In addition to our open-source core, we offer a suite of **Premium Plugins** that are available via a private NPM registry or commercial license. These extensions provide advanced logic for enterprise-scale requirements.
In addition to our open-source core, we offer a suite of **Premium Plugins** published directly to the standard public NPM registry (`npmjs.com`), secured by a commercial License Key. These extensions provide advanced, proprietary logic for enterprise-scale requirements.

**[Browse the Plugin Marketplace](./tempo.plugin.md)**
More details on browsing the 'Tempo Store' will be provided soon...

For details on how to unlock and use these features, see our [License Key Guide](./tempo.license.md).

---

Expand Down
Loading
Loading