Skip to content

Primitives

Three primitives, infinite possibilities.

The z0 SDK has exactly three primitives. Everything else is derived from or built on top of these.


What: Identity and state container. Like a row in a database, but immutable.

When to use:

  • Need unique identity for a domain object
  • Building multi-tenant systems
  • Tracking lifecycle (active, retired, deleted)

When NOT to use:

  • Storing events themselves (use Facts)
  • Configuration (use Config)
interface Entity<T = Record<string, unknown>> {
id: string; // Unique identifier
type: string; // Domain category (e.g., 'account', 'asset')
parent_id: string | null; // Parent entity ID (null = root)
tenant_id?: string; // Tenant ownership
version: number; // Optimistic concurrency control
metadata: Record<string, unknown>; // Platform metadata
data: T; // Domain-specific payload
created_at: number; // Unix timestamp (ms)
updated_at: number; // Unix timestamp (ms)
// GDPR compliance
anonymized_at?: number;
anonymization_reason?: 'gdpr_request' | 'data_retention' | 'manual';
anonymization_request_id?: string;
original_data_hash?: string; // SHA-256 proof of original data
// Extension point for domain-specific indexing
[key: string]: any;
}
const account: Entity<{ balance: number }> = {
id: 'acct_abc123',
type: 'account',
parent_id: null,
tenant_id: 'tenant_xyz',
version: 42,
metadata: { status: 'active' },
data: { balance: 1000.00 },
created_at: 1704067200000,
updated_at: 1704153600000
};
StateDescriptionFacts Accepted
activeNormal operationAll
retiringDraining in progressSystem only
retiredRead-only, completeNone
anonymizedGDPR-compliantNone
deletedMarked for cleanupNone

What: Immutable event record. Once written, never changed.

When to use:

  • Recording business events (payment, login, API call)
  • Audit trail requirements
  • Time-series data
  • Event sourcing

When NOT to use:

  • Storing current state (derive from Facts instead)
  • Mutable data (append correction Facts)
interface Fact<T = Record<string, unknown>> {
id: string; // Unique fact identifier
type: string; // Primary event category (e.g., 'payment')
subtype?: string; // Refined action (e.g., 'completed')
timestamp: number; // Business time (can be backdated)
recorded_at?: number; // System time (always "now")
seq?: number; // Monotonic sequence for replay
tenant_id: string; // Tenant isolation
source_id?: string; // Tool/service that generated fact
entity_id?: string; // Master entity ID
correlation_id?: string; // Distributed tracing
data: T; // Domain-specific payload
schema_version?: number; // For migrations (default: 1)
// Extension point
[key: string]: any;
}
const deposit: Fact<{ amount: number; currency: string }> = {
id: 'fact_deposit_xyz',
type: 'payment',
subtype: 'deposit',
timestamp: 1704153600000,
recorded_at: 1704153601000,
seq: 127,
tenant_id: 'tenant_xyz',
entity_id: 'acct_abc123',
correlation_id: 'trace_456',
data: { amount: 500.00, currency: 'USD' },
schema_version: 1
};

Facts follow a two-level taxonomy:

  • type: Broad category (e.g., payment, lifecycle, webhook)
  • subtype: Specific action (e.g., completed, failed, retrying)

System Fact Types:

  • lifecycle - Entity state changes
  • webhook - Delivery events
  • cache_invalidated - Cache busting
  • entity_retired, entity_anonymized, entity_deleted

What: Versioned configuration. Changes create new versions, never overwrite.

When to use:

  • Routing rules
  • Pricing tiers
  • Feature flags
  • Policies that change over time

When NOT to use:

  • Environment variables (use Cloudflare secrets)
  • Per-request state (use middleware)
  • Frequently-changing data (use Facts)
interface Config<T = Record<string, unknown>> {
id: string; // Unique config identifier
tenant_id: string; // Owner tenant
type: string; // Config category
category: string; // Logical grouping
name: string; // Human-readable name
applies_to: string; // Target entity type/ID
scope: string; // Access level
version: number; // Auto-incrementing
effective_at: number; // When this version activates
superseded_at?: number | null; // When replaced by newer version
settings: T; // The actual configuration
}
const pricingConfig: Config<{ tiers: Tier[] }> = {
id: 'pricing_standard',
tenant_id: 'tenant_xyz',
type: 'pricing',
category: 'subscription',
name: 'Standard Plan Pricing',
applies_to: 'subscription',
scope: 'tenant',
version: 3,
effective_at: 1704067200000,
superseded_at: null,
settings: {
tiers: [
{ ending_quantity: 100, unit_price_cents: 200 },
{ ending_quantity: 1000, unit_price_cents: 100 },
{ ending_quantity: null, unit_price_cents: 50 }
]
}
};
ScopePriorityUse Case
global10Platform defaults
tenant20Tenant overrides
entity30Entity-specific

Higher priority wins during resolution.


PrimitiveMutableVersionedUse For
EntityYes (via version)YesIdentity, state container
FactNoYes (schema_version)Events, audit trail
ConfigNo (new version)YesConfiguration, policies

These are TypeScript types used with primitives, not primitives themselves:

TypePurpose
CachedStateDerived views stored in EntityLedger (rebuilt from Facts)
InvariantSettingsRuntime validation rules (stored as Config)
EntityLifecycleStateLifecycle state enum (active, retiring, retired, etc.)
EntityMigrationStateMigration state enum
AnonymizationReasonGDPR anonymization reasons
BudgetModeBudget enforcement mode