@suluk/provision - v0.1.0
    Preparing search index...

    @suluk/provision - v0.1.0

    Suluk

    @suluk/provision

    Declarative service provisioning, driven like drizzle-kit — declare the infra you want in one config, then plan the diff and apply it along the binding DAG, landing credentials in @suluk/env.

    Part of Suluk — one typed OpenAPI v4 contract projecting into every full-stack layer.


    CANDIDATE tooling — not official OpenAPI. Suluk is a single-contributor candidate for OpenAPI Specification v4.0 ("Moonwalk"), unaffiliated with the OpenAPI Initiative and unable to ratify anything on the SIG's behalf.

    bun add @suluk/provision
    

    Infrastructure as a declarative config, modelled on the Open Service Broker API and driven like drizzle-kit (C047). You list the instances you want — each an OSB broker plus a name, plan, params, and binding map — and the lifecycle is a pure function of that config × live state:

    • plan diffs desired-vs-live into a reviewable list of steps (create / update / noop / deprovision), in binding-DAG order. Pure: no provider calls, no clock.
    • apply walks that DAG and calls the OSB verbs — provision (idempotent) → poll async lastOperation until a create settles → bind for credentials → land them in a BindingSink (the @suluk/env manifest in prod). A downstream @ref.key param resolves to a freshly provisioned value, so the DAG wires itself.
    • checkDrift / assertNoDrift is the CI gate (a clean plan ⇒ no drift).

    A param of the form @<ref>.<key> is a binding reference — that's what makes db provision before the token that scopes to @db.database_id. defineProvision validates the static shape (unique refs, an acyclic binding DAG) at authoring time, so a loop or an unreferenceable ref is caught before any provider is called. Stateful resources can be protected (the terraform prevent_destroy analog). A drizzle-style snapshot + migration model gives repeatable, documentable steps.

    import {
    defineProvision, plan, apply, assertNoDrift,
    memoryStore, memorySink,
    cloudflareD1, cloudflareToken,
    } from "@suluk/provision";

    // Declare the infra. `@db.database_id` wires the token AFTER the D1 database.
    const config = defineProvision({
    instances: [
    { ref: "db", service: "cloudflare-d1", name: "app-db", protected: true, bind: { database_id: "CLOUDFLARE_D1_ID" } },
    { ref: "token", service: "cloudflare-token", name: "d1-token",
    params: { scope: "@db.database_id" }, bind: { token: "CLOUDFLARE_D1_TOKEN" } },
    ],
    });

    // The concrete Cloudflare brokers wrap @suluk/cloudflare's idempotent provisioners.
    const brokers = { "cloudflare-d1": cloudflareD1(cf), "cloudflare-token": cloudflareToken(cf) };
    const store = memoryStore(); // a JSON file in prod (`fileStore`)
    const sink = memorySink(); // the @suluk/env manifest in prod (`envSink`)

    // Review, then execute along the binding DAG.
    const state = await store.load();
    console.table(plan(config, state).steps); // [{ ref, action, reason }, …]

    const result = await apply(config, { brokers, store, sink });
    result.outputsByRef.db; // { database_id: "…" } — resolved outputs after the run

    // CI gate: throw if the live infra has drifted from the config.
    await assertNoDrift(config, await store.load());
    suluk-provision plan     # diff desired-vs-live
    suluk-provision apply # provision → bind → land credentials
    suluk-provision check # drift / orphan gate (CI)
    suluk-provision status
    Export What it does
    defineProvision(config) Validate + return the config (throws on dup/unknown ref or a cycle).
    plan(config, state, prune?) The pure diff → ProvisionPlan (steps, orphans, clean).
    apply(config, opts) Execute a plan along the binding DAG → ApplyResult (with resolved outputs).
    checkDrift / assertNoDrift Drift report + the CI gate.
    pull / reconcile / discover Read live state back; adopt un-tracked instances.
    teardown Deprovision (skips protected unless forced).
    topoOrder, parseRef, depsOf, resolveParams, fingerprint The binding-DAG + ref-resolution internals.
    snapshot / diffSnapshots / migrate / generate The drizzle-style snapshot + migration model.
    memoryStore / fileStore, memorySink / envSink The state store + binding sink (dev/test vs prod).
    cloudflareD1 / cloudflareKv / cloudflareR2 / cloudflareSecrets / cloudflareToken / cloudflarePagesDomain The concrete Cloudflare brokers.
    defineProvisionApp, runCli The drizzle-kit-style app config + CLI.

    Types Broker, BindingSink, StateStore, Catalog, InstanceSpec, InstanceState, and the OSB request/result shapes are exported alongside.

    @suluk/provision is the OSB client / orchestrator — a layer above @suluk/cloudflare (the idempotent provisioners it wraps as brokers), @suluk/deploy, and @suluk/env (the binding sink). It orchestrates; they execute. Provider calls live in the brokers; the core is pure orchestration over them, with the clock and sleep injected so apply is deterministically testable. It's the lower half of @suluk/platform, which merges each module's provision fragment into one config.

    Apache-2.0