Sandbox Factory Pattern
Tempo v2.5.0 introduces the Sandbox Factory pattern, allowing for deep isolation of configurations and parsing rules. This is particularly useful in complex applications where different modules may require different date-time aliases or behaviors without polluting the global Tempo namespace.
The Problem
Historically, Tempo.init() modified the global library state. This meant that:
- Only one set of custom
EventorPeriodaliases could exist. - Large applications or libraries using Tempo internally would step on each other's configurations.
- Testing multiple configurations required careful cleanup between tests.
The Solution
Tempo.create() returns a derived sandboxed class with its own isolated configuration, registry, and plugin state. Each sandbox inherits from the caller, but runs with independent internal state.
Lifecycle Methods
To understand when to use Tempo.create(), it helps to contrast it with the other initialization methods:
Tempo.init({ options })Concept: Hard-reset to "out-of-the-box" factory defaults, then apply the provided configuration globally. All previous plugins, terms, and custom formats are purged.Tempo.extend({ options })Concept: Additive mutation. Keep all existing global settings, plugins, and formats intact, but merge in new configurations.Tempo.create({ options })Concept: Sandbox Factory. Clone the current global state (inheriting all currently loaded plugins and settings), but branch it off into a brand new, isolated class. Any future changes made to this Sandbox will not affect the globalTempo, and vice-versa.
Example: Creating a Sandbox
import { Tempo } from '@magmacomputing/tempo';
// Create a specialized Sandbox for a Financial app
const FinTempo = Tempo.create({
registry: {
periods: {
'market-open': '09:30',
'market-close': '16:00'
}
}
});
// Standard Tempo remains untouched
const t1 = new Tempo('market-open'); // Error: Unknown alias
const t2 = new FinTempo('market-open'); // Success: 09:30Traceability & Collision Management
When using sandboxes, it's important to know which configuration resolved an input. Tempo now records the source of every match in the parse.result array.
Hierarchy of Resolution
When a conflict occurs (e.g., you redefine "noon"), Tempo resolves it by checking layers from highest priority to lowest priority:
- Local (Instance): Options passed to
new Tempo(val, options). - Sandbox (Factory): Options passed to
Tempo.create(options). - Plugins: Aliases registered via
Tempo.extend(). - Global Defaults: Built-in aliases like "xmas", "midnight", etc.
Checking the Trace
You can inspect the parse.result to see exactly which layer provided the definition:
const t = new FinTempo('market-open');
console.log(t.parse.result);
/*
[
{
type: "Period",
value: "market-open",
source: "sandbox", // Resolved from FinTempo (factory/sandbox layer)
match: "tm",
...
}
]
*/Immutability & Security
Sandboxed classes created via Tempo.create() are protected by the same @Immutable and @Serializable decorators as the base class.
- The Sandbox class itself is hardened against static member modification.
- Instances of the Sandbox are frozen upon construction.
- The internal state is stored in a
WeakMap, inaccessible to external code.
Best Practices
- Create Once: Create your application-specific Sandbox once and export it as your primary entry point.
- Prefer Sandboxes for Custom Aliases: Avoid modifying the base
Tempoclass if your app is intended to be used as a library. - Use Debug Mode: When developing new aliases, set
debug: 'debug'to receive console warnings about naming collisions.