Core Axioms: The Essential Building Blocks
Overview
This document defines the core set of axioms that form the foundation of the Canon type system. These axioms represent the essential semantic concepts that appear in virtually all data-centric applications, providing a standardized way to work with common data patterns across different formats.
Core Axiom Set
The core axiom set consists of five essential axioms that cover the fundamental concepts found in most data-centric applications:
- Id - Unique identifiers for entities
- Type - Entity classification and schema information
- Version - Data versioning for optimistic concurrency control
- Timestamps - Time-based data with format conversion
- References - Entity relationships and references
Axiom Types
// Key-name axiom for simple field-based concepts
type KeyNameAxiom = Axiom<
{
$basis: Record<string, unknown>
key: string
},
{
key: string
}
>
// Representation axiom for data with multiple representations
type RepresentationAxiom<T, C = unknown> = Axiom<
{
$basis: T | TypeGuard<unknown>
isCanonical: (value: T | TypeGuard<unknown>) => value is C
},
{
isCanonical: (value: T | TypeGuard<unknown>) => value is C
}
>Axiom Definitions
1. Id Axiom
Purpose: Represents unique identifiers for entities across different data formats.
Type Definition:
type IdAxiom = KeyNameAxiomRegistration:
declare module '@relational-fabric/canon' {
interface Axioms {
Id: IdAxiom
}
}Common Field Names:
- REST APIs:
id - MongoDB:
_id - JSON-LD:
@id - GraphQL:
id
API Functions:
function idOf<T extends Satisfies<'Id'>>(x: T): AxiomValue<'Id'> {
const config = inferAxiom('Id', x)
return x[config.key] as AxiomValue<'Id'>
}2. Type Axiom
Purpose: Represents entity classification or schema information across different data formats.
Type Definition:
type TypeAxiom = KeyNameAxiomRegistration:
declare module '@relational-fabric/canon' {
interface Axioms {
Type: TypeAxiom
}
}Common Field Names:
- REST APIs:
type - MongoDB:
_type - JSON-LD:
@type - GraphQL:
__typename
API Functions:
function typeOf<T extends Satisfies<'Type'>>(x: T): AxiomValue<'Type'> {
const config = inferAxiom('Type', x)
return x[config.key] as AxiomValue<'Type'>
}Usage Example:
import { createLogger, typeOf } from '@relational-fabric/canon'
const logger = createLogger('docs:axioms:type')
// Works with different data formats
const restData = { type: 'user' }
const mongoData = { _type: 'User' }
const jsonLdData = { '@type': 'Person' }
logger.info(typeOf(restData)) // "user"
logger.info(typeOf(mongoData)) // "User"
logger.info(typeOf(jsonLdData)) // "Person"3. Version Axiom
Purpose: Represents version information for data entities, enabling optimistic concurrency control and change tracking.
Type Definition:
type VersionAxiom = KeyNameAxiomRegistration:
declare module '@relational-fabric/canon' {
interface Axioms {
Version: VersionAxiom
}
}Common Field Names:
- REST APIs:
version - MongoDB:
_version - JSON-LD:
@version - Custom:
rev
API Functions:
function versionOf<T extends Satisfies<'Version'>>(x: T): AxiomValue<'Version'> {
const config = inferAxiom('Version', x)
return x[config.key] as AxiomValue<'Version'>
}Usage Example:
import { createLogger, versionOf } from '@relational-fabric/canon'
const logger = createLogger('docs:axioms:version')
// Works with different data formats
const restData = { version: 5 }
const mongoData = { _version: 3 }
const jsonLdData = { '@version': '2.1' }
logger.info(versionOf(restData)) // 5
logger.info(versionOf(mongoData)) // 3
logger.info(versionOf(jsonLdData)) // "2.1"4. Timestamps Axiom
Purpose: Represents time-based data with automatic conversion between different timestamp formats.
Type Definition:
type TimestampsAxiom = RepresentationAxiom<number | string | Date, Date>Registration:
declare module '@relational-fabric/canon' {
interface Axioms {
Timestamps: {
$basis: number | string | Date | TypeGuard<unknown>
isCanonicalTimestamp: (value: number | string | Date | TypeGuard<unknown>) => value is Date
}
}
}Common Value Types:
- Unix timestamps:
number(milliseconds since epoch) - ISO strings:
string(ISO 8601 format) - Date objects:
Date(JavaScript Date instances) - Custom formats:
string(various timestamp formats)
API Functions:
function timestampsOf<T extends Satisfies<'Timestamps'>>(x: T): AxiomValue<'Timestamps'> {
const config = inferAxiom('Timestamps', x)
return config.isCanonicalTimestamp(x) ? x : new Date(x as any)
}Implementation:
// Timestamp canonical type guard
function isCanonicalTimestamp(value: number | string | Date | TypeGuard<unknown>): value is Date {
return value instanceof Date
}Usage Example:
import { createLogger, timestampsOf } from '@relational-fabric/canon'
const logger = createLogger('docs:axioms:timestamps')
// Works with different timestamp value types
const unixTimestamp = 1640995200000
const isoTimestamp = '2022-01-01T00:00:00Z'
const dateTimestamp = new Date('2022-01-01')
logger.info(timestampsOf(unixTimestamp)) // Converted to canonical Date
logger.info(timestampsOf(isoTimestamp)) // Converted to canonical Date
logger.info(timestampsOf(dateTimestamp)) // Converted to canonical Date5. References Axiom
Purpose: Represents relationships between entities with automatic conversion between different reference formats.
Type Definition:
// Canonical reference type for entity relationships
interface EntityReference<R, T = unknown> {
ref: R
value?: T
resolved: boolean
}
type ReferencesAxiom = RepresentationAxiom<string | object, EntityReference<string, unknown>>Registration:
declare module '@relational-fabric/canon' {
interface Axioms {
References: {
$basis: string | object | TypeGuard<unknown>
isCanonicalReference: (
value: string | object | TypeGuard<unknown>,
) => value is EntityReference<string, unknown>
}
}
}Common Value Types:
- String IDs:
string(single identifier) - Object references:
object(reference objects with metadata) - URI references:
string(URI-formatted references)
API Functions:
function referencesOf<T extends Satisfies<'References'>>(x: T): AxiomValue<'References'> {
const config = inferAxiom('References', x)
return config.isCanonicalReference(x) ? x : { ref: x as string, resolved: false }
}Implementation:
// Reference canonical type guard
function isCanonicalReference(
value: string | object | TypeGuard<unknown>,
): value is EntityReference<string, unknown> {
return typeof value === 'object' && value !== null && 'ref' in value && 'resolved' in value
}Usage Example:
import { createLogger, referencesOf } from '@relational-fabric/canon'
const logger = createLogger('docs:axioms:references')
// Works with different reference value types
const stringRef = 'user-123'
const entityRef = { ref: 'user-123', resolved: false }
logger.info(referencesOf(stringRef)) // Converted to EntityReference
logger.info(referencesOf(entityRef)) // Already canonical EntityReferenceConversion Types
The core axioms use conversion functions that work with different data formats. The specific types are determined by the axiom implementation:
// Conversion functions work with any type
// The specific types are determined by the axiom implementationUtility Types
The core axioms are built on top of the fundamental axiom utility types:
// Base axiom type with universal distinguished keys
interface Axiom<TBasis, TMeta> {
$basis: TBasis
$meta: TMeta
}
// Utility types for working with axioms
type Satisfies<T extends keyof Axioms> = {
[K in keyof Axioms[T]['$basis']]: Axioms[T]['$basis'][K]
}
type AxiomValue<T extends keyof Axioms> = Axioms[T]['$basis'][keyof Axioms[T]['$basis']]
// Runtime axiom inference
declare function inferAxiom<T extends keyof Axioms>(axiom: T, value: Satisfies<T>): Axioms[T]Implementation Notes
Registration Pattern
All core axioms follow the same registration pattern:
- Type Definition: Define the axiom type using the appropriate base type (
KeyNameAxiom,RepresentationAxiom<T>, orAxiom<{...}, {...}>) - Module Augmentation: Register the axiom in the
@relational-fabric/canonmodule - API Functions: Provide utility functions for working with the axiom
Field Name Conventions
While the core axioms define the semantic concepts, the actual field names vary by data format:
- REST APIs: Use standard camelCase (
id,type,version) - MongoDB: Use underscore prefix (
_id,_type,_version) - JSON-LD: Use at-sign prefix (
@id,@type,@version) - GraphQL: Use double underscore for types (
__typename)
Conversion Functions
Axioms that support multiple data formats (like Timestamps and References) include conversion functions:
toCanonical: Convert from the specific format to the canonical formatfromCanonical: Convert from the canonical format to the specific format
This enables seamless interoperability between different data sources while maintaining type safety.
Usage Guidelines
- Always use the provided API functions rather than accessing fields directly
- Register axioms early in your application lifecycle
- Use the
Satisfiesconstraint to ensure type safety - Leverage canonical types for consistent data processing
- Test across different formats to ensure compatibility
Extending the Core Set
While the core axioms cover the most common use cases, applications may need additional axioms for specific domains:
- Email: For email address formatting and processing
- URL: For web resource references
- Currency: For monetary values with locale support
- GeoLocation: For geographic coordinates
When adding new axioms, follow the same patterns established by the core axioms to maintain consistency and interoperability.