Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

232 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

database-kit

Database semantic models, declarations, QueryIR, and the canonical binary protocol for the database ecosystem.

The normative ownership and product contract is documented in database-kit Responsibility Specification.

The package implements the target-free DatabaseWire v2 contract by default. The non-default MultipleBases trait adds Base, Composition, persisted Grant, target, provenance, and federated-consistency semantics as DatabaseWire v3. Completion is verified through Native behavior tests, standard and Embedded WASM release builds, the Embedded macro-compilation fixture, and byte-owner identity tests in both trait configurations.

Overview

database-kit is the semantic contract consumed by both database-framework and database-client. Primitive representations are owned by database-types; database-kit adds model, schema, query, graph, and protocol meaning.

It provides:

  • @Persistable macro for defining data models
  • @Polymorphable macro for protocol-oriented polymorphic storage metadata
  • @OWLClass macro for OWL ontology class mapping
  • @OWLDataProperty / @OWLObjectProperty macros for OWL property annotations
  • IndexKind protocol for extensible index type definitions
  • Foundation-independent identity, RDF, and QueryIR semantic models
  • bounded RDF term-role and structural validation without binary materialization
  • canonical binary DatabaseWire operations, envelopes, limits, and errors
  • canonical schema manifests, SHA-256 fingerprints, and typed schema plan/apply requests through schema.execute
  • strict, lossless JSON adaptation for schema manifests in the optional native DatabaseSchemaJSON product
  • one Foundation-independent semantic module
  • one canonical bounded binary protocol module
  • one optional native Foundation model-integration module

With the non-default MultipleBases trait it additionally provides:

  • Base, Base.Composition, and persisted Security.Grant semantics;
  • database, Base, or Composition operation targets;
  • Base-qualified identity, placement, lifecycle, provenance, and federated read consistency;
  • the base.execute, composition.execute, and grant.execute operations.
database-client ────────┐
                       ▼
                database-kit ───────▶ database-types
                       ▲
                       │
                database-framework ─▶ storage-kit

Installation

dependencies: [
    .package(
        url: "https://github.com/1amageek/database-kit.git",
        from: "26.0815.0"
    )
]

Modules

Module Description
DatabaseKit Foundation-independent model, identity, schema, query, mutation, relationship, index, graph, ontology, SHACL, and shared streaming digest support
DatabaseWire Canonical binary envelopes, typed operations, bounded encoding and decoding, results, errors, and protocol-specific digest values
DatabaseSchemaJSON Native strict JSON adapter for SchemaManifest; rejects duplicate or unknown keys and preserves every FieldValue case without numeric inference
DatabaseKitFoundation Native-only participation of Foundation scalar types in Persistable field adaptation

Relationship, vector, full-text, geographic, rank, permutation, graph, ontology, and SHACL are source classifications within DatabaseKit, not separate products.

DatabaseKit and DatabaseWire build with the matching Swift 6.4 standard and Embedded WASM SDKs. DatabaseSchemaJSON and DatabaseKitFoundation are native adapter products and are excluded from that dependency graph.

There is no umbrella value module in database-kit. FieldValue and every primitive alternative are defined only by DatabaseTypes; the database-kit modules above add model, RDF, SHACL, query, and protocol semantics without redeclaring that algebra.

Query pagination uses UInt64, so negative LIMIT and OFFSET values are not representable. DatabaseWire encoding measures the exact frame and writes directly into one final ByteString allocation; there is no public mutable-array writer path.

The default graph has no Base declarations, target field, persisted Grant operations, provenance table, topology, or federated consistency payload. It represents one database and one execution root directly.

When MultipleBases is enabled, Base.ID, Base.Placement.ID, and Base.Composition.ID are validated ASCII slugs. A Composition stores a nonempty, unique, canonically ordered Base set. Requests then carry an explicit database, Base, or Composition target. Composition result pages encode their Base table once and attach ordinal provenance to each row or quad, together with the transactional or federated read points that fixed the result.

.package(
    url: "https://github.com/1amageek/database-kit.git",
    from: "26.0815.0",
    traits: [.trait(name: "MultipleBases")]
)

See Architecture and ownership for the package boundary and dependency rules. See Zero-Copy and Embedded Architecture for the copy budget, static model-adaptation design, lazy result pages, and WASM host transport contract.

Verification

Contract Verification
Apple platform behavior TOOLCHAINS=org.swift.64202607231a scripts/xcode-test-harness; the package-owned harness enforces the reviewed exact test count with zero skips, expected failures, runtime warnings, or internal tool errors
Standard WASM contract Release builds of DatabaseKit and DatabaseWire with the matching Swift 6.4 WASM SDK and -debug-info-format none
Embedded semantic and Wire graph Release builds of DatabaseKit and DatabaseWire with the matching Swift 6.4 Embedded WASM SDK and -debug-info-format none
Embedded macro use Embedded release build of DatabaseKitDeclarationContract with -debug-info-format none, which expands model, field, directory, index, and relationship declarations
Binary ownership Tests assert that payload pages borrow ranges from the single final frame allocation
Decoder safety Tests cover truncation, limits, malformed values, non-canonical input, and cyclic RDF lists

The current baseline is swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-23-a with the exactly matching standard and Embedded WASM SDKs. The macro dependency requires swift-syntax as a release range (from: "603.0.2") so downstream graphs that also constrain swift-syntax (e.g. through JavaScriptKit) stay resolvable; reproducibility comes from the committed Package.resolved, which CI enforces with -onlyUsePackageVersionsFromResolvedFile. The full verification matrix (host test harness plus the standard and Embedded WASM release builds below) passes on this toolchain with swift-syntax 603.0.2. Release/WASM verification disables debug information because it is not shipped in the reactor and avoids running host-side dsymutil over macro dependency objects. Compiler diagnostics remain enabled.

TOOLCHAINS=org.swift.64202607231a scripts/xcode-test-harness

swift build --disable-default-traits --product DatabaseWire
swift build --disable-default-traits --traits MultipleBases --product DatabaseWire

swift build --swift-sdk swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-23-a_wasm --product DatabaseKit -c release -debug-info-format none
swift build --swift-sdk swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-23-a_wasm --product DatabaseWire -c release -debug-info-format none
swift build --swift-sdk swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-23-a_wasm-embedded --product DatabaseKit -c release -debug-info-format none
swift build --swift-sdk swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-23-a_wasm-embedded --product DatabaseWire -c release -debug-info-format none
swift build --swift-sdk swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-23-a_wasm-embedded --target DatabaseKitDeclarationContract -c release -debug-info-format none

Quick Start

Define a Model

import DatabaseKit

@Persistable
struct User {
    #Directory<User>("app", "users")
    #Index(.scalar, fields: [\User.email], unique: true)
    #Index(.scalar, fields: [\User.createdAt])

    var id: String
    var email: String
    var name: String
    var createdAt: Timestamp
}

Schema

let schema = try Schema(
    entities: [
        try User.schemaEntity,
        try Product.schemaEntity,
    ],
    version: Schema.Version(1, 0, 0)
)

@Persistable Macro

The @Persistable macro generates all required protocol conformances:

@Persistable
struct Product {
    #Index(
        .scalar,
        fields: [\Product.category, \Product.price]
    )
    #Index(.scalar, fields: [\Product.name], unique: true)

    var id: String
    var name: String
    var category: String
    var price: Double

    @Transient
    var cachedDescription: String?  // Excluded from persistence
}

Generated code: persistableType, allFields, fieldSchemas, typed fields, indexDescriptors, fieldAccessRules, concrete PersistedFieldOutput traversal, canonical decoding, and Sendable conformance. The model declares id and owns its generation policy.

The production encoder does not inspect a model through Mirror, Any, or a runtime metatype. It writes each concrete property directly to the selected output. PersistableFieldEncoder.encode(_:) is the explicit owned materializer for callers that require [PersistableField]; Wire and storage outputs can consume the generated traversal without that intermediate array. When an execution path needs one field after typed access has been erased, persistedFieldValue(for:) selects it by FieldIdentity through the same generated traversal. It materializes only that field as FieldValue; the model contract has no Any, any Sendable, dynamic-member, or reflection fallback.

PersistedEntityValue is the common read boundary for compiled values and PersistedModel. Once a concrete model becomes an owned PersistedModel, heterogeneous execution can read its canonical fields without reconstructing a synthetic or concrete Persistable value. Its complete-field accessor preserves the owned PersistedModel backing and materializes compiled values only at the explicit type-erasure boundary.

Ontology bindings retain the complete field-to-property descriptor mapping in Schema.Entity. A schema manifest therefore preserves the same RDF projection contract as a compiled @OWLClass or @OWLObjectProperty model. OWLCanonicalDataPropertyProjection and the canonical OWLIndividualIRIBuilder overloads apply that contract directly to FieldValue, so schema-driven runtimes do not reconstruct concrete Swift models or pass through JSON before materializing RDF terms.

Polymorphic Persistence

Polymorphic persistence lets multiple concrete @Persistable models share a logical protocol group for storage and querying.

The intended API is:

@Polymorphable
@PolymorphicDirectory("memory", "entities")
@PolymorphicIndex(
    .vector(dimensions: 256),
    embedding: "embedding"
)
public protocol Entity: Polymorphable<EntityPolymorphicGroup> {
    var label: String { get }
    var embedding: Vector { get set }
}

@Persistable
public struct Person: Entity {
    public var id: String
    public var name: String
    public var embedding: Vector

    public var label: String { name }
}

@Persistable owns Persistable conformance for concrete structs. Polymorphable<Group> owns only static group membership. The domain protocol requires the fields shared by its concrete models, while EntityPolymorphicGroup is the generated metadata declaration.

@Polymorphable is a metadata and validation macro. Swift 6.4 does not allow an attached macro on a protocol to add protocol inheritance, so the protocol must explicitly bind the generated declaration with : Polymorphable<EntityPolymorphicGroup>.

Schema versions may use different Swift protocol names while preserving one logical storage identity. Declare that stable identity explicitly:

@Polymorphable(identifier: "Document")
protocol DocumentV2: Polymorphable<DocumentV2PolymorphicGroup> {
    var id: String { get }
}

The identifier is part of persisted polymorphic membership and directory identity. It must remain unchanged across schema versions that represent the same logical group.

Swift 6.4 cannot form a KeyPath<Self, Value> while the protocol containing the declaration is still being defined. Protocol-level indexes therefore use logical property names with the @PolymorphicIndex attribute. @Polymorphable validates every name against a declared protocol property at compile time. Schema then resolves the logical property to each concrete member's generated FieldIdentity and validates its canonical field type.

This string boundary is limited to protocol source declarations. Concrete models continue to use KeyPath syntax with #Index, and runtime index maintenance receives only concrete field identities. It performs neither KeyPath retention nor string-to-field discovery.

See Polymorphic Persistence Design for the full design and migration plan.

#Directory Macro

Static Path

#Directory<User>("app", "users")

Multi-tenant Partitioning

@Persistable
struct Order {
    #Directory<Order>("tenants", \Order.accountID, "orders", layer: .partition)

    var orderID: Int64
    var accountID: String  // Partition key
}

Multi-level Partitioning

@Persistable
struct Message {
    #Directory<Message>(
        "tenants", \Message.accountID,
        "channels", \Message.channelID,
        "messages",
        layer: .partition
    )

    var messageID: String
    var accountID: String
    var channelID: String
    var content: String
}

Built-in Index Kinds

Built-in indexes have one declaration surface: IndexDefinition through #Index. The generic *IndexKind<Model> duplicates are not part of version 1.

@Persistable
struct Event {
    #Index(
        .scalar,
        fields: [\Event.calendarID, \Event.startsAt]
    )
    #Index(.count, groupBy: [\Event.calendarID])
    #Index(
        .sum,
        groupBy: [\Event.calendarID],
        value: \Event.attendeeCount
    )
    #Index(
        .version(strategy: .keepAll),
        field: \Event.id
    )
    #Index(
        .vector(dimensions: 384, metric: .cosine),
        embedding: \Event.embedding
    )
    #Index(
        .fullText(tokenizer: .simple),
        fields: [\Event.title, \Event.description]
    )
    #Index(
        .autocomplete(minPrefixLength: 2, maxPrefixLength: 12),
        fields: [\Event.title, \Event.searchTerms]
    )
    #Index(.spatial(), location: \Event.location)
    #Index(.rank, field: \Event.attendeeCount)
    #Index(
        .propertyGraph(strategy: .adjacency),
        from: \Event.sourceID,
        edge: \Event.relationship,
        to: \Event.targetID
    )

    var id: String
    var calendarID: String
    var startsAt: Timestamp
    var attendeeCount: Int64
    var embedding: Vector
    var title: String
    var description: String
    var searchTerms: [String]
    var location: GeographicPoint
    var sourceID: String
    var relationship: String
    var targetID: String
}

The enclosing macro consumes each KeyPath and emits the corresponding generated field identity. Protocol-level polymorphic declarations use logical property names, which @Polymorphable verifies against protocol properties. Neither runtime path retains a KeyPath.

Property graphs and RDF datasets have separate declarations because their identity contracts are different. A property graph uses String source, label, target, and optional namespace fields. An RDF dataset uses RDFTerm subject, predicate, object, and optional graph fields:

@Persistable
struct Statement {
    #Index(
        .rdfDataset,
        from: \Statement.subject,
        edge: \Statement.predicate,
        to: \Statement.object,
        graph: \Statement.graph
    )

    var id: String
    var subject: RDFTerm
    var predicate: RDFTerm
    var object: RDFTerm
    var graph: RDFTerm?
}

The canonical kind identifiers remain graph and rdf_quad, respectively. Schema validation never infers one graph model from the other.

Custom Index Kinds

IndexKind is reserved for OWL-generated and third-party extension semantics that are not built into IndexDefinition. Property graph and RDF dataset declarations are built in and use IndexDefinition:

import DatabaseKit

public struct TimeSeriesIndexKind<Root: Persistable>: IndexKind {
    public static var identifier: String { "com.mycompany.timeseries" }
    public static var subspaceStructure: SubspaceStructure { .hierarchical }

    public let indexFields: [IndexField<Root>]
    public let resolution: TimeResolution

    public var indexName: String {
        "\(Root.persistableType)_timeseries_\(fieldNames.joined(separator: "_"))"
    }

    public var metadata: [String: FieldValue] {
        ["resolution": .string(resolution.rawValue)]
    }

    public enum TimeResolution: String, Sendable, Hashable {
        case second, minute, hour, day
    }

    public init(
        fields: [IndexField<Root>],
        resolution: TimeResolution = .minute
    ) {
        self.indexFields = fields
        self.resolution = resolution
    }

    public static func validateFields(
        _ fields: [FieldSchema]
    ) throws(IndexValidationError) {
        guard fields.count == 1 else {
            throw .invalidFieldCount(
                index: identifier,
                expected: 1,
                actual: fields.count
            )
        }
        guard fields[0].type == .timestamp, !fields[0].isArray else {
            throw .unsupportedField(
                index: identifier,
                field: fields[0],
                reason: "Time-series fields must use timestamp values"
            )
        }
    }
}

Application use passes a generated field through the custom descriptor path:

let timeSeries = try IndexDescriptor(
    name: "Event_timeseries_startsAt",
    kind: TimeSeriesIndexKind<Event>(
        fields: [Event.fields.startsAt.ascending],
        resolution: .minute
    )
)

let schema = try Schema(
    entities: [
        try Schema.Entity(
            from: Event.self,
            including: [timeSeries]
        )
    ]
)

Server-side maintenance is implemented and registered by database-framework. Custom declarations expose only canonical IndexKindMetadata; runtime behavior remains outside this package. IndexDescriptor validates concrete generated fields and configuration before Schema exposes the catalog.

@Persistable enums

@Persistable
enum Status: String {
    case active, inactive, pending
}

@Persistable
struct Task {
    var id: String
    var title: String
    var status: Status = .pending
}

Applying @Persistable to a raw-value enum generates its PersistableEnum conformance and case enumeration. String- and Int-backed enums receive canonical field encoding and case metadata. Availability-qualified cases are rejected so the persisted schema cannot vary by platform. @Persistable(type:) remains specific to model structs.

@Relationship Macro

@Persistable
struct Order {
    var total: Double

    @Relationship(Customer.self, indexFields: [\.name])
    var customerID: String?
}

Ontology Integration

Ontology features are in the Graph module. Three usage levels can be combined incrementally.

Level Components Use Case
1. OntologyStore OWLOntology, context.ontology API OWL reasoning, class hierarchy, property chain evaluation
2. Macros + OntologyStore Level 1 + @OWLClass, @OWLObjectProperty, @OWLDataProperty Bind Persistable types to OWL concepts, IRI validation, SPARQL over tables
3. Macros + OntologyStore + Triples Level 2 + graph and RDF index declarations SPARQL federation across Persistable tables and RDF triples

Level 1: OntologyStore

Define and load OWL ontologies for reasoning and hierarchy queries. No macros required.

var ontology = OWLOntology(iri: "http://example.org/onto")
ontology.classes = [OWLClass(iri: "ex:Person"), OWLClass(iri: "ex:Employee")]
ontology.axioms = [.subClassOf(sub: .named("ex:Employee"), sup: .named("ex:Person"))]

// Load and query (server-side, database-framework)
try await context.ontology.load(ontology)
let reasoner = try await context.ontology.reasoner(for: "http://example.org/onto")
let superClasses = reasoner.superClasses(of: "ex:Employee")

Level 2: Macros + OntologyStore

Bind Persistable types to OntologyStore concepts. Macros are bindings, not definitions — class hierarchies, property characteristics, and axioms live in the OntologyStore. Each row is interpreted as virtual RDF triples, enabling SPARQL queries over Persistable tables.

@Persistable
@OWLClass("ex:Employee")
struct Employee {
    @OWLDataProperty("ex:name")
    var name: String

    @OWLDataProperty("ex:worksFor", to: \Department.id, functional: true)
    var departmentID: String?
}

@Persistable
@OWLObjectProperty("ex:employs", from: "employeeID", to: "projectID")
struct Assignment {
    var id: String = UUID().uuidString
    var employeeID: String = ""
    var projectID: String = ""

    @OWLDataProperty("ex:since")
    var startDate: Timestamp
}

IRI validation checks that macro bindings reference valid OntologyStore entries:

try await context.ontology.validateSchema(schema, ontologyIRI: "http://example.org/onto")

SPARQL queries Persistable tables directly:

let results = try await context.sparql()
    .from(Employee.self)
    .where("?e", "rdf:type", "ex:Employee")
    .where("?e", "ex:name", "?name")
    .select("?e", "?name")
    .execute()

Level 3: Macros + OntologyStore + Triples

Add a GraphIndex triple store alongside Persistable tables. SPARQL federation resolves each triple pattern to the optimal source — structured data in tables, unstructured knowledge in triples:

let results = try await context.sparql()
    .from(RDFTriple.self)        // Triple store
    .from(Employee.self)          // Persistable table
    .where("?e", "rdf:type", "ex:Employee")     // → Employee table
    .where("?e", "ex:worksFor", "?dept")         // → Employee table
    .where("?dept", "ex:locatedIn", "?city")     // → Triple store
    .select("?e", "?city")
    .execute()

Macro Reference

@OWLClass(_ iri: String) — Maps a Persistable type to an OWL class. Generates OWLClassEntity conformance, ontologyClassIRI, and ontologyPropertyDescriptors.

@OWLObjectProperty(_ iri: String, from: String, to: String) — Maps a Persistable type to an OWL ObjectProperty with endpoint fields. Generates OWLObjectPropertyEntity conformance and an adjacency graph index declaration with an implicit label.

@OWLDataProperty(_ iri: String, ...) — Annotates a field with an OWL datatype property IRI.

Bare names (without :, #, or /) default to the namespace extracted from the parent @OWLClass or @OWLObjectProperty IRI.

Platform Support

Platform Minimum Version
iOS 26.0+
macOS 26.0+
tvOS 26.0+
watchOS 26.0+
visionOS 26.0+
Linux Swift 6.4+
WASI Embedded Swift 6.4+ for DatabaseKit and DatabaseWire
WASI (standard runtime) Swift 6.4+ for the same semantic and wire products

Related Packages

Package Role Platform
database-types Primitive field-value algebra and immutable byte ownership Embedded, Apple, Linux
database-framework Database execution, transactions, indexes, graph, ontology, and validation WASI, macOS, Linux
database-client Typed invocation and WASM host, HTTP, and WebSocket transports Embedded, Apple, Linux
storage-kit Storage transactions and backend adapters WASI, macOS, Linux

License

Licensed under the MIT License.

About

Foundation-independent schema, query, RDF/OWL, and canonical DatabaseWire contracts for Swift database clients and runtimes.

Topics

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages