@better-schemic/core 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{chunk-IUPOUD4L.js → chunk-UPFTOIPR.js} +1 -1
- package/lib/{chunk-IUPOUD4L.js.map → chunk-UPFTOIPR.js.map} +1 -1
- package/lib/{driver-LVldBEhS.d.ts → driver-BGaMUprn.d.ts} +1 -1
- package/lib/driver.d.ts +1 -1
- package/lib/driver.js +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/testing.d.ts +51 -2
- package/lib/testing.js +82 -1
- package/lib/testing.js.map +1 -1
- package/package.json +6 -11
- package/src/driver/driver.ts +1 -1
- package/src/testing.ts +154 -0
- package/lib/query.d.ts +0 -81
- package/lib/query.js +0 -30
- package/lib/query.js.map +0 -1
- package/src/query/call.ts +0 -21
- package/src/query/codec.ts +0 -33
- package/src/query/index.ts +0 -22
- package/src/query/project.ts +0 -25
- package/src/query/ref.ts +0 -32
- package/src/query.ts +0 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kind/plan.ts","../src/kind/registry.ts","../src/driver/driver.ts"],"sourcesContent":["// The GENERIC migration spine over a {@link KindRegistry} — core's kind-blind orchestration. It\n// classifies each portable object as add/change/remove, ORDERS them across kinds by a dependency\n// graph, and emits up/down DDL + the display {@link Diff}. It never names a kind: every kind-specific\n// decision is delegated to that kind's {@link KindEngine}.\n//\n// The spine works on PORTABLE objects (both sides already lowered), exactly like the fixed-slot\n// `Driver.diff(prev, next)`: the stored snapshot IS portable, and the authoring side is lowered once\n// via {@link lowerSchema}. So `prev` is a snapshot, `next` is `lowerSchema(registry, defs)`.\n//\n// Cross-kind ordering is the load-bearing part (docs/kind-registry.md §7.1). THREE layers:\n// 1. dependency GRAPH + topological sort -> CORRECTNESS (an object emits after everything it deps on)\n// 2. kind ORDINAL (registration order) -> stable TIE-BREAK among independent objects (layering)\n// 3. OWNER clustering -> READABILITY (an index right after its table)\n// A per-kind ordinal ALONE is wrong: a table's event can call a function, so the function must emit\n// BEFORE the table — a function-before-table the graph handles and an ordinal cannot. Drops reverse it.\n\n// NOTE: `Diff`/`DiffItem` are a type-only import (erased at compile — no runtime cli->kind coupling),\n// the same arrangement as ./driver/portable-diff.ts.\nimport type { Diff, DiffItem } from \"../cli-kit/diff\";\nimport type {\n Definable,\n KindEngine,\n KindRegistry,\n PortableObject,\n Ref,\n} from \"./registry\";\n\nconst refKey = (r: Ref) => `${r.kind}:${r.name}`;\n\n/** A node in the dependency graph: identity + the edges/owner used to order it. */\nexport interface OrderNode {\n readonly kind: string;\n readonly name: string;\n /** Objects this node must come AFTER (only intra-set refs constrain; external refs are ignored). */\n readonly deps: Ref[];\n /** Owning object to cluster next to (readability tie-break only; never overrides `deps`). */\n readonly owner?: Ref;\n}\n\n/**\n * Kahn's topological sort with two presentation tweaks among the nodes whose deps are all satisfied:\n * prefer one OWNED by the currently-open cluster (so a table's children follow it), then lowest\n * (kind-ordinal, then name). Correctness (deps) always wins — an owned/low-ordinal node can't jump a\n * dependency. A genuine cycle throws (a named error). Refs to nodes outside `nodes` are ignored (an\n * object may depend on something untouched by this diff — it already exists / isn't changing).\n */\nexport function orderObjects<T extends OrderNode>(\n nodes: T[],\n ordinalOf: (kind: string) => number,\n): T[] {\n const byKey = new Map(nodes.map((n) => [refKey(n), n]));\n const indeg = new Map<string, number>(nodes.map((n) => [refKey(n), 0]));\n const dependents = new Map<string, string[]>();\n for (const n of nodes)\n for (const d of n.deps) {\n if (!byKey.has(refKey(d))) continue; // external dep -> not a constraint within this set\n indeg.set(refKey(n), (indeg.get(refKey(n)) ?? 0) + 1);\n const list = dependents.get(refKey(d)) ?? [];\n list.push(refKey(n));\n dependents.set(refKey(d), list);\n }\n\n const out: T[] = [];\n const done = new Set<string>();\n let group: string | undefined; // the last unowned node emitted == the open cluster\n while (out.length < nodes.length) {\n const ready = nodes.filter(\n (n) => !done.has(refKey(n)) && indeg.get(refKey(n)) === 0,\n );\n if (ready.length === 0)\n throw new Error(\n `dependency cycle among: ${nodes\n .filter((n) => !done.has(refKey(n)))\n .map(refKey)\n .join(\", \")}`,\n );\n ready.sort((a, b) => {\n const ao = a.owner && refKey(a.owner) === group ? 0 : 1; // prefer the open cluster\n const bo = b.owner && refKey(b.owner) === group ? 0 : 1;\n return (\n ao - bo ||\n ordinalOf(a.kind) - ordinalOf(b.kind) ||\n refKey(a).localeCompare(refKey(b))\n );\n });\n const next = ready[0];\n out.push(next);\n done.add(refKey(next));\n if (!next.owner) group = refKey(next); // a top-level object opens a new cluster\n for (const dep of dependents.get(refKey(next)) ?? [])\n indeg.set(dep, (indeg.get(dep) ?? 1) - 1);\n }\n return out;\n}\n\n// --- lowering + snapshot ------------------------------------------------------------------------\n\n/**\n * Author -> portable: lower each definable through its kind's engine (skipping unregistered kinds).\n * The single place authoring becomes portable; everything downstream (diff/emit/snapshot) is portable.\n */\nexport function lowerSchema(\n registry: KindRegistry,\n defs: Definable[],\n): PortableObject[] {\n const out: PortableObject[] = [];\n for (const d of defs) {\n const engine = registry.engine(d.kind);\n if (engine) out.push(engine.lower(d));\n }\n return out;\n}\n\n/**\n * The registry SNAPSHOT — portable objects grouped by kind. The open, generic replacement for\n * `PortableDb`'s fixed slots; serializes as plain JSON (it is plain data). Pre-launch: the format is\n * free to change, no version migration.\n */\nexport interface KindSnapshot {\n kinds: Record<string, PortableObject[]>;\n}\n\n/**\n * Group a flat portable schema into a snapshot (by kind). Pass `registry` to DROP kinds/objects\n * marked {@link KindEngine.excludeFromMigrations} (e.g. SurrealDB key-bearing access) so unmanaged,\n * secret-bearing objects never enter a snapshot / migration. Omit it to snapshot every object\n * unchanged.\n */\nexport function snapshotKinds(\n schema: PortableObject[],\n registry?: KindRegistry,\n): KindSnapshot {\n const kinds: Record<string, PortableObject[]> = {};\n for (const o of schema) {\n if (registry?.isExcludedFromMigrations(o)) continue;\n const bucket = kinds[o.kind] ?? [];\n bucket.push(o);\n kinds[o.kind] = bucket;\n }\n return { kinds };\n}\n\n/** Flatten a snapshot back into a portable schema (the inverse of {@link snapshotKinds}). */\nexport function snapshotObjects(snap: KindSnapshot): PortableObject[] {\n return Object.values(snap.kinds).flat();\n}\n\n// --- diff / plan --------------------------------------------------------------------------------\n\n/** One classified object change, carrying its ordering metadata + the portable sides for DDL. */\ninterface Change extends OrderNode {\n readonly op: \"add\" | \"change\" | \"remove\";\n readonly prev?: PortableObject;\n readonly next?: PortableObject;\n}\n\n/** An up/down DDL program (each a list of statements). */\nexport interface KindPlan {\n up: string[];\n down: string[];\n}\n\n/** The canonical change-detection key for an object — the kind's `canonical`, else its emitted DDL. */\nconst canonicalOf = (engine: KindEngine, p: PortableObject): string =>\n engine.canonical?.(p) ?? engine.emit(p).join(\"\\n\");\n\nconst orderNodeOf = (\n engine: KindEngine,\n portable: PortableObject,\n): OrderNode => ({\n kind: portable.kind,\n name: portable.name,\n deps: engine.deps?.(portable) ?? [],\n owner: engine.owner?.(portable),\n});\n\n/** Display identity: `kind:owner:name` (owner blank for a top-level object) + the display owner. */\nconst itemKey = (n: OrderNode) => `${n.kind}:${n.owner?.name ?? \"\"}:${n.name}`;\nconst itemTable = (n: OrderNode) => n.owner?.name ?? n.name;\n\nconst byKey = (schema: PortableObject[]) =>\n new Map(schema.map((o) => [refKey(o), o]));\n\n/**\n * Classify both sides into ordered add/change/remove sets — the shared core of plan + diff. A `change`\n * is two objects of the same key whose emitted DDL differs (same test as the fixed-slot engine). Each\n * class is topologically ordered parent-first; the caller reverses one class for drops/inversion.\n */\nfunction orderedChanges(\n registry: KindRegistry,\n prev: PortableObject[],\n next: PortableObject[],\n): { nonRemoves: Change[]; removes: Change[] } {\n const prevByKey = byKey(prev);\n const nextByKey = byKey(next);\n const changes: Change[] = [];\n for (const k of new Set([...prevByKey.keys(), ...nextByKey.keys()])) {\n const p = prevByKey.get(k);\n const n = nextByKey.get(k);\n const portable = n ?? p;\n if (!portable) continue;\n // Migration-unmanaged kinds/objects (e.g. key-bearing access) never diff — they're reconciled\n // out-of-band by driver commands, so they must not appear in gen/migrate/diff-live output.\n // Central choke point (a per-object predicate is fed the object that would be emitted).\n if (registry.isExcludedFromMigrations(portable)) continue;\n const engine = registry.engine(portable.kind);\n if (!engine) continue;\n const node = orderNodeOf(engine, portable);\n if (p && !n) changes.push({ op: \"remove\", prev: p, ...node });\n else if (!p && n) changes.push({ op: \"add\", next: n, ...node });\n else if (p && n && canonicalOf(engine, p) !== canonicalOf(engine, n))\n changes.push({ op: \"change\", prev: p, next: n, ...node });\n }\n const ord = (kind: string) => registry.ordinal(kind);\n return {\n nonRemoves: orderObjects(\n changes.filter((c) => c.op !== \"remove\"),\n ord,\n ),\n removes: orderObjects(\n changes.filter((c) => c.op === \"remove\"),\n ord,\n ),\n };\n}\n\nconst overwriteUp = (\n engine: KindEngine,\n a: PortableObject,\n b: PortableObject,\n): string[] =>\n engine.overwrite?.(a, b) ?? [...engine.remove(a), ...engine.emit(b)];\n\n/**\n * Diff two portable schema states into an executable up/down program, generically over the registry.\n *\n * `up` runs creates/changes parent-first (the dependency graph) then drops child-first; `down` is the\n * mirror: recreate drops parent-first, then undo creates/changes child-first. We invert PER OBJECT (not\n * by reversing the flat DDL list) so a kind's multi-line block — a table emitted with its fields —\n * keeps its internal order in both directions.\n */\nexport function planKinds(\n registry: KindRegistry,\n prev: PortableObject[],\n next: PortableObject[],\n): KindPlan {\n const { nonRemoves, removes } = orderedChanges(registry, prev, next);\n const up: string[] = [];\n const down: string[] = [];\n for (const c of nonRemoves) {\n const e = registry.engine(c.kind);\n if (!e) continue;\n if (c.op === \"add\" && c.next) up.push(...e.emit(c.next));\n else if (c.op === \"change\" && c.prev && c.next)\n up.push(...overwriteUp(e, c.prev, c.next));\n }\n for (const c of [...removes].reverse()) {\n const e = registry.engine(c.kind); // drops child-first\n if (e && c.prev) up.push(...e.remove(c.prev));\n }\n for (const c of removes) {\n const e = registry.engine(c.kind); // recreate dropped objects parent-first\n if (e && c.prev) down.push(...e.emit(c.prev));\n }\n for (const c of [...nonRemoves].reverse()) {\n const e = registry.engine(c.kind); // undo creates/changes child-first\n if (!e) continue;\n if (c.op === \"add\" && c.next) down.push(...e.remove(c.next));\n else if (c.op === \"change\" && c.prev && c.next)\n down.push(...overwriteUp(e, c.next, c.prev));\n }\n return { up, down };\n}\n\n/**\n * Display items for a change set, in up order (creates/changes parent-first, drops child-first). A kind\n * with `displayItems` decomposes into FINE-grained sub-items (per-field, each carrying its `table` so\n * the display groups them under it); otherwise it falls back to ONE whole-object item.\n */\nfunction diffItems(\n registry: KindRegistry,\n nonRemoves: Change[],\n removes: Change[],\n): DiffItem[] {\n const items: DiffItem[] = [];\n const push = (c: Change) => {\n const e = registry.engine(c.kind);\n if (!e) return;\n if (e.displayItems) {\n items.push(...e.displayItems(c.prev, c.next));\n return;\n }\n const base = { key: itemKey(c), table: itemTable(c), kind: c.kind };\n if (c.op === \"add\" && c.next)\n items.push({ ...base, op: \"add\", ddl: e.emit(c.next).join(\"\\n\") });\n else if (c.op === \"remove\" && c.prev)\n items.push({\n ...base,\n op: \"remove\",\n ddl: e.remove(c.prev).join(\"\\n\"),\n old: e.emit(c.prev).join(\"\\n\"),\n });\n else if (c.op === \"change\" && c.prev && c.next)\n items.push({\n ...base,\n op: \"change\",\n before: e.emit(c.prev).join(\"\\n\"),\n after: e.emit(c.next).join(\"\\n\"),\n });\n };\n for (const c of nonRemoves) push(c);\n for (const c of [...removes].reverse()) push(c);\n return items;\n}\n\n/**\n * The full {@link Diff} the CLI + migration model consume — up/down DDL + per-object display items +\n * the whole desired schema (`full`, for `--full`). This is what a driver's `Driver.diff` returns once\n * its kinds are on the registry (the generic counterpart of the fixed-slot `buildDiff`). Source-file\n * linkage on the items is attached by the caller (the snapshot's `files` map), so `file` is left unset.\n */\nexport function buildKindDiff(\n registry: KindRegistry,\n prev: PortableObject[],\n next: PortableObject[],\n): Diff {\n const { nonRemoves, removes } = orderedChanges(registry, prev, next);\n const { up, down } = planKinds(registry, prev, next);\n // `full` mirrors the items' granularity: a kind with `displayItems` projects its object as per-\n // sub-object adds (displayItems(undefined, portable)); otherwise one whole-object entry.\n const full = orderedSchema(registry, next).flatMap(\n ({ engine, portable, node }) => {\n if (engine.displayItems)\n return engine.displayItems(undefined, portable).map((it) => ({\n key: it.key,\n table: it.table,\n ddl: it.op === \"add\" ? it.ddl : \"\",\n }));\n return [\n {\n key: itemKey(node),\n table: itemTable(node),\n ddl: engine.emit(portable).join(\"\\n\"),\n },\n ];\n },\n );\n return { up, down, items: diffItems(registry, nonRemoves, removes), full };\n}\n\n/** Lower-already portable schema, topologically ordered, paired with each object's engine + node. */\nfunction orderedSchema(\n registry: KindRegistry,\n schema: PortableObject[],\n): { engine: KindEngine; portable: PortableObject; node: OrderNode }[] {\n const items = schema.flatMap((portable) => {\n const engine = registry.engine(portable.kind);\n return engine\n ? [{ engine, portable, node: orderNodeOf(engine, portable) }]\n : [];\n });\n const pos = new Map(\n orderObjects(\n items.map((i) => i.node),\n (k) => registry.ordinal(k),\n ).map((n, i) => [itemKey(n), i]),\n );\n return items.sort(\n (a, b) => (pos.get(itemKey(a.node)) ?? 0) - (pos.get(itemKey(b.node)) ?? 0),\n );\n}\n\n/**\n * Fresh-apply DDL for a portable schema: every object created, ordered across kinds by the graph.\n * (The `up` of a diff from an empty state.) Lower authoring first via {@link lowerSchema}.\n */\nexport function emitKinds(\n registry: KindRegistry,\n schema: PortableObject[],\n): string[] {\n // Skip migration-unmanaged kinds/objects (e.g. key-bearing access) — they're applied out-of-band\n // by driver commands.\n const managed = schema.filter(\n (o) => !registry.isExcludedFromMigrations(o),\n );\n return orderedSchema(registry, managed).flatMap(({ engine, portable }) =>\n engine.emit(portable),\n );\n}\n\n/**\n * Reverse direction, fanned out across kinds: introspect every introspectable kind off one live\n * connection and flatten into portable objects. The RESOLUTION of \"per-kind vs one driver read\":\n * the contract is per-kind ({@link KindEngine.introspect}), but a driver backs all of its kinds with\n * ONE shared (memoized) read of `conn` and slices out each kind's objects — so the fan-out here costs\n * a single round-trip, not N. A kind without `introspect` contributes nothing (not introspectable).\n */\nexport async function introspectKinds(\n registry: KindRegistry,\n conn: unknown,\n): Promise<PortableObject[]> {\n const out: PortableObject[] = [];\n for (const [kind, engine] of registry.entries()) {\n if (!engine.introspect) continue;\n // Skip STATICALLY migration-unmanaged kinds so the live side never phantom-diffs against a\n // schema that (by design) excludes them. A per-object predicate can't be evaluated without the\n // object — introspection still runs, and the diff choke point filters by object afterwards.\n if (registry.skipsIntrospection(kind)) continue;\n out.push(...(await engine.introspect(conn)));\n }\n return out;\n}\n","// The KIND REGISTRY — core-v2's generic, open replacement for the fixed object-kind slots.\n//\n// Today `PortableDb` hard-codes the object kinds a schema may contain (`tables`/`functions`/\n// `accesses`/`natives`) and the Driver's whole-DB methods switch on those slots. The kind registry\n// turns the slots into a REGISTRY a driver populates: each driver registers KINDS, and every kind\n// brings (a) its OWN authoring builder — any shape/chain it likes, fully typed — and (b) its engine\n// behavior (`lower`/`emit`/`remove`/`overwrite`/`deps`/`owner`/`introspect`) over THAT kind's objects.\n// Core orchestrates generically over the registry (see ./plan.ts) and never names a kind.\n//\n// What stays in core is the field/type VOCABULARY (`SFieldBase`, the Zod-drop-in `s.*`, `PortableType`,\n// codecs) — the substrate every kind builds on. Fields/types are NOT a kind: a table HAS fields, a\n// function's args ARE fields, an index REFERENCES fields. See docs/kind-registry.md.\n//\n// The registry is PER-DRIVER, not a module global: a driver builds one `KindRegistry` and registers\n// its kinds into it.\n\n/**\n * An authored definable, tagged with the KIND that owns it. Core dispatches on `kind` alone — every\n * other field is the kind's own business, handed straight to {@link KindEngine.lower}. This is the\n * neutral upper bound for a kind's authoring-object type (a driver's concrete `TableDef`/`FnDef` is a\n * structural subtype).\n */\nexport interface Definable {\n readonly kind: string;\n readonly name: string;\n}\n\n/**\n * A kind's PORTABLE object — the dialect-independent data shape core stores + diffs. A kind chooses\n * how structured this is: a table's portable form carries fields/indexes (so core can field-level\n * diff it); an opaque kind (function/access) carries a neutral identity + a `native` payload it\n * round-trips. Either way it is tagged with `kind`/`name` for cross-kind dispatch + ordering.\n */\nexport interface PortableObject {\n readonly kind: string;\n readonly name: string;\n}\n\n/** A reference to another object in the schema graph — the unit of cross-kind dependency ordering. */\nexport interface Ref {\n readonly kind: string;\n readonly name: string;\n}\n\n// `DiffItem` is a type-only import (erased at compile) — the display contract the `displayItems` hook\n// produces; no runtime cli->kind coupling, same arrangement as ./plan.ts.\nimport type { DiffItem } from \"../cli-kit/diff\";\n\n/**\n * What core needs to orchestrate ONE kind generically — it never inspects the specifics. The\n * change-vocabulary (`emit`/`remove`/`overwrite`) mirrors the Driver contract's, so a kind's behavior\n * is parity-checkable against the fixed-slot engine. `A` is the kind's authoring object, `P` its\n * portable object; both are opaque to core beyond the {@link Definable}/{@link PortableObject} bounds.\n */\nexport interface KindEngine<\n A extends Definable = Definable,\n P extends PortableObject = PortableObject,\n> {\n /** Authoring object -> this kind's portable object (normalized; both lowerings must converge here). */\n lower(authored: A): P;\n /** CREATE DDL for one portable object (a fresh apply / migration `up` for an added object). */\n emit(portable: P): string[];\n /** DROP DDL for one portable object (`up` for a removed object, `down` for an added one). */\n remove(portable: P): string[];\n /**\n * In-place CHANGE DDL taking `prev` to `next` (the dialect's ALTER/OVERWRITE). The spine calls\n * `overwrite(next, prev)` to roll a change back. A kind with no in-place form recreates: implement\n * as `[...remove(prev), ...emit(next)]`. Default (omitted) = recreate via emit(next).\n */\n overwrite?(prev: P, next: P): string[];\n /**\n * The CANONICAL change-detection key: the spine treats prev/next of the same object as a CHANGE iff\n * their `canonical` differs. Default (omitted) = `emit(portable).join(\"\\n\")` — so a kind whose `emit`\n * is already its canonical form needs nothing. Override when `emit` is FAITHFUL but some clauses must\n * be EXCLUDED from equality — because the DB rewrites them on read (a cast suffix like `'x'::text`,\n * added parens like `(a>0)`) or never introspects them (a COMMENT, an index) — so a faithful `emit` would phantom-diff a\n * freshly-applied schema against `introspect`. Return `emit` MINUS those clauses: they stay create-time\n * faithful in `emit`, but don't count as changes. `canonical(a) === canonical(b)` MUST mean \"no\n * migration needed\". Affects ONLY classification; `emit`/`overwrite` (the DDL) are unaffected.\n */\n canonical?(portable: P): string;\n /**\n * Fine-grained DISPLAY items for a change of this object — so `better-schemic diff` shows per-SUB-OBJECT\n * changes (a table decomposes into per-FIELD items: `field:user:name` changed), each carrying its\n * owner `table` so the display GROUPS them hierarchically under it, instead of one coarse whole-object\n * item. Called `(prev, next)`: a change diffs the two; `(undefined, next)` lists the object's\n * sub-items as adds — the `--full` projection core uses for the full desired-state view. Default\n * (omitted) = ONE whole-object item. DISPLAY ONLY — never affects up/down DDL (that is\n * `emit`/`overwrite`); a structured driver reuses the per-field diff it already computes. Leave\n * `DiffItem.file` unset (the caller attaches source linkage).\n */\n displayItems?(prev: P | undefined, next: P | undefined): DiffItem[];\n /**\n * Objects this one must be emitted AFTER — the cross-kind dependency edges (a field/index -> its\n * table; an edge table -> its in/out tables; an event -> its table + any function it calls). Drives\n * the topological sort in ./plan.ts. Omitted = no dependencies.\n */\n deps?(portable: P): Ref[];\n /**\n * The owning object to CLUSTER next to in the emitted order (an index's table) — readability only,\n * never overrides {@link deps}. Omitted = a top-level object.\n */\n owner?(portable: P): Ref | undefined;\n /**\n * The STRUCTURAL container this object is nested within (an index's/field's table) — used for\n * ADDRESSING and grouping (the CLI's dotted `parent.child`, e.g. `sc index info user.email_idx`) and\n * distinct from {@link owner}, which is a DISPLAY choice (diff clustering). A kind may declare `parent`\n * (it IS nested) while declining `owner` (it doesn't want per-parent diff clustering), or vice versa.\n * The CLI resolves a nesting address as `parent ?? owner` (so a kind that only sets `owner` still\n * addresses dotted). Omitted (and no `owner`) = a top-level object, addressed by its bare name.\n */\n parent?(portable: P): Ref | undefined;\n /**\n * Live connection -> all portable objects of THIS kind (the reverse direction). Introspection is\n * often one `INFO`/catalog read yielding every kind at once; a driver backs all of its kinds'\n * `introspect` with one shared (memoized) read and slices out this kind's objects. Omitted -> this\n * kind isn't introspectable (diff/emit still work from authored state).\n */\n introspect?(conn: unknown): Promise<P[]>;\n /**\n * How this kind is PRESENTED — its human labels and the folder its objects render into. All optional\n * with sensible defaults off the kind name (see {@link KindRegistry.display}), so a kind only declares\n * what the defaults get wrong (e.g. `plural: \"Indexes\"`, or `folder: \"access\"`). DISPLAY ONLY.\n */\n display?: KindDisplay;\n /**\n * UNMANAGED by the migration pipeline: when `true`, objects of this kind are EXCLUDED from\n * snapshot / diff / gen AND from the introspect-compare — so they never enter a migration file nor\n * phantom-diff. For a kind whose lifecycle doesn't fit committed migrations: e.g. SurrealDB\n * `DEFINE ACCESS`, which carries a secret the DB redacts on introspection (can't round-trip) and\n * rotates on its own cadence. Such a kind is managed OUT-OF-BAND via the driver's own commands\n * (`sc <kind> …`). `emit`/`lower` still work (a driver command may use them); only the automatic\n * migration lifecycle skips it. Omitted/false = a normal, migration-managed kind.\n *\n * A PREDICATE form (`(portable) => boolean`) decides PER OBJECT — for a kind whose objects are\n * managed only when they round-trip cleanly (e.g. SurrealDB access: key-free defs are manageable,\n * key-bearing/redacted ones are not). The object is REQUIRED by\n * {@link KindRegistry.isExcludedFromMigrations}; only a boolean `true` also skips introspection\n * ({@link KindRegistry.skipsIntrospection}) — a predicate can't be decided there.\n */\n excludeFromMigrations?: boolean | ((portable: PortableObject) => boolean);\n}\n\n/** Per-kind presentation metadata (labels + output folder). All optional; core fills defaults. */\nexport interface KindDisplay {\n /** Title-Case singular, e.g. `\"Table\"`, `\"Field\"`. Default: the kind name, capitalized. */\n label?: string;\n /** Title-Case plural, e.g. `\"Tables\"`, `\"Indexes\"`. Default: the English plural of `label`. */\n plural?: string;\n /** The directory this kind's objects render into. Default: the lowercase slug of `plural`. */\n folder?: string;\n}\n\n/** A kind's resolved presentation — every field filled (the shape {@link KindRegistry.display} returns). */\nexport type ResolvedDisplay = Required<KindDisplay>;\n\n/** A kind's full spec: its `name`, its `build` (the driver's authoring entry), and its engine. */\nexport type KindSpec<\n Build extends (...args: never[]) => unknown,\n A extends Definable,\n P extends PortableObject,\n> = { name: string; build: Build } & KindEngine<A, P>;\n\n/** `\"table\"` -> `\"Table\"`. */\nfunction capitalize(s: string): string {\n return s ? s[0].toUpperCase() + s.slice(1) : s;\n}\n\n/** A plain English pluralizer for kind labels: `Index` -> `Indexes`, `Policy` -> `Policies`. */\nfunction pluralize(s: string): string {\n if (/[^aeiou]y$/i.test(s)) return `${s.slice(0, -1)}ies`;\n if (/(s|x|z|ch|sh)$/i.test(s)) return `${s}es`;\n return `${s}s`;\n}\n\n/** `\"Tables\"` -> `\"tables\"`; collapses non-alphanumerics to single dashes (a filesystem-safe folder). */\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/**\n * A driver's set of registered kinds + the generic behavior the spine reads off them. Built once per\n * driver; `define` registers a kind and returns the driver's OWN `build` function UNCHANGED — so the\n * driver writes `export const defineTable = registry.define({ name: \"table\", build, ...engine })` and\n * keeps full type-safety + DX (TS preserves a generic `build`'s parameters across the passthrough).\n */\nexport class KindRegistry {\n // Heterogeneous kinds erase at the engine seam (engine ops are structural); the AUTHORING side\n // keeps full types via `define`'s `Build` passthrough.\n // biome-ignore lint/suspicious/noExplicitAny: the engine seam is intentionally type-erased.\n private readonly kinds = new Map<string, KindEngine<any, any>>();\n\n /**\n * Register a KIND. `build` is the driver's own authoring entry — ANY shape/chain — and its type\n * flows through unchanged (type-safety + DX are the driver's to design). The engine fns give core\n * the generic behavior. Registration ORDER is the kind's ordinal (the stable tie-break among\n * independent objects in {@link orderObjects}), so register coarse-to-fine (table before index).\n */\n define<\n Build extends (...args: never[]) => unknown,\n A extends Definable,\n P extends PortableObject,\n >(spec: KindSpec<Build, A, P>): Build {\n this.kinds.set(spec.name, spec);\n return spec.build;\n }\n\n /** The engine for `kind`, or undefined if no such kind is registered. */\n // biome-ignore lint/suspicious/noExplicitAny: the engine erases at this seam (see `kinds`).\n engine(kind: string): KindEngine<any, any> | undefined {\n return this.kinds.get(kind);\n }\n\n /**\n * Is this PORTABLE OBJECT UNMANAGED by the migration pipeline (its engine set\n * {@link KindEngine.excludeFromMigrations})? The snapshot/diff/emit spine skips such objects — see\n * the flag's docs. The OBJECT is required: a per-object predicate can't be evaluated without it, and\n * defaulting to \"managed\" would let a key-bearing object slip into a migration. An unregistered kind\n * is treated as managed (false), so a stray object never gets silently dropped by a typo.\n */\n isExcludedFromMigrations(portable: PortableObject): boolean {\n const flag = this.kinds.get(portable.kind)?.excludeFromMigrations;\n return typeof flag === \"function\" ? flag(portable) : flag === true;\n }\n\n /**\n * Whether `kind` is STATICALLY excluded from migrations (`excludeFromMigrations === true`), so even\n * introspection skips it. A per-object PREDICATE can't be decided without an object: introspection\n * runs for the kind, and the diff/snapshot/emit choke points filter by object afterwards.\n */\n skipsIntrospection(kind: string): boolean {\n return this.kinds.get(kind)?.excludeFromMigrations === true;\n }\n\n /**\n * A kind's resolved presentation — `label`/`plural`/`folder`, with defaults derived from the kind\n * name for whatever the driver left unset. Works for unregistered display sub-kinds too (e.g. the\n * `\"field\"` items a table's `displayItems` emits) — they just get the name-derived defaults.\n */\n display(kind: string): ResolvedDisplay {\n const d = this.kinds.get(kind)?.display ?? {};\n const label = d.label ?? capitalize(kind);\n const plural = d.plural ?? pluralize(label);\n return { label, plural, folder: d.folder ?? slugify(plural) };\n }\n\n /** Registered kind names, in registration order (== ordinal order). */\n names(): string[] {\n return [...this.kinds.keys()];\n }\n\n /**\n * A kind's ORDINAL = its registration index. Used ONLY as a tie-break among objects with no\n * dependency relation, so independent objects come out stably layered (readability); it never\n * overrides the dependency graph. An unknown kind sorts last.\n */\n ordinal(kind: string): number {\n const i = this.names().indexOf(kind);\n return i === -1 ? Number.MAX_SAFE_INTEGER : i;\n }\n\n /** [name, engine] pairs in registration order — the spine iterates these. */\n // biome-ignore lint/suspicious/noExplicitAny: the engine erases at this seam (see `kinds`).\n entries(): [string, KindEngine<any, any>][] {\n return [...this.kinds.entries()];\n }\n}\n","// The DRIVER interface — the dialect seam (see docs/MULTI-DB-SPIKE.md).\n//\n// Everything dialect-specific lives behind a `Driver`: lowering authoring to the Struct-IR, emitting\n// DDL, introspecting a live DB, normalizing to a canonical form, and executing. Everything ABOVE the\n// driver (the diff algorithm, the magicast TS-merge, the migration model, the CLI shell) stays\n// dialect-free and calls these ops.\n//\n// The connection type is a driver-private parameter `Conn`: the orchestration treats it opaquely and\n// only ever hands it back to the SAME driver. So the Surreal driver is `Driver<Surreal>`, and core\n// never sees the concrete type. The AUTHORING\n// types (`Tbl`/`Def`) are driver-private the same way — opaque to core beyond the neutral\n// `Authored`/`AuthoredDef` bounds — so the neutral engine never names a dialect's concrete builder\n// (`TableDef`/`StandaloneDef`).\n\nimport type { ResolvedConfig } from \"../cli-kit/config\";\nimport type { Diff } from \"../cli-kit/diff\";\nimport type { Filter } from \"../cli-kit/filter\";\nimport type { PullPlan } from \"../cli-kit/merge\";\nimport type { Definable, KindRegistry, PortableObject } from \"../kind\";\nimport type { SecretProvider, SecretRef } from \"../secrets\";\n\n/**\n * The dialect-NEUTRAL authoring contract — the only structure the orchestration reads off an\n * authored object (everything else is opaque and handed straight to {@link Driver.explode}). A table\n * contributes just its `name`; this is the upper bound for a driver's table-authoring type. The\n * Surreal `TableDef` is a structural subtype, as is any future dialect's table builder.\n */\nexport interface Authored {\n readonly name: string;\n}\n\n/**\n * The neutral contract for a standalone (non-table) authored object — an event/function/access. It\n * adds a `kind` discriminant and, for objects owned by a table (e.g. an event), the owner `table`\n * name (so the snapshot can file-link a child object under its parent). The Surreal `StandaloneDef`\n * union is a structural subtype.\n */\nexport interface AuthoredDef extends Authored {\n readonly kind: string;\n readonly table?: string;\n}\n\n/**\n * A single emitted DDL statement, structured: object identity (`kind`/`name`/`table`) + the dialect\n * `ddl` string, plus an optional clause map (each value an `ALTER … <set>` form) for dialects that\n * diff clause-level. `kind` is a dialect-defined string the orchestration treats opaquely — the\n * SurrealDB `DefineStatement` (with its fixed kind union) is a structural subtype of this.\n */\nexport interface Statement {\n kind: string;\n name: string;\n table?: string;\n ddl: string;\n clauses?: Record<string, string>;\n /**\n * Apply-time secret bindings for this statement's `$param` placeholders — `param` name ->\n * a write-only {@link SecretRef}. Collected into {@link Diff.bindings}; the value never lives here\n * (resolved at apply through a `SecretProvider`). See {@link Diff.bindings}.\n */\n bindings?: Record<string, SecretRef>;\n}\n\n/** Options for {@link Driver.emit} — mirrors the existing `DefineOptions` (e.g. IF NOT EXISTS). */\nexport interface EmitOptions {\n ifNotExists?: boolean;\n overwrite?: boolean;\n}\n\n/** Options for {@link Driver.apply}. */\nexport interface ApplyOptions {\n /**\n * Run the whole batch atomically. `migrate` wraps up/down + `_migrations` bookkeeping in one\n * transaction; a driver that can't MUST surface that (the migration model degrades to best-effort).\n */\n transactional?: boolean;\n}\n\n/** Per-connection overrides (url/namespace/credentials) — superset across dialects. */\nexport interface ConnectionOverrides {\n url?: string;\n namespace?: string;\n database?: string;\n username?: string;\n password?: string;\n authLevel?: string;\n}\n\n/** The direction a migration is applied in. */\nexport type MigrationDirection = \"up\" | \"down\";\n\n/** A migration's bookkeeping identity, recorded in the migrations-tracking table. */\nexport interface MigrationRecord {\n tag: string;\n file: string;\n /** sha of the migration file at apply time (drift detection). */\n checksum: string;\n}\n\n/**\n * The apply-time, dialect-specific half of the migration runner. The orchestration (which\n * migrations are pending, ordering, the lock-then-loop) stays driver-neutral in cli/migrate.ts;\n * this capability owns the SQL: the tracking table, the applied-records, the advisory lock, and the\n * atomic apply+record. A driver WITHOUT it can't run migrations (diff/gen still work). `Conn` is the\n * driver's own connection type.\n */\nexport interface MigrationStore<Conn = unknown> {\n /** This dialect's migration-file extension, e.g. `\".surql\"` (SurrealDB). */\n readonly extension: string;\n /** Render a diff as this dialect's migration-file body (e.g. SurrealQL `IF $direction` up/down). */\n render(tag: string, diff: Diff): string;\n /** Ensure the migrations-tracking table exists. */\n ensure(conn: Conn, table: string): Promise<void>;\n /** Applied migrations: tag -> checksum recorded at apply time. */\n applied(conn: Conn, table: string): Promise<Map<string, string>>;\n /**\n * Apply one migration's `up`/`down` PROGRAM plus its bookkeeping write atomically: on `up` record\n * the migration, on `down` erase it — so the record is written iff the DDL actually applied.\n */\n apply(\n conn: Conn,\n table: string,\n m: {\n content: string;\n direction: MigrationDirection;\n record: MigrationRecord;\n },\n ): Promise<void>;\n /** Record a migration as applied WITHOUT running its DDL (baseline of an existing DB). */\n record(conn: Conn, table: string, record: MigrationRecord): Promise<void>;\n /** Drop all applied records (baseline-squash reconcile). */\n clear(conn: Conn, table: string): Promise<void>;\n /** Take an advisory lock so two runs can't race — throws if already held. */\n lock(conn: Conn, table: string): Promise<void>;\n /** Release the advisory lock (idempotent). */\n unlock(conn: Conn, table: string): Promise<void>;\n}\n\n/**\n * An OPTIONAL throwaway-instance capability for round-trip canonicalization and `sz check`'s\n * migration replay. Absent -> `check`/replay-verification is degraded/unavailable (diff/apply still\n * work, since a kind's `lower`/`introspectAll` already canonicalize).\n */\nexport interface ShadowCapability<Conn> {\n /** Apply `ddl` to a fresh scratch DB, introspect it back to portable objects, then drop it. */\n roundTrip(\n conn: Conn,\n config: ResolvedConfig,\n ddl: string,\n ): Promise<PortableObject[]>;\n /** Spin up a fully-isolated ephemeral instance (for migration replay). Caller must `stop()`. */\n ephemeral?(): Promise<{ conn: Conn; stop: () => Promise<void> }>;\n}\n\n/**\n * User-defined DB functions — the `.call` side of the query layer's (B) surface (DB functions as code).\n * `invoke` calls a defined function by name with already-encoded args and returns the function's RAW\n * result (the driver extracts it from its own response shape — surreal `RETURN fn::name($a)` yields the\n * value; a row-returning call yields a row set). The caller decodes that raw value through the\n * function's `.returns(R)` schema via `callFunction` in `@better-schemic/core/query`. A defined function still\n * emits/migrates via the schema engine regardless; this capability only adds INVOCATION.\n */\nexport interface CallableFunctions<Conn = unknown> {\n invoke(\n conn: Conn,\n name: string,\n args: Record<string, unknown>,\n ): Promise<unknown>;\n}\n\n// --- driver-contributed CLI commands -----------------------------------------------------------\n// A driver may contribute dialect-specific commands invoked as `sc <kind> <verb> [args]` (e.g. surreal\n// `sc access rotate <name>`). CORE provides only the general\n// mechanism: it discovers `driver.commands`, registers each, parses argv against `args`, resolves the\n// connection, and dispatches to `run` with a {@link CommandContext}. The DRIVER owns the dialect logic\n// and the meaning of each kind/verb/arg — core never names one. Depth is fixed at kind/verb.\n\n/** A declared argument for a {@link DriverCommand} — used for parsing + `--help`. */\nexport interface CommandArgs {\n /**\n * Positional args, in order. Core collects ALL positional tokens into a list (incl. raw `key=value`)\n * and the driver interprets + validates arity; mark the last `variadic` for \"one or more\".\n */\n positionals?: readonly {\n name: string;\n required?: boolean;\n variadic?: boolean;\n help?: string;\n }[];\n /** Named flags. `value: true` takes a value (`--user U`); otherwise it's a boolean (`--dry-run`). */\n flags?: readonly {\n name: string;\n value?: boolean;\n required?: boolean;\n help?: string;\n }[];\n}\n\n/** argv parsed against a command's {@link CommandArgs}: positional tokens (driver-interpreted) + flags. */\nexport interface ParsedCommandArgs {\n /** Every positional token in order (incl. raw `key=value`); the driver interprets them. */\n positionals: string[];\n /** Flags: a value-flag -> its string, a boolean flag -> `true`, an absent flag -> `undefined`. */\n flags: Record<string, string | boolean | undefined>;\n}\n\n/** Output + input helpers core hands a command (so a driver never touches stdio directly). */\nexport interface CommandIo {\n ok(message: string): void;\n fail(message: string): void;\n info(message: string): void;\n /** Prompt for a line of input (`hidden` masks it) — for a sensitive value not passed inline (no shell-history leak). */\n prompt(question: string, opts?: { hidden?: boolean }): Promise<string>;\n}\n\n/** The context core hands a {@link DriverCommand.run}: a connected db + the resolved config + io + secrets. */\nexport interface CommandContext<Conn = unknown> {\n conn: Conn;\n config: ResolvedConfig;\n io: CommandIo;\n /** The configured secret provider (default reads `process.env`) for resolving {@link SecretRef}s. */\n secrets: SecretProvider;\n}\n\n/**\n * A driver-contributed CLI command — `sc <kind> <verb> [args]`. Core dispatches; the driver owns the\n * dialect logic in {@link run}. The driver validates positional arity itself (core only collects them).\n */\nexport interface DriverCommand<Conn = unknown> {\n /** Kind namespace — `sc <kind> …` (e.g. `\"access\"`, `\"table\"`). */\n kind: string;\n /** Verb under the kind — `sc <kind> <verb>` (e.g. `\"rotate\"`, `\"check\"`, `\"find\"`). */\n verb: string;\n /** One-line help shown in the command listing. */\n summary: string;\n /** Declared args for parsing + `--help`. */\n args?: CommandArgs;\n run(ctx: CommandContext<Conn>, args: ParsedCommandArgs): Promise<void>;\n}\n\n/**\n * A database dialect, expressed as a SET OF KINDS (core-v2). The driver registers its object kinds on\n * `registry`; core orchestrates schema ops GENERICALLY over it (`lowerSchema`/`buildKindDiff`/\n * `emitKinds`/`orderObjects`) — it never names a kind. The driver owns only what isn't generic: the\n * authoring -> kinded `explode`, a single-read `introspectAll`, the connection lifecycle, and the\n * dialect-specific command capabilities. The field/type substrate (`PortableType`/`s.*`) stays core.\n * See docs/kind-registry-flip-plan.md.\n */\nexport interface Driver<\n Conn = unknown,\n Tbl extends Authored = Authored,\n Def extends AuthoredDef = AuthoredDef,\n> {\n readonly name: string;\n\n // --- kind registry (the schema engine) -----------------------------------------------------\n /** This driver's registered KINDS. Core runs lower/diff/emit/order generically over it. */\n readonly registry: KindRegistry;\n /**\n * Authoring (loaded `defineTable`/standalone defs) -> kinded {@link Definable}s. The driver-side\n * fan-out: one inline-authored table explodes into `[table, ...index, ...event/constraint]`, each\n * tagged with its `kind`. Core then lowers via `lowerSchema(registry, explode(...))` — so\n * `KindEngine.lower` stays 1:1 and the contract needs no explode hook.\n */\n explode(tables: Tbl[], defs: Def[]): Definable[];\n /**\n * Live connection -> ALL portable objects, fanned across kinds from ONE read (INFO STRUCTURE /\n * the system catalog). Must canonicalize IDENTICALLY to lowering (a clean apply round-trips to a zero diff)\n * and be COMPLETE (return every diffable kind, else presence-phantom-diffs). `exclude` skips tables\n * by name.\n */\n introspectAll(conn: Conn, exclude?: Set<string>): Promise<PortableObject[]>;\n\n // --- execution -----------------------------------------------------------------------------\n connect(config: ResolvedConfig, over?: ConnectionOverrides): Promise<Conn>;\n apply(conn: Conn, statements: string[], opts?: ApplyOptions): Promise<void>;\n /** Tear down a connection opened by {@link connect} (the orchestration owns the lifecycle). */\n close(conn: Conn): Promise<void>;\n\n // --- optional capabilities -----------------------------------------------------------------\n readonly shadow?: ShadowCapability<Conn>;\n /** Apply-time migration bookkeeping. Absent -> this driver can't run migrations (diff/gen still do). */\n readonly migrations?: MigrationStore<Conn>;\n\n // --- optional COMMAND capabilities ---------------------------------------------------------\n // The dialect-agnostic CLI routes each schema-syncing command through one of these. A driver that\n // omits a capability makes that command unavailable on it — the CLI never hardcodes `if surreal`.\n\n /**\n * Diff the LIVE database against the loaded schema into executable up/down DDL. Owns every\n * dialect-specific normalization and apply-time fixup (Surreal: a shadow-DB round-trip to cancel\n * formatting noise, the redacted-access-key swap, and the implicit-wildcard OVERWRITE re-mark), so\n * the result is safe to apply as-is. Backs `diff --live`, `push`, and the baseline reconcile.\n */\n diffLive?(conn: Conn, config: ResolvedConfig, filter: Filter): Promise<Diff>;\n /** Reduce a live diff (from {@link diffLive}) to the statements `push` applies; `prune: false` keeps removals. */\n syncPlan?(diff: Diff, prune?: boolean): string[];\n /**\n * Dialect-specific CLI commands invoked as `sc <kind> <verb> [args]` — e.g. surreal `access rotate`.\n * Core discovers + dispatches them generically (see {@link DriverCommand});\n * it never names a kind/verb. Absent -> this driver contributes no extra commands.\n */\n readonly commands?: readonly DriverCommand<Conn>[];\n /**\n * Render portable objects to per-file source in THIS dialect's `s.*` syntax, filtered — the codegen\n * behind the offline `diff --ts` and `pull`. Takes `PortableObject[]` (this driver's own portable\n * shape): `diff --ts` renders the SNAPSHOT side (stored portable) and the DESIRED side\n * (`lowerSchema(explode(...))`) at MATCHING fidelity so an in-sync schema yields identical files;\n * `pull` renders the introspected DB. The driver re-derives its structured form from the portable\n * objects (parsing its own DDL where needed — docs/kind-registry-flip-plan.md §6b). `single` (a file\n * key) folds everything into one module; otherwise `fileFor` maps each object to its own file.\n */\n renderSchema?(\n objects: PortableObject[],\n filter: Filter,\n fileFor: (kind: string, name: string) => string,\n single?: string,\n ): Map<string, string>;\n /**\n * The two sides of `diff --ts --live` rendered to per-file source: the live DB (`current`) and the\n * declared schema (`desired`), both normalized through the dialect so an unchanged schema yields\n * identical files.\n */\n diffTsLive?(\n conn: Conn,\n config: ResolvedConfig,\n filter: Filter,\n fileFor: (kind: string, name: string) => string,\n single?: string,\n ): Promise<{ current: Map<string, string>; desired: Map<string, string> }>;\n /**\n * Replay every migration into a throwaway engine and diff the result against the schema (`check`).\n * Owns ephemeral-engine selection + setup; `log` receives progress lines. Needs a {@link shadow}-\n * class capability. An empty diff means the migrations reproduce the schema.\n */\n checkReplay?(\n config: ResolvedConfig,\n over: ConnectionOverrides,\n filter: Filter,\n log: (msg: string) => void,\n ): Promise<Diff>;\n /** Introspect the live DB and plan schema-file codegen (`pull`); writing is the neutral `applyPull`. */\n planPull?(\n conn: Conn,\n config: ResolvedConfig,\n opts: { filter: Filter; keepLocal?: boolean },\n ): Promise<PullPlan>;\n /** A human-readable server identity for `doctor` (e.g. \"SurrealDB 3.1.3\"); throws if unreachable. */\n serverInfo?(conn: Conn): Promise<string>;\n /**\n * Run a raw READ query and return rows — for connection RESOLVERS (a multi-connection resolver's\n * `ctx.connections.<name>.query(...)`) and `seed`. The `sql` is this dialect's query language; the\n * orchestration treats the rows opaquely. Absent -> a resolver can't read from this connection.\n */\n query?<T = unknown>(\n conn: Conn,\n sql: string,\n vars?: Record<string, unknown>,\n ): Promise<T[]>;\n /**\n * User-defined DB functions ((B) of the query layer) — invoke a defined function + decode the result.\n * Absent -> no `.call()` surface on this driver (the function still emits/migrates). See\n * {@link CallableFunctions}.\n */\n readonly callable?: CallableFunctions<Conn>;\n /**\n * The dialect-specific files `better-schemic init` scaffolds, keyed by project-relative path: a\n * connections-only `better-schemic.config.ts` (using this driver's `<driver>Connection` factory), a sample\n * schema module in this dialect's `s.*`, a seed stub, a `.env.example`, … The CLI writes them\n * verbatim (never overwriting) alongside the dialect-neutral migration snapshot it records itself.\n * Absent -> `better-schemic init` can't scaffold a project for this driver.\n */\n initScaffold?(): Record<string, string>;\n /**\n * Scaffold a NEW entity file's contents — the starter `s.*` / `define*` module for an object of\n * `kind` named `name` (e.g. `(\"table\", \"user\")` -> a `defineTable(\"user\", { … })` module in this\n * dialect's authoring). Returns the file text; the CLI writes it under the kind's\n * {@link KindRegistry.display} folder. THROW for a kind this driver can't author (the CLI surfaces\n * the message). Absent -> `better-schemic new` is unavailable for this driver.\n */\n scaffoldEntity?(kind: string, name: string): string;\n}\n\n// --- Registry -----------------------------------------------------------------------------------\n\n// Shared across every loaded copy of `@better-schemic/core` so the registry is process-global — the CLI run\n// via `bunx` resolves its own core, while a driver loaded from the user's project resolves the\n// project's core; without sharing, the driver self-registers in one Map and the CLI reads an empty\n// one. Keyed by a REGISTERED symbol (`Symbol.for`) — same key in every instance, but no string-keyed\n// `globalThis` pollution and namespaced so it can't collide.\nconst REGISTRY_KEY = Symbol.for(\"@better-schemic/core.driverRegistry\");\nconst REGISTRY: Map<string, Driver<unknown>> = ((\n globalThis as Record<symbol, Map<string, Driver<unknown>> | undefined>\n)[REGISTRY_KEY] ??= new Map<string, Driver<unknown>>());\n\n/** Register a driver under its `name` (idempotent; last write wins). */\nexport function registerDriver(driver: Driver<unknown>): void {\n REGISTRY.set(driver.name, driver);\n}\n\n/** Look up a registered driver, or throw with the list of known names. */\nexport function getDriver(name: string): Driver<unknown> {\n const d = REGISTRY.get(name);\n if (!d) {\n const known = [...REGISTRY.keys()].join(\", \") || \"(none registered)\";\n throw new Error(`Unknown database driver \"${name}\". Registered: ${known}.`);\n }\n return d;\n}\n\n/** All registered driver names (for help text / config validation). */\nexport function driverNames(): string[] {\n return [...REGISTRY.keys()];\n}\n"],"mappings":"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","names":["byKey"]}
|
|
1
|
+
{"version":3,"sources":["../src/kind/plan.ts","../src/kind/registry.ts","../src/driver/driver.ts"],"sourcesContent":["// The GENERIC migration spine over a {@link KindRegistry} — core's kind-blind orchestration. It\n// classifies each portable object as add/change/remove, ORDERS them across kinds by a dependency\n// graph, and emits up/down DDL + the display {@link Diff}. It never names a kind: every kind-specific\n// decision is delegated to that kind's {@link KindEngine}.\n//\n// The spine works on PORTABLE objects (both sides already lowered), exactly like the fixed-slot\n// `Driver.diff(prev, next)`: the stored snapshot IS portable, and the authoring side is lowered once\n// via {@link lowerSchema}. So `prev` is a snapshot, `next` is `lowerSchema(registry, defs)`.\n//\n// Cross-kind ordering is the load-bearing part (docs/kind-registry.md §7.1). THREE layers:\n// 1. dependency GRAPH + topological sort -> CORRECTNESS (an object emits after everything it deps on)\n// 2. kind ORDINAL (registration order) -> stable TIE-BREAK among independent objects (layering)\n// 3. OWNER clustering -> READABILITY (an index right after its table)\n// A per-kind ordinal ALONE is wrong: a table's event can call a function, so the function must emit\n// BEFORE the table — a function-before-table the graph handles and an ordinal cannot. Drops reverse it.\n\n// NOTE: `Diff`/`DiffItem` are a type-only import (erased at compile — no runtime cli->kind coupling),\n// the same arrangement as ./driver/portable-diff.ts.\nimport type { Diff, DiffItem } from \"../cli-kit/diff\";\nimport type {\n Definable,\n KindEngine,\n KindRegistry,\n PortableObject,\n Ref,\n} from \"./registry\";\n\nconst refKey = (r: Ref) => `${r.kind}:${r.name}`;\n\n/** A node in the dependency graph: identity + the edges/owner used to order it. */\nexport interface OrderNode {\n readonly kind: string;\n readonly name: string;\n /** Objects this node must come AFTER (only intra-set refs constrain; external refs are ignored). */\n readonly deps: Ref[];\n /** Owning object to cluster next to (readability tie-break only; never overrides `deps`). */\n readonly owner?: Ref;\n}\n\n/**\n * Kahn's topological sort with two presentation tweaks among the nodes whose deps are all satisfied:\n * prefer one OWNED by the currently-open cluster (so a table's children follow it), then lowest\n * (kind-ordinal, then name). Correctness (deps) always wins — an owned/low-ordinal node can't jump a\n * dependency. A genuine cycle throws (a named error). Refs to nodes outside `nodes` are ignored (an\n * object may depend on something untouched by this diff — it already exists / isn't changing).\n */\nexport function orderObjects<T extends OrderNode>(\n nodes: T[],\n ordinalOf: (kind: string) => number,\n): T[] {\n const byKey = new Map(nodes.map((n) => [refKey(n), n]));\n const indeg = new Map<string, number>(nodes.map((n) => [refKey(n), 0]));\n const dependents = new Map<string, string[]>();\n for (const n of nodes)\n for (const d of n.deps) {\n if (!byKey.has(refKey(d))) continue; // external dep -> not a constraint within this set\n indeg.set(refKey(n), (indeg.get(refKey(n)) ?? 0) + 1);\n const list = dependents.get(refKey(d)) ?? [];\n list.push(refKey(n));\n dependents.set(refKey(d), list);\n }\n\n const out: T[] = [];\n const done = new Set<string>();\n let group: string | undefined; // the last unowned node emitted == the open cluster\n while (out.length < nodes.length) {\n const ready = nodes.filter(\n (n) => !done.has(refKey(n)) && indeg.get(refKey(n)) === 0,\n );\n if (ready.length === 0)\n throw new Error(\n `dependency cycle among: ${nodes\n .filter((n) => !done.has(refKey(n)))\n .map(refKey)\n .join(\", \")}`,\n );\n ready.sort((a, b) => {\n const ao = a.owner && refKey(a.owner) === group ? 0 : 1; // prefer the open cluster\n const bo = b.owner && refKey(b.owner) === group ? 0 : 1;\n return (\n ao - bo ||\n ordinalOf(a.kind) - ordinalOf(b.kind) ||\n refKey(a).localeCompare(refKey(b))\n );\n });\n const next = ready[0];\n out.push(next);\n done.add(refKey(next));\n if (!next.owner) group = refKey(next); // a top-level object opens a new cluster\n for (const dep of dependents.get(refKey(next)) ?? [])\n indeg.set(dep, (indeg.get(dep) ?? 1) - 1);\n }\n return out;\n}\n\n// --- lowering + snapshot ------------------------------------------------------------------------\n\n/**\n * Author -> portable: lower each definable through its kind's engine (skipping unregistered kinds).\n * The single place authoring becomes portable; everything downstream (diff/emit/snapshot) is portable.\n */\nexport function lowerSchema(\n registry: KindRegistry,\n defs: Definable[],\n): PortableObject[] {\n const out: PortableObject[] = [];\n for (const d of defs) {\n const engine = registry.engine(d.kind);\n if (engine) out.push(engine.lower(d));\n }\n return out;\n}\n\n/**\n * The registry SNAPSHOT — portable objects grouped by kind. The open, generic replacement for\n * `PortableDb`'s fixed slots; serializes as plain JSON (it is plain data). Pre-launch: the format is\n * free to change, no version migration.\n */\nexport interface KindSnapshot {\n kinds: Record<string, PortableObject[]>;\n}\n\n/**\n * Group a flat portable schema into a snapshot (by kind). Pass `registry` to DROP kinds/objects\n * marked {@link KindEngine.excludeFromMigrations} (e.g. SurrealDB key-bearing access) so unmanaged,\n * secret-bearing objects never enter a snapshot / migration. Omit it to snapshot every object\n * unchanged.\n */\nexport function snapshotKinds(\n schema: PortableObject[],\n registry?: KindRegistry,\n): KindSnapshot {\n const kinds: Record<string, PortableObject[]> = {};\n for (const o of schema) {\n if (registry?.isExcludedFromMigrations(o)) continue;\n const bucket = kinds[o.kind] ?? [];\n bucket.push(o);\n kinds[o.kind] = bucket;\n }\n return { kinds };\n}\n\n/** Flatten a snapshot back into a portable schema (the inverse of {@link snapshotKinds}). */\nexport function snapshotObjects(snap: KindSnapshot): PortableObject[] {\n return Object.values(snap.kinds).flat();\n}\n\n// --- diff / plan --------------------------------------------------------------------------------\n\n/** One classified object change, carrying its ordering metadata + the portable sides for DDL. */\ninterface Change extends OrderNode {\n readonly op: \"add\" | \"change\" | \"remove\";\n readonly prev?: PortableObject;\n readonly next?: PortableObject;\n}\n\n/** An up/down DDL program (each a list of statements). */\nexport interface KindPlan {\n up: string[];\n down: string[];\n}\n\n/** The canonical change-detection key for an object — the kind's `canonical`, else its emitted DDL. */\nconst canonicalOf = (engine: KindEngine, p: PortableObject): string =>\n engine.canonical?.(p) ?? engine.emit(p).join(\"\\n\");\n\nconst orderNodeOf = (\n engine: KindEngine,\n portable: PortableObject,\n): OrderNode => ({\n kind: portable.kind,\n name: portable.name,\n deps: engine.deps?.(portable) ?? [],\n owner: engine.owner?.(portable),\n});\n\n/** Display identity: `kind:owner:name` (owner blank for a top-level object) + the display owner. */\nconst itemKey = (n: OrderNode) => `${n.kind}:${n.owner?.name ?? \"\"}:${n.name}`;\nconst itemTable = (n: OrderNode) => n.owner?.name ?? n.name;\n\nconst byKey = (schema: PortableObject[]) =>\n new Map(schema.map((o) => [refKey(o), o]));\n\n/**\n * Classify both sides into ordered add/change/remove sets — the shared core of plan + diff. A `change`\n * is two objects of the same key whose emitted DDL differs (same test as the fixed-slot engine). Each\n * class is topologically ordered parent-first; the caller reverses one class for drops/inversion.\n */\nfunction orderedChanges(\n registry: KindRegistry,\n prev: PortableObject[],\n next: PortableObject[],\n): { nonRemoves: Change[]; removes: Change[] } {\n const prevByKey = byKey(prev);\n const nextByKey = byKey(next);\n const changes: Change[] = [];\n for (const k of new Set([...prevByKey.keys(), ...nextByKey.keys()])) {\n const p = prevByKey.get(k);\n const n = nextByKey.get(k);\n const portable = n ?? p;\n if (!portable) continue;\n // Migration-unmanaged kinds/objects (e.g. key-bearing access) never diff — they're reconciled\n // out-of-band by driver commands, so they must not appear in gen/migrate/diff-live output.\n // Central choke point (a per-object predicate is fed the object that would be emitted).\n if (registry.isExcludedFromMigrations(portable)) continue;\n const engine = registry.engine(portable.kind);\n if (!engine) continue;\n const node = orderNodeOf(engine, portable);\n if (p && !n) changes.push({ op: \"remove\", prev: p, ...node });\n else if (!p && n) changes.push({ op: \"add\", next: n, ...node });\n else if (p && n && canonicalOf(engine, p) !== canonicalOf(engine, n))\n changes.push({ op: \"change\", prev: p, next: n, ...node });\n }\n const ord = (kind: string) => registry.ordinal(kind);\n return {\n nonRemoves: orderObjects(\n changes.filter((c) => c.op !== \"remove\"),\n ord,\n ),\n removes: orderObjects(\n changes.filter((c) => c.op === \"remove\"),\n ord,\n ),\n };\n}\n\nconst overwriteUp = (\n engine: KindEngine,\n a: PortableObject,\n b: PortableObject,\n): string[] =>\n engine.overwrite?.(a, b) ?? [...engine.remove(a), ...engine.emit(b)];\n\n/**\n * Diff two portable schema states into an executable up/down program, generically over the registry.\n *\n * `up` runs creates/changes parent-first (the dependency graph) then drops child-first; `down` is the\n * mirror: recreate drops parent-first, then undo creates/changes child-first. We invert PER OBJECT (not\n * by reversing the flat DDL list) so a kind's multi-line block — a table emitted with its fields —\n * keeps its internal order in both directions.\n */\nexport function planKinds(\n registry: KindRegistry,\n prev: PortableObject[],\n next: PortableObject[],\n): KindPlan {\n const { nonRemoves, removes } = orderedChanges(registry, prev, next);\n const up: string[] = [];\n const down: string[] = [];\n for (const c of nonRemoves) {\n const e = registry.engine(c.kind);\n if (!e) continue;\n if (c.op === \"add\" && c.next) up.push(...e.emit(c.next));\n else if (c.op === \"change\" && c.prev && c.next)\n up.push(...overwriteUp(e, c.prev, c.next));\n }\n for (const c of [...removes].reverse()) {\n const e = registry.engine(c.kind); // drops child-first\n if (e && c.prev) up.push(...e.remove(c.prev));\n }\n for (const c of removes) {\n const e = registry.engine(c.kind); // recreate dropped objects parent-first\n if (e && c.prev) down.push(...e.emit(c.prev));\n }\n for (const c of [...nonRemoves].reverse()) {\n const e = registry.engine(c.kind); // undo creates/changes child-first\n if (!e) continue;\n if (c.op === \"add\" && c.next) down.push(...e.remove(c.next));\n else if (c.op === \"change\" && c.prev && c.next)\n down.push(...overwriteUp(e, c.next, c.prev));\n }\n return { up, down };\n}\n\n/**\n * Display items for a change set, in up order (creates/changes parent-first, drops child-first). A kind\n * with `displayItems` decomposes into FINE-grained sub-items (per-field, each carrying its `table` so\n * the display groups them under it); otherwise it falls back to ONE whole-object item.\n */\nfunction diffItems(\n registry: KindRegistry,\n nonRemoves: Change[],\n removes: Change[],\n): DiffItem[] {\n const items: DiffItem[] = [];\n const push = (c: Change) => {\n const e = registry.engine(c.kind);\n if (!e) return;\n if (e.displayItems) {\n items.push(...e.displayItems(c.prev, c.next));\n return;\n }\n const base = { key: itemKey(c), table: itemTable(c), kind: c.kind };\n if (c.op === \"add\" && c.next)\n items.push({ ...base, op: \"add\", ddl: e.emit(c.next).join(\"\\n\") });\n else if (c.op === \"remove\" && c.prev)\n items.push({\n ...base,\n op: \"remove\",\n ddl: e.remove(c.prev).join(\"\\n\"),\n old: e.emit(c.prev).join(\"\\n\"),\n });\n else if (c.op === \"change\" && c.prev && c.next)\n items.push({\n ...base,\n op: \"change\",\n before: e.emit(c.prev).join(\"\\n\"),\n after: e.emit(c.next).join(\"\\n\"),\n });\n };\n for (const c of nonRemoves) push(c);\n for (const c of [...removes].reverse()) push(c);\n return items;\n}\n\n/**\n * The full {@link Diff} the CLI + migration model consume — up/down DDL + per-object display items +\n * the whole desired schema (`full`, for `--full`). This is what a driver's `Driver.diff` returns once\n * its kinds are on the registry (the generic counterpart of the fixed-slot `buildDiff`). Source-file\n * linkage on the items is attached by the caller (the snapshot's `files` map), so `file` is left unset.\n */\nexport function buildKindDiff(\n registry: KindRegistry,\n prev: PortableObject[],\n next: PortableObject[],\n): Diff {\n const { nonRemoves, removes } = orderedChanges(registry, prev, next);\n const { up, down } = planKinds(registry, prev, next);\n // `full` mirrors the items' granularity: a kind with `displayItems` projects its object as per-\n // sub-object adds (displayItems(undefined, portable)); otherwise one whole-object entry.\n const full = orderedSchema(registry, next).flatMap(\n ({ engine, portable, node }) => {\n if (engine.displayItems)\n return engine.displayItems(undefined, portable).map((it) => ({\n key: it.key,\n table: it.table,\n ddl: it.op === \"add\" ? it.ddl : \"\",\n }));\n return [\n {\n key: itemKey(node),\n table: itemTable(node),\n ddl: engine.emit(portable).join(\"\\n\"),\n },\n ];\n },\n );\n return { up, down, items: diffItems(registry, nonRemoves, removes), full };\n}\n\n/** Lower-already portable schema, topologically ordered, paired with each object's engine + node. */\nfunction orderedSchema(\n registry: KindRegistry,\n schema: PortableObject[],\n): { engine: KindEngine; portable: PortableObject; node: OrderNode }[] {\n const items = schema.flatMap((portable) => {\n const engine = registry.engine(portable.kind);\n return engine\n ? [{ engine, portable, node: orderNodeOf(engine, portable) }]\n : [];\n });\n const pos = new Map(\n orderObjects(\n items.map((i) => i.node),\n (k) => registry.ordinal(k),\n ).map((n, i) => [itemKey(n), i]),\n );\n return items.sort(\n (a, b) => (pos.get(itemKey(a.node)) ?? 0) - (pos.get(itemKey(b.node)) ?? 0),\n );\n}\n\n/**\n * Fresh-apply DDL for a portable schema: every object created, ordered across kinds by the graph.\n * (The `up` of a diff from an empty state.) Lower authoring first via {@link lowerSchema}.\n */\nexport function emitKinds(\n registry: KindRegistry,\n schema: PortableObject[],\n): string[] {\n // Skip migration-unmanaged kinds/objects (e.g. key-bearing access) — they're applied out-of-band\n // by driver commands.\n const managed = schema.filter(\n (o) => !registry.isExcludedFromMigrations(o),\n );\n return orderedSchema(registry, managed).flatMap(({ engine, portable }) =>\n engine.emit(portable),\n );\n}\n\n/**\n * Reverse direction, fanned out across kinds: introspect every introspectable kind off one live\n * connection and flatten into portable objects. The RESOLUTION of \"per-kind vs one driver read\":\n * the contract is per-kind ({@link KindEngine.introspect}), but a driver backs all of its kinds with\n * ONE shared (memoized) read of `conn` and slices out each kind's objects — so the fan-out here costs\n * a single round-trip, not N. A kind without `introspect` contributes nothing (not introspectable).\n */\nexport async function introspectKinds(\n registry: KindRegistry,\n conn: unknown,\n): Promise<PortableObject[]> {\n const out: PortableObject[] = [];\n for (const [kind, engine] of registry.entries()) {\n if (!engine.introspect) continue;\n // Skip STATICALLY migration-unmanaged kinds so the live side never phantom-diffs against a\n // schema that (by design) excludes them. A per-object predicate can't be evaluated without the\n // object — introspection still runs, and the diff choke point filters by object afterwards.\n if (registry.skipsIntrospection(kind)) continue;\n out.push(...(await engine.introspect(conn)));\n }\n return out;\n}\n","// The KIND REGISTRY — core-v2's generic, open replacement for the fixed object-kind slots.\n//\n// Today `PortableDb` hard-codes the object kinds a schema may contain (`tables`/`functions`/\n// `accesses`/`natives`) and the Driver's whole-DB methods switch on those slots. The kind registry\n// turns the slots into a REGISTRY a driver populates: each driver registers KINDS, and every kind\n// brings (a) its OWN authoring builder — any shape/chain it likes, fully typed — and (b) its engine\n// behavior (`lower`/`emit`/`remove`/`overwrite`/`deps`/`owner`/`introspect`) over THAT kind's objects.\n// Core orchestrates generically over the registry (see ./plan.ts) and never names a kind.\n//\n// What stays in core is the field/type VOCABULARY (`SFieldBase`, the Zod-drop-in `s.*`, `PortableType`,\n// codecs) — the substrate every kind builds on. Fields/types are NOT a kind: a table HAS fields, a\n// function's args ARE fields, an index REFERENCES fields. See docs/kind-registry.md.\n//\n// The registry is PER-DRIVER, not a module global: a driver builds one `KindRegistry` and registers\n// its kinds into it.\n\n/**\n * An authored definable, tagged with the KIND that owns it. Core dispatches on `kind` alone — every\n * other field is the kind's own business, handed straight to {@link KindEngine.lower}. This is the\n * neutral upper bound for a kind's authoring-object type (a driver's concrete `TableDef`/`FnDef` is a\n * structural subtype).\n */\nexport interface Definable {\n readonly kind: string;\n readonly name: string;\n}\n\n/**\n * A kind's PORTABLE object — the dialect-independent data shape core stores + diffs. A kind chooses\n * how structured this is: a table's portable form carries fields/indexes (so core can field-level\n * diff it); an opaque kind (function/access) carries a neutral identity + a `native` payload it\n * round-trips. Either way it is tagged with `kind`/`name` for cross-kind dispatch + ordering.\n */\nexport interface PortableObject {\n readonly kind: string;\n readonly name: string;\n}\n\n/** A reference to another object in the schema graph — the unit of cross-kind dependency ordering. */\nexport interface Ref {\n readonly kind: string;\n readonly name: string;\n}\n\n// `DiffItem` is a type-only import (erased at compile) — the display contract the `displayItems` hook\n// produces; no runtime cli->kind coupling, same arrangement as ./plan.ts.\nimport type { DiffItem } from \"../cli-kit/diff\";\n\n/**\n * What core needs to orchestrate ONE kind generically — it never inspects the specifics. The\n * change-vocabulary (`emit`/`remove`/`overwrite`) mirrors the Driver contract's, so a kind's behavior\n * is parity-checkable against the fixed-slot engine. `A` is the kind's authoring object, `P` its\n * portable object; both are opaque to core beyond the {@link Definable}/{@link PortableObject} bounds.\n */\nexport interface KindEngine<\n A extends Definable = Definable,\n P extends PortableObject = PortableObject,\n> {\n /** Authoring object -> this kind's portable object (normalized; both lowerings must converge here). */\n lower(authored: A): P;\n /** CREATE DDL for one portable object (a fresh apply / migration `up` for an added object). */\n emit(portable: P): string[];\n /** DROP DDL for one portable object (`up` for a removed object, `down` for an added one). */\n remove(portable: P): string[];\n /**\n * In-place CHANGE DDL taking `prev` to `next` (the dialect's ALTER/OVERWRITE). The spine calls\n * `overwrite(next, prev)` to roll a change back. A kind with no in-place form recreates: implement\n * as `[...remove(prev), ...emit(next)]`. Default (omitted) = recreate via emit(next).\n */\n overwrite?(prev: P, next: P): string[];\n /**\n * The CANONICAL change-detection key: the spine treats prev/next of the same object as a CHANGE iff\n * their `canonical` differs. Default (omitted) = `emit(portable).join(\"\\n\")` — so a kind whose `emit`\n * is already its canonical form needs nothing. Override when `emit` is FAITHFUL but some clauses must\n * be EXCLUDED from equality — because the DB rewrites them on read (a cast suffix like `'x'::text`,\n * added parens like `(a>0)`) or never introspects them (a COMMENT, an index) — so a faithful `emit` would phantom-diff a\n * freshly-applied schema against `introspect`. Return `emit` MINUS those clauses: they stay create-time\n * faithful in `emit`, but don't count as changes. `canonical(a) === canonical(b)` MUST mean \"no\n * migration needed\". Affects ONLY classification; `emit`/`overwrite` (the DDL) are unaffected.\n */\n canonical?(portable: P): string;\n /**\n * Fine-grained DISPLAY items for a change of this object — so `better-schemic diff` shows per-SUB-OBJECT\n * changes (a table decomposes into per-FIELD items: `field:user:name` changed), each carrying its\n * owner `table` so the display GROUPS them hierarchically under it, instead of one coarse whole-object\n * item. Called `(prev, next)`: a change diffs the two; `(undefined, next)` lists the object's\n * sub-items as adds — the `--full` projection core uses for the full desired-state view. Default\n * (omitted) = ONE whole-object item. DISPLAY ONLY — never affects up/down DDL (that is\n * `emit`/`overwrite`); a structured driver reuses the per-field diff it already computes. Leave\n * `DiffItem.file` unset (the caller attaches source linkage).\n */\n displayItems?(prev: P | undefined, next: P | undefined): DiffItem[];\n /**\n * Objects this one must be emitted AFTER — the cross-kind dependency edges (a field/index -> its\n * table; an edge table -> its in/out tables; an event -> its table + any function it calls). Drives\n * the topological sort in ./plan.ts. Omitted = no dependencies.\n */\n deps?(portable: P): Ref[];\n /**\n * The owning object to CLUSTER next to in the emitted order (an index's table) — readability only,\n * never overrides {@link deps}. Omitted = a top-level object.\n */\n owner?(portable: P): Ref | undefined;\n /**\n * The STRUCTURAL container this object is nested within (an index's/field's table) — used for\n * ADDRESSING and grouping (the CLI's dotted `parent.child`, e.g. `sc index info user.email_idx`) and\n * distinct from {@link owner}, which is a DISPLAY choice (diff clustering). A kind may declare `parent`\n * (it IS nested) while declining `owner` (it doesn't want per-parent diff clustering), or vice versa.\n * The CLI resolves a nesting address as `parent ?? owner` (so a kind that only sets `owner` still\n * addresses dotted). Omitted (and no `owner`) = a top-level object, addressed by its bare name.\n */\n parent?(portable: P): Ref | undefined;\n /**\n * Live connection -> all portable objects of THIS kind (the reverse direction). Introspection is\n * often one `INFO`/catalog read yielding every kind at once; a driver backs all of its kinds'\n * `introspect` with one shared (memoized) read and slices out this kind's objects. Omitted -> this\n * kind isn't introspectable (diff/emit still work from authored state).\n */\n introspect?(conn: unknown): Promise<P[]>;\n /**\n * How this kind is PRESENTED — its human labels and the folder its objects render into. All optional\n * with sensible defaults off the kind name (see {@link KindRegistry.display}), so a kind only declares\n * what the defaults get wrong (e.g. `plural: \"Indexes\"`, or `folder: \"access\"`). DISPLAY ONLY.\n */\n display?: KindDisplay;\n /**\n * UNMANAGED by the migration pipeline: when `true`, objects of this kind are EXCLUDED from\n * snapshot / diff / gen AND from the introspect-compare — so they never enter a migration file nor\n * phantom-diff. For a kind whose lifecycle doesn't fit committed migrations: e.g. SurrealDB\n * `DEFINE ACCESS`, which carries a secret the DB redacts on introspection (can't round-trip) and\n * rotates on its own cadence. Such a kind is managed OUT-OF-BAND via the driver's own commands\n * (`sc <kind> …`). `emit`/`lower` still work (a driver command may use them); only the automatic\n * migration lifecycle skips it. Omitted/false = a normal, migration-managed kind.\n *\n * A PREDICATE form (`(portable) => boolean`) decides PER OBJECT — for a kind whose objects are\n * managed only when they round-trip cleanly (e.g. SurrealDB access: key-free defs are manageable,\n * key-bearing/redacted ones are not). The object is REQUIRED by\n * {@link KindRegistry.isExcludedFromMigrations}; only a boolean `true` also skips introspection\n * ({@link KindRegistry.skipsIntrospection}) — a predicate can't be decided there.\n */\n excludeFromMigrations?: boolean | ((portable: PortableObject) => boolean);\n}\n\n/** Per-kind presentation metadata (labels + output folder). All optional; core fills defaults. */\nexport interface KindDisplay {\n /** Title-Case singular, e.g. `\"Table\"`, `\"Field\"`. Default: the kind name, capitalized. */\n label?: string;\n /** Title-Case plural, e.g. `\"Tables\"`, `\"Indexes\"`. Default: the English plural of `label`. */\n plural?: string;\n /** The directory this kind's objects render into. Default: the lowercase slug of `plural`. */\n folder?: string;\n}\n\n/** A kind's resolved presentation — every field filled (the shape {@link KindRegistry.display} returns). */\nexport type ResolvedDisplay = Required<KindDisplay>;\n\n/** A kind's full spec: its `name`, its `build` (the driver's authoring entry), and its engine. */\nexport type KindSpec<\n Build extends (...args: never[]) => unknown,\n A extends Definable,\n P extends PortableObject,\n> = { name: string; build: Build } & KindEngine<A, P>;\n\n/** `\"table\"` -> `\"Table\"`. */\nfunction capitalize(s: string): string {\n return s ? s[0].toUpperCase() + s.slice(1) : s;\n}\n\n/** A plain English pluralizer for kind labels: `Index` -> `Indexes`, `Policy` -> `Policies`. */\nfunction pluralize(s: string): string {\n if (/[^aeiou]y$/i.test(s)) return `${s.slice(0, -1)}ies`;\n if (/(s|x|z|ch|sh)$/i.test(s)) return `${s}es`;\n return `${s}s`;\n}\n\n/** `\"Tables\"` -> `\"tables\"`; collapses non-alphanumerics to single dashes (a filesystem-safe folder). */\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/**\n * A driver's set of registered kinds + the generic behavior the spine reads off them. Built once per\n * driver; `define` registers a kind and returns the driver's OWN `build` function UNCHANGED — so the\n * driver writes `export const defineTable = registry.define({ name: \"table\", build, ...engine })` and\n * keeps full type-safety + DX (TS preserves a generic `build`'s parameters across the passthrough).\n */\nexport class KindRegistry {\n // Heterogeneous kinds erase at the engine seam (engine ops are structural); the AUTHORING side\n // keeps full types via `define`'s `Build` passthrough.\n // biome-ignore lint/suspicious/noExplicitAny: the engine seam is intentionally type-erased.\n private readonly kinds = new Map<string, KindEngine<any, any>>();\n\n /**\n * Register a KIND. `build` is the driver's own authoring entry — ANY shape/chain — and its type\n * flows through unchanged (type-safety + DX are the driver's to design). The engine fns give core\n * the generic behavior. Registration ORDER is the kind's ordinal (the stable tie-break among\n * independent objects in {@link orderObjects}), so register coarse-to-fine (table before index).\n */\n define<\n Build extends (...args: never[]) => unknown,\n A extends Definable,\n P extends PortableObject,\n >(spec: KindSpec<Build, A, P>): Build {\n this.kinds.set(spec.name, spec);\n return spec.build;\n }\n\n /** The engine for `kind`, or undefined if no such kind is registered. */\n // biome-ignore lint/suspicious/noExplicitAny: the engine erases at this seam (see `kinds`).\n engine(kind: string): KindEngine<any, any> | undefined {\n return this.kinds.get(kind);\n }\n\n /**\n * Is this PORTABLE OBJECT UNMANAGED by the migration pipeline (its engine set\n * {@link KindEngine.excludeFromMigrations})? The snapshot/diff/emit spine skips such objects — see\n * the flag's docs. The OBJECT is required: a per-object predicate can't be evaluated without it, and\n * defaulting to \"managed\" would let a key-bearing object slip into a migration. An unregistered kind\n * is treated as managed (false), so a stray object never gets silently dropped by a typo.\n */\n isExcludedFromMigrations(portable: PortableObject): boolean {\n const flag = this.kinds.get(portable.kind)?.excludeFromMigrations;\n return typeof flag === \"function\" ? flag(portable) : flag === true;\n }\n\n /**\n * Whether `kind` is STATICALLY excluded from migrations (`excludeFromMigrations === true`), so even\n * introspection skips it. A per-object PREDICATE can't be decided without an object: introspection\n * runs for the kind, and the diff/snapshot/emit choke points filter by object afterwards.\n */\n skipsIntrospection(kind: string): boolean {\n return this.kinds.get(kind)?.excludeFromMigrations === true;\n }\n\n /**\n * A kind's resolved presentation — `label`/`plural`/`folder`, with defaults derived from the kind\n * name for whatever the driver left unset. Works for unregistered display sub-kinds too (e.g. the\n * `\"field\"` items a table's `displayItems` emits) — they just get the name-derived defaults.\n */\n display(kind: string): ResolvedDisplay {\n const d = this.kinds.get(kind)?.display ?? {};\n const label = d.label ?? capitalize(kind);\n const plural = d.plural ?? pluralize(label);\n return { label, plural, folder: d.folder ?? slugify(plural) };\n }\n\n /** Registered kind names, in registration order (== ordinal order). */\n names(): string[] {\n return [...this.kinds.keys()];\n }\n\n /**\n * A kind's ORDINAL = its registration index. Used ONLY as a tie-break among objects with no\n * dependency relation, so independent objects come out stably layered (readability); it never\n * overrides the dependency graph. An unknown kind sorts last.\n */\n ordinal(kind: string): number {\n const i = this.names().indexOf(kind);\n return i === -1 ? Number.MAX_SAFE_INTEGER : i;\n }\n\n /** [name, engine] pairs in registration order — the spine iterates these. */\n // biome-ignore lint/suspicious/noExplicitAny: the engine erases at this seam (see `kinds`).\n entries(): [string, KindEngine<any, any>][] {\n return [...this.kinds.entries()];\n }\n}\n","// The DRIVER interface — the dialect seam (see docs/MULTI-DB-SPIKE.md).\n//\n// Everything dialect-specific lives behind a `Driver`: lowering authoring to the Struct-IR, emitting\n// DDL, introspecting a live DB, normalizing to a canonical form, and executing. Everything ABOVE the\n// driver (the diff algorithm, the magicast TS-merge, the migration model, the CLI shell) stays\n// dialect-free and calls these ops.\n//\n// The connection type is a driver-private parameter `Conn`: the orchestration treats it opaquely and\n// only ever hands it back to the SAME driver. So the Surreal driver is `Driver<Surreal>`, and core\n// never sees the concrete type. The AUTHORING\n// types (`Tbl`/`Def`) are driver-private the same way — opaque to core beyond the neutral\n// `Authored`/`AuthoredDef` bounds — so the neutral engine never names a dialect's concrete builder\n// (`TableDef`/`StandaloneDef`).\n\nimport type { ResolvedConfig } from \"../cli-kit/config\";\nimport type { Diff } from \"../cli-kit/diff\";\nimport type { Filter } from \"../cli-kit/filter\";\nimport type { PullPlan } from \"../cli-kit/merge\";\nimport type { Definable, KindRegistry, PortableObject } from \"../kind\";\nimport type { SecretProvider, SecretRef } from \"../secrets\";\n\n/**\n * The dialect-NEUTRAL authoring contract — the only structure the orchestration reads off an\n * authored object (everything else is opaque and handed straight to {@link Driver.explode}). A table\n * contributes just its `name`; this is the upper bound for a driver's table-authoring type. The\n * Surreal `TableDef` is a structural subtype, as is any future dialect's table builder.\n */\nexport interface Authored {\n readonly name: string;\n}\n\n/**\n * The neutral contract for a standalone (non-table) authored object — an event/function/access. It\n * adds a `kind` discriminant and, for objects owned by a table (e.g. an event), the owner `table`\n * name (so the snapshot can file-link a child object under its parent). The Surreal `StandaloneDef`\n * union is a structural subtype.\n */\nexport interface AuthoredDef extends Authored {\n readonly kind: string;\n readonly table?: string;\n}\n\n/**\n * A single emitted DDL statement, structured: object identity (`kind`/`name`/`table`) + the dialect\n * `ddl` string, plus an optional clause map (each value an `ALTER … <set>` form) for dialects that\n * diff clause-level. `kind` is a dialect-defined string the orchestration treats opaquely — the\n * SurrealDB `DefineStatement` (with its fixed kind union) is a structural subtype of this.\n */\nexport interface Statement {\n kind: string;\n name: string;\n table?: string;\n ddl: string;\n clauses?: Record<string, string>;\n /**\n * Apply-time secret bindings for this statement's `$param` placeholders — `param` name ->\n * a write-only {@link SecretRef}. Collected into {@link Diff.bindings}; the value never lives here\n * (resolved at apply through a `SecretProvider`). See {@link Diff.bindings}.\n */\n bindings?: Record<string, SecretRef>;\n}\n\n/** Options for {@link Driver.emit} — mirrors the existing `DefineOptions` (e.g. IF NOT EXISTS). */\nexport interface EmitOptions {\n ifNotExists?: boolean;\n overwrite?: boolean;\n}\n\n/** Options for {@link Driver.apply}. */\nexport interface ApplyOptions {\n /**\n * Run the whole batch atomically. `migrate` wraps up/down + `_migrations` bookkeeping in one\n * transaction; a driver that can't MUST surface that (the migration model degrades to best-effort).\n */\n transactional?: boolean;\n}\n\n/** Per-connection overrides (url/namespace/credentials) — superset across dialects. */\nexport interface ConnectionOverrides {\n url?: string;\n namespace?: string;\n database?: string;\n username?: string;\n password?: string;\n authLevel?: string;\n}\n\n/** The direction a migration is applied in. */\nexport type MigrationDirection = \"up\" | \"down\";\n\n/** A migration's bookkeeping identity, recorded in the migrations-tracking table. */\nexport interface MigrationRecord {\n tag: string;\n file: string;\n /** sha of the migration file at apply time (drift detection). */\n checksum: string;\n}\n\n/**\n * The apply-time, dialect-specific half of the migration runner. The orchestration (which\n * migrations are pending, ordering, the lock-then-loop) stays driver-neutral in cli/migrate.ts;\n * this capability owns the SQL: the tracking table, the applied-records, the advisory lock, and the\n * atomic apply+record. A driver WITHOUT it can't run migrations (diff/gen still work). `Conn` is the\n * driver's own connection type.\n */\nexport interface MigrationStore<Conn = unknown> {\n /** This dialect's migration-file extension, e.g. `\".surql\"` (SurrealDB). */\n readonly extension: string;\n /** Render a diff as this dialect's migration-file body (e.g. SurrealQL `IF $direction` up/down). */\n render(tag: string, diff: Diff): string;\n /** Ensure the migrations-tracking table exists. */\n ensure(conn: Conn, table: string): Promise<void>;\n /** Applied migrations: tag -> checksum recorded at apply time. */\n applied(conn: Conn, table: string): Promise<Map<string, string>>;\n /**\n * Apply one migration's `up`/`down` PROGRAM plus its bookkeeping write atomically: on `up` record\n * the migration, on `down` erase it — so the record is written iff the DDL actually applied.\n */\n apply(\n conn: Conn,\n table: string,\n m: {\n content: string;\n direction: MigrationDirection;\n record: MigrationRecord;\n },\n ): Promise<void>;\n /** Record a migration as applied WITHOUT running its DDL (baseline of an existing DB). */\n record(conn: Conn, table: string, record: MigrationRecord): Promise<void>;\n /** Drop all applied records (baseline-squash reconcile). */\n clear(conn: Conn, table: string): Promise<void>;\n /** Take an advisory lock so two runs can't race — throws if already held. */\n lock(conn: Conn, table: string): Promise<void>;\n /** Release the advisory lock (idempotent). */\n unlock(conn: Conn, table: string): Promise<void>;\n}\n\n/**\n * An OPTIONAL throwaway-instance capability for round-trip canonicalization and `sz check`'s\n * migration replay. Absent -> `check`/replay-verification is degraded/unavailable (diff/apply still\n * work, since a kind's `lower`/`introspectAll` already canonicalize).\n */\nexport interface ShadowCapability<Conn> {\n /** Apply `ddl` to a fresh scratch DB, introspect it back to portable objects, then drop it. */\n roundTrip(\n conn: Conn,\n config: ResolvedConfig,\n ddl: string,\n ): Promise<PortableObject[]>;\n /** Spin up a fully-isolated ephemeral instance (for migration replay). Caller must `stop()`. */\n ephemeral?(): Promise<{ conn: Conn; stop: () => Promise<void> }>;\n}\n\n/**\n * User-defined DB functions — the `.call` side of the query layer's (B) surface (DB functions as code).\n * `invoke` calls a defined function by name with already-encoded args and returns the function's RAW\n * result (the driver extracts it from its own response shape — surreal `RETURN fn::name($a)` yields the\n * value; a row-returning call yields a row set). The caller decodes that raw value through the\n * function's `.returns(R)` schema (the driver-side call surface decodes it). A defined function still\n * emits/migrates via the schema engine regardless; this capability only adds INVOCATION.\n */\nexport interface CallableFunctions<Conn = unknown> {\n invoke(\n conn: Conn,\n name: string,\n args: Record<string, unknown>,\n ): Promise<unknown>;\n}\n\n// --- driver-contributed CLI commands -----------------------------------------------------------\n// A driver may contribute dialect-specific commands invoked as `sc <kind> <verb> [args]` (e.g. surreal\n// `sc access rotate <name>`). CORE provides only the general\n// mechanism: it discovers `driver.commands`, registers each, parses argv against `args`, resolves the\n// connection, and dispatches to `run` with a {@link CommandContext}. The DRIVER owns the dialect logic\n// and the meaning of each kind/verb/arg — core never names one. Depth is fixed at kind/verb.\n\n/** A declared argument for a {@link DriverCommand} — used for parsing + `--help`. */\nexport interface CommandArgs {\n /**\n * Positional args, in order. Core collects ALL positional tokens into a list (incl. raw `key=value`)\n * and the driver interprets + validates arity; mark the last `variadic` for \"one or more\".\n */\n positionals?: readonly {\n name: string;\n required?: boolean;\n variadic?: boolean;\n help?: string;\n }[];\n /** Named flags. `value: true` takes a value (`--user U`); otherwise it's a boolean (`--dry-run`). */\n flags?: readonly {\n name: string;\n value?: boolean;\n required?: boolean;\n help?: string;\n }[];\n}\n\n/** argv parsed against a command's {@link CommandArgs}: positional tokens (driver-interpreted) + flags. */\nexport interface ParsedCommandArgs {\n /** Every positional token in order (incl. raw `key=value`); the driver interprets them. */\n positionals: string[];\n /** Flags: a value-flag -> its string, a boolean flag -> `true`, an absent flag -> `undefined`. */\n flags: Record<string, string | boolean | undefined>;\n}\n\n/** Output + input helpers core hands a command (so a driver never touches stdio directly). */\nexport interface CommandIo {\n ok(message: string): void;\n fail(message: string): void;\n info(message: string): void;\n /** Prompt for a line of input (`hidden` masks it) — for a sensitive value not passed inline (no shell-history leak). */\n prompt(question: string, opts?: { hidden?: boolean }): Promise<string>;\n}\n\n/** The context core hands a {@link DriverCommand.run}: a connected db + the resolved config + io + secrets. */\nexport interface CommandContext<Conn = unknown> {\n conn: Conn;\n config: ResolvedConfig;\n io: CommandIo;\n /** The configured secret provider (default reads `process.env`) for resolving {@link SecretRef}s. */\n secrets: SecretProvider;\n}\n\n/**\n * A driver-contributed CLI command — `sc <kind> <verb> [args]`. Core dispatches; the driver owns the\n * dialect logic in {@link run}. The driver validates positional arity itself (core only collects them).\n */\nexport interface DriverCommand<Conn = unknown> {\n /** Kind namespace — `sc <kind> …` (e.g. `\"access\"`, `\"table\"`). */\n kind: string;\n /** Verb under the kind — `sc <kind> <verb>` (e.g. `\"rotate\"`, `\"check\"`, `\"find\"`). */\n verb: string;\n /** One-line help shown in the command listing. */\n summary: string;\n /** Declared args for parsing + `--help`. */\n args?: CommandArgs;\n run(ctx: CommandContext<Conn>, args: ParsedCommandArgs): Promise<void>;\n}\n\n/**\n * A database dialect, expressed as a SET OF KINDS (core-v2). The driver registers its object kinds on\n * `registry`; core orchestrates schema ops GENERICALLY over it (`lowerSchema`/`buildKindDiff`/\n * `emitKinds`/`orderObjects`) — it never names a kind. The driver owns only what isn't generic: the\n * authoring -> kinded `explode`, a single-read `introspectAll`, the connection lifecycle, and the\n * dialect-specific command capabilities. The field/type substrate (`PortableType`/`s.*`) stays core.\n * See docs/kind-registry-flip-plan.md.\n */\nexport interface Driver<\n Conn = unknown,\n Tbl extends Authored = Authored,\n Def extends AuthoredDef = AuthoredDef,\n> {\n readonly name: string;\n\n // --- kind registry (the schema engine) -----------------------------------------------------\n /** This driver's registered KINDS. Core runs lower/diff/emit/order generically over it. */\n readonly registry: KindRegistry;\n /**\n * Authoring (loaded `defineTable`/standalone defs) -> kinded {@link Definable}s. The driver-side\n * fan-out: one inline-authored table explodes into `[table, ...index, ...event/constraint]`, each\n * tagged with its `kind`. Core then lowers via `lowerSchema(registry, explode(...))` — so\n * `KindEngine.lower` stays 1:1 and the contract needs no explode hook.\n */\n explode(tables: Tbl[], defs: Def[]): Definable[];\n /**\n * Live connection -> ALL portable objects, fanned across kinds from ONE read (INFO STRUCTURE /\n * the system catalog). Must canonicalize IDENTICALLY to lowering (a clean apply round-trips to a zero diff)\n * and be COMPLETE (return every diffable kind, else presence-phantom-diffs). `exclude` skips tables\n * by name.\n */\n introspectAll(conn: Conn, exclude?: Set<string>): Promise<PortableObject[]>;\n\n // --- execution -----------------------------------------------------------------------------\n connect(config: ResolvedConfig, over?: ConnectionOverrides): Promise<Conn>;\n apply(conn: Conn, statements: string[], opts?: ApplyOptions): Promise<void>;\n /** Tear down a connection opened by {@link connect} (the orchestration owns the lifecycle). */\n close(conn: Conn): Promise<void>;\n\n // --- optional capabilities -----------------------------------------------------------------\n readonly shadow?: ShadowCapability<Conn>;\n /** Apply-time migration bookkeeping. Absent -> this driver can't run migrations (diff/gen still do). */\n readonly migrations?: MigrationStore<Conn>;\n\n // --- optional COMMAND capabilities ---------------------------------------------------------\n // The dialect-agnostic CLI routes each schema-syncing command through one of these. A driver that\n // omits a capability makes that command unavailable on it — the CLI never hardcodes `if surreal`.\n\n /**\n * Diff the LIVE database against the loaded schema into executable up/down DDL. Owns every\n * dialect-specific normalization and apply-time fixup (Surreal: a shadow-DB round-trip to cancel\n * formatting noise, the redacted-access-key swap, and the implicit-wildcard OVERWRITE re-mark), so\n * the result is safe to apply as-is. Backs `diff --live`, `push`, and the baseline reconcile.\n */\n diffLive?(conn: Conn, config: ResolvedConfig, filter: Filter): Promise<Diff>;\n /** Reduce a live diff (from {@link diffLive}) to the statements `push` applies; `prune: false` keeps removals. */\n syncPlan?(diff: Diff, prune?: boolean): string[];\n /**\n * Dialect-specific CLI commands invoked as `sc <kind> <verb> [args]` — e.g. surreal `access rotate`.\n * Core discovers + dispatches them generically (see {@link DriverCommand});\n * it never names a kind/verb. Absent -> this driver contributes no extra commands.\n */\n readonly commands?: readonly DriverCommand<Conn>[];\n /**\n * Render portable objects to per-file source in THIS dialect's `s.*` syntax, filtered — the codegen\n * behind the offline `diff --ts` and `pull`. Takes `PortableObject[]` (this driver's own portable\n * shape): `diff --ts` renders the SNAPSHOT side (stored portable) and the DESIRED side\n * (`lowerSchema(explode(...))`) at MATCHING fidelity so an in-sync schema yields identical files;\n * `pull` renders the introspected DB. The driver re-derives its structured form from the portable\n * objects (parsing its own DDL where needed — docs/kind-registry-flip-plan.md §6b). `single` (a file\n * key) folds everything into one module; otherwise `fileFor` maps each object to its own file.\n */\n renderSchema?(\n objects: PortableObject[],\n filter: Filter,\n fileFor: (kind: string, name: string) => string,\n single?: string,\n ): Map<string, string>;\n /**\n * The two sides of `diff --ts --live` rendered to per-file source: the live DB (`current`) and the\n * declared schema (`desired`), both normalized through the dialect so an unchanged schema yields\n * identical files.\n */\n diffTsLive?(\n conn: Conn,\n config: ResolvedConfig,\n filter: Filter,\n fileFor: (kind: string, name: string) => string,\n single?: string,\n ): Promise<{ current: Map<string, string>; desired: Map<string, string> }>;\n /**\n * Replay every migration into a throwaway engine and diff the result against the schema (`check`).\n * Owns ephemeral-engine selection + setup; `log` receives progress lines. Needs a {@link shadow}-\n * class capability. An empty diff means the migrations reproduce the schema.\n */\n checkReplay?(\n config: ResolvedConfig,\n over: ConnectionOverrides,\n filter: Filter,\n log: (msg: string) => void,\n ): Promise<Diff>;\n /** Introspect the live DB and plan schema-file codegen (`pull`); writing is the neutral `applyPull`. */\n planPull?(\n conn: Conn,\n config: ResolvedConfig,\n opts: { filter: Filter; keepLocal?: boolean },\n ): Promise<PullPlan>;\n /** A human-readable server identity for `doctor` (e.g. \"SurrealDB 3.1.3\"); throws if unreachable. */\n serverInfo?(conn: Conn): Promise<string>;\n /**\n * Run a raw READ query and return rows — for connection RESOLVERS (a multi-connection resolver's\n * `ctx.connections.<name>.query(...)`) and `seed`. The `sql` is this dialect's query language; the\n * orchestration treats the rows opaquely. Absent -> a resolver can't read from this connection.\n */\n query?<T = unknown>(\n conn: Conn,\n sql: string,\n vars?: Record<string, unknown>,\n ): Promise<T[]>;\n /**\n * User-defined DB functions ((B) of the query layer) — invoke a defined function + decode the result.\n * Absent -> no `.call()` surface on this driver (the function still emits/migrates). See\n * {@link CallableFunctions}.\n */\n readonly callable?: CallableFunctions<Conn>;\n /**\n * The dialect-specific files `better-schemic init` scaffolds, keyed by project-relative path: a\n * connections-only `better-schemic.config.ts` (using this driver's `<driver>Connection` factory), a sample\n * schema module in this dialect's `s.*`, a seed stub, a `.env.example`, … The CLI writes them\n * verbatim (never overwriting) alongside the dialect-neutral migration snapshot it records itself.\n * Absent -> `better-schemic init` can't scaffold a project for this driver.\n */\n initScaffold?(): Record<string, string>;\n /**\n * Scaffold a NEW entity file's contents — the starter `s.*` / `define*` module for an object of\n * `kind` named `name` (e.g. `(\"table\", \"user\")` -> a `defineTable(\"user\", { … })` module in this\n * dialect's authoring). Returns the file text; the CLI writes it under the kind's\n * {@link KindRegistry.display} folder. THROW for a kind this driver can't author (the CLI surfaces\n * the message). Absent -> `better-schemic new` is unavailable for this driver.\n */\n scaffoldEntity?(kind: string, name: string): string;\n}\n\n// --- Registry -----------------------------------------------------------------------------------\n\n// Shared across every loaded copy of `@better-schemic/core` so the registry is process-global — the CLI run\n// via `bunx` resolves its own core, while a driver loaded from the user's project resolves the\n// project's core; without sharing, the driver self-registers in one Map and the CLI reads an empty\n// one. Keyed by a REGISTERED symbol (`Symbol.for`) — same key in every instance, but no string-keyed\n// `globalThis` pollution and namespaced so it can't collide.\nconst REGISTRY_KEY = Symbol.for(\"@better-schemic/core.driverRegistry\");\nconst REGISTRY: Map<string, Driver<unknown>> = ((\n globalThis as Record<symbol, Map<string, Driver<unknown>> | undefined>\n)[REGISTRY_KEY] ??= new Map<string, Driver<unknown>>());\n\n/** Register a driver under its `name` (idempotent; last write wins). */\nexport function registerDriver(driver: Driver<unknown>): void {\n REGISTRY.set(driver.name, driver);\n}\n\n/** Look up a registered driver, or throw with the list of known names. */\nexport function getDriver(name: string): Driver<unknown> {\n const d = REGISTRY.get(name);\n if (!d) {\n const known = [...REGISTRY.keys()].join(\", \") || \"(none registered)\";\n throw new Error(`Unknown database driver \"${name}\". Registered: ${known}.`);\n }\n return d;\n}\n\n/** All registered driver names (for help text / config validation). */\nexport function driverNames(): string[] {\n return [...REGISTRY.keys()];\n}\n"],"mappings":"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","names":["byKey"]}
|
|
@@ -633,7 +633,7 @@ interface ShadowCapability<Conn> {
|
|
|
633
633
|
* `invoke` calls a defined function by name with already-encoded args and returns the function's RAW
|
|
634
634
|
* result (the driver extracts it from its own response shape — surreal `RETURN fn::name($a)` yields the
|
|
635
635
|
* value; a row-returning call yields a row set). The caller decodes that raw value through the
|
|
636
|
-
* function's `.returns(R)` schema
|
|
636
|
+
* function's `.returns(R)` schema (the driver-side call surface decodes it). A defined function still
|
|
637
637
|
* emits/migrates via the schema engine regardless; this capability only adds INVOCATION.
|
|
638
638
|
*/
|
|
639
639
|
interface CallableFunctions<Conn = unknown> {
|
package/lib/driver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { A as AnyConnectionEntry, d as ConnectionConfigBase, e as ConnectionEntry, f as ConnectionInput, h as ResolveContext, R as ResolvedConfig, k as StandardSchemaLike, l as connectionEntry } from './config-BYh7WA4P.js';
|
|
2
|
-
export { b as ApplyOptions, A as Authored, a as AuthoredDef, c as CommandArgs, d as CommandContext, e as CommandIo, f as ConnectionOverrides, g as Definable, h as Diff, i as DiffItem, D as Driver, j as DriverCommand, k as EmitOptions, n as KindEngine, o as KindPlan, K as KindRegistry, p as KindSnapshot, q as KindSpec, t as MigrationDirection, u as MigrationRecord, v as MigrationStore, O as OrderNode, P as ParsedCommandArgs, w as PortableObject, R as Ref, S as ShadowCapability, G as Statement, N as buildKindDiff, T as driverNames, U as emitKinds, Z as getDriver, a0 as introspectKinds, a5 as lowerSchema, a8 as orderObjects, ab as planKinds, ad as registerDriver, af as snapshotKinds, ag as snapshotObjects } from './driver-
|
|
2
|
+
export { b as ApplyOptions, A as Authored, a as AuthoredDef, c as CommandArgs, d as CommandContext, e as CommandIo, f as ConnectionOverrides, g as Definable, h as Diff, i as DiffItem, D as Driver, j as DriverCommand, k as EmitOptions, n as KindEngine, o as KindPlan, K as KindRegistry, p as KindSnapshot, q as KindSpec, t as MigrationDirection, u as MigrationRecord, v as MigrationStore, O as OrderNode, P as ParsedCommandArgs, w as PortableObject, R as Ref, S as ShadowCapability, G as Statement, N as buildKindDiff, T as driverNames, U as emitKinds, Z as getDriver, a0 as introspectKinds, a5 as lowerSchema, a8 as orderObjects, ab as planKinds, ad as registerDriver, af as snapshotKinds, ag as snapshotObjects } from './driver-BGaMUprn.js';
|
|
3
3
|
import 'jiti';
|
|
4
4
|
import './secrets-BETi5p8g.js';
|
|
5
5
|
import 'commander';
|
package/lib/driver.js
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { B as BetterSchemicConfig, R as ResolvedConfig, A as AnyConnectionEntry } from './config-BYh7WA4P.js';
|
|
2
2
|
export { a as BetterSchemicProject, C as ChainCtx, b as ChainableDriverFactory, c as ChainedConfig, d as ConnectionConfigBase, e as ConnectionEntry, f as ConnectionInput, E as EntryArgs, g as EntryClient, h as ResolveContext, i as ResolvedConnectionHandle, S as SchemicConfig, j as SchemicProject, k as StandardSchemaLike, l as connectionEntry, m as isConnectionEntry, n as loadConfig, o as loadProject, p as makeJiti, r as resolveConnectionConfig } from './config-BYh7WA4P.js';
|
|
3
|
-
import { A as Authored, a as AuthoredDef } from './driver-
|
|
4
|
-
export { b as ApplyOptions, C as CallableFunctions, c as CommandArgs, d as CommandContext, e as CommandIo, f as ConnectionOverrides, g as Definable, h as Diff, i as DiffItem, D as Driver, j as DriverCommand, E as EMPTY_STORED, k as EmitOptions, F as Filter, l as FilterOpts, m as KindDisplay, n as KindEngine, o as KindPlan, K as KindRegistry, p as KindSnapshot, q as KindSpec, L as LocalOnly, M as MergeOptions, r as MergeResult, s as Migration, t as MigrationDirection, u as MigrationRecord, v as MigrationStore, O as OrderNode, P as ParsedCommandArgs, w as PortableObject, x as PullFilePlan, y as PullPlan, R as Ref, z as RenderedUnit, B as ResolvedDisplay, S as ShadowCapability, G as Statement, H as StoredSnapshot, I as actionLabel, J as applyPull, N as buildKindDiff, Q as checksum, T as driverNames, U as emitKinds, V as filterKinds, W as formatDiff, X as formatItems, Y as formatPatch, Z as getDriver, _ as inCat, $ as intersectKinds, a0 as introspectKinds, a1 as isEmptyDiff, a2 as kindFlags, a3 as lineDiff, a4 as listMigrations, a5 as lowerSchema, a6 as mergeStored, a7 as mergeUnits, a8 as orderObjects, a9 as parseFilter, aa as passesFilter, ab as planKinds, ac as readSnapshot, ad as registerDriver, ae as slug, af as snapshotKinds, ag as snapshotObjects, ah as summarizeKinds, ai as timestamp, aj as tokenDiff, ak as unifiedDiff, al as writeSnapshot } from './driver-
|
|
3
|
+
import { A as Authored, a as AuthoredDef } from './driver-BGaMUprn.js';
|
|
4
|
+
export { b as ApplyOptions, C as CallableFunctions, c as CommandArgs, d as CommandContext, e as CommandIo, f as ConnectionOverrides, g as Definable, h as Diff, i as DiffItem, D as Driver, j as DriverCommand, E as EMPTY_STORED, k as EmitOptions, F as Filter, l as FilterOpts, m as KindDisplay, n as KindEngine, o as KindPlan, K as KindRegistry, p as KindSnapshot, q as KindSpec, L as LocalOnly, M as MergeOptions, r as MergeResult, s as Migration, t as MigrationDirection, u as MigrationRecord, v as MigrationStore, O as OrderNode, P as ParsedCommandArgs, w as PortableObject, x as PullFilePlan, y as PullPlan, R as Ref, z as RenderedUnit, B as ResolvedDisplay, S as ShadowCapability, G as Statement, H as StoredSnapshot, I as actionLabel, J as applyPull, N as buildKindDiff, Q as checksum, T as driverNames, U as emitKinds, V as filterKinds, W as formatDiff, X as formatItems, Y as formatPatch, Z as getDriver, _ as inCat, $ as intersectKinds, a0 as introspectKinds, a1 as isEmptyDiff, a2 as kindFlags, a3 as lineDiff, a4 as listMigrations, a5 as lowerSchema, a6 as mergeStored, a7 as mergeUnits, a8 as orderObjects, a9 as parseFilter, aa as passesFilter, ab as planKinds, ac as readSnapshot, ad as registerDriver, ae as slug, af as snapshotKinds, ag as snapshotObjects, ah as summarizeKinds, ai as timestamp, aj as tokenDiff, ak as unifiedDiff, al as writeSnapshot } from './driver-BGaMUprn.js';
|
|
5
5
|
export { PortableField, PortablePermissions, PortableType, ScalarName, array, literal, nullable, option, record, scalar, union } from './driver.js';
|
|
6
6
|
export { S as SecretProvider, a as SecretRef, e as env, b as envSecretProvider, i as isSecretRef, s as secret } from './secrets-BETi5p8g.js';
|
|
7
7
|
import 'jiti';
|
package/lib/index.js
CHANGED
package/lib/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { K as KindRegistry, D as Driver } from './driver-
|
|
1
|
+
import { K as KindRegistry, D as Driver } from './driver-BGaMUprn.js';
|
|
2
2
|
import './config-BYh7WA4P.js';
|
|
3
3
|
import 'jiti';
|
|
4
4
|
import './secrets-BETi5p8g.js';
|
|
@@ -95,5 +95,54 @@ interface CoverageReconcileOptions {
|
|
|
95
95
|
* features: FEATURE_MANIFEST, testDir: import.meta.dir });
|
|
96
96
|
*/
|
|
97
97
|
declare function describeCoverageReconcile(opts: CoverageReconcileOptions): void;
|
|
98
|
+
/** One condition's unique-cause independence pair (assignments differ only in that condition). */
|
|
99
|
+
interface McdcIndependence {
|
|
100
|
+
/** The condition name. */
|
|
101
|
+
readonly condition: string;
|
|
102
|
+
/** The two assignments (identical except at `condition`'s index). */
|
|
103
|
+
readonly pair: readonly [readonly boolean[], readonly boolean[]];
|
|
104
|
+
/** The decision outcomes for the pair (always different by construction). */
|
|
105
|
+
readonly outcomes: readonly [boolean, boolean];
|
|
106
|
+
}
|
|
107
|
+
/** The MC/DC analysis of one decision. */
|
|
108
|
+
interface McdcAnalysis {
|
|
109
|
+
readonly label: string;
|
|
110
|
+
readonly conditions: readonly string[];
|
|
111
|
+
/** Every assignment considered (rows of the truth table / the supplied cases). */
|
|
112
|
+
readonly assignments: readonly (readonly boolean[])[];
|
|
113
|
+
/** The decision outcome for each assignment. */
|
|
114
|
+
readonly outcomes: readonly boolean[];
|
|
115
|
+
/** One unique-cause pair per condition that has one. */
|
|
116
|
+
readonly independent: readonly McdcIndependence[];
|
|
117
|
+
/** Conditions with NO unique-cause pair — MC/DC is NOT satisfied for these. */
|
|
118
|
+
readonly missing: readonly string[];
|
|
119
|
+
readonly ok: boolean;
|
|
120
|
+
}
|
|
121
|
+
interface McdcOptions {
|
|
122
|
+
/** Label for the describe block / report (`"isNotFound"`). */
|
|
123
|
+
readonly label: string;
|
|
124
|
+
/** The named conditions, in the SAME order as each assignment's booleans. */
|
|
125
|
+
readonly conditions: readonly string[];
|
|
126
|
+
/** The REAL decision under test: an assignment -> the decision's boolean outcome. */
|
|
127
|
+
readonly evaluate: (assignment: Readonly<Record<string, boolean>>) => boolean;
|
|
128
|
+
/**
|
|
129
|
+
* The assignments actually exercised (each an array of booleans aligned with `conditions`).
|
|
130
|
+
* Defaults to the FULL truth table (`2^conditions.length`) — use this when your tests only cover a
|
|
131
|
+
* subset, so the analysis reflects REAL coverage rather than a hypothetical enumeration.
|
|
132
|
+
*/
|
|
133
|
+
readonly cases?: readonly (readonly boolean[])[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Analyze a decision for unique-cause MC/DC. PURE — returns the per-condition independence pairs and
|
|
137
|
+
* the conditions that lack one; {@link describeMcdc} is the `bun:test` shell.
|
|
138
|
+
*/
|
|
139
|
+
declare function analyzeMcdc(options: McdcOptions): McdcAnalysis;
|
|
140
|
+
/**
|
|
141
|
+
* Register a decision's MC/DC proof as `bun:test` blocks. A condition with no unique-cause
|
|
142
|
+
* independence pair fails a NAMED test, so a redundant/masked operand is caught even when Tier-1
|
|
143
|
+
* branch coverage is 100%. Supply `cases` (the assignments your tests exercise) for REAL coverage;
|
|
144
|
+
* omit it to prove the decision is non-redundant over the full truth table.
|
|
145
|
+
*/
|
|
146
|
+
declare function describeMcdc(options: McdcOptions): void;
|
|
98
147
|
|
|
99
|
-
export { type CoverageCheck, type CoverageReconcileInput, type CoverageReconcileOptions, type CoverageReconcileResult, type CoverageStatus, type DriverConformanceOptions, type FeatureCoverage, type KindCoverage, describeCoverageReconcile, describeDriverConformance, reconcileCoverage };
|
|
148
|
+
export { type CoverageCheck, type CoverageReconcileInput, type CoverageReconcileOptions, type CoverageReconcileResult, type CoverageStatus, type DriverConformanceOptions, type FeatureCoverage, type KindCoverage, type McdcAnalysis, type McdcIndependence, type McdcOptions, analyzeMcdc, describeCoverageReconcile, describeDriverConformance, describeMcdc, reconcileCoverage };
|
package/lib/testing.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
emitKinds,
|
|
4
4
|
getDriver,
|
|
5
5
|
lowerSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-UPFTOIPR.js";
|
|
7
7
|
|
|
8
8
|
// src/testing.ts
|
|
9
9
|
import { describe, expect, test } from "bun:test";
|
|
@@ -204,9 +204,90 @@ function describeCoverageReconcile(opts) {
|
|
|
204
204
|
function readTestSrc(dir) {
|
|
205
205
|
return readdirSync(dir).filter((f) => f.endsWith(".test.ts")).map((f) => readFileSync(join(dir, f), "utf8")).join("\n");
|
|
206
206
|
}
|
|
207
|
+
var MAX_MCDC_CONDITIONS = 8;
|
|
208
|
+
function truthTable(n) {
|
|
209
|
+
const rows = [];
|
|
210
|
+
for (let i = 0; i < 1 << n; i++)
|
|
211
|
+
rows.push(Array.from({ length: n }, (_, bit) => (i >> bit & 1) === 1));
|
|
212
|
+
return rows;
|
|
213
|
+
}
|
|
214
|
+
function toAssignment(conditions, row) {
|
|
215
|
+
const out = {};
|
|
216
|
+
conditions.forEach((name, i) => {
|
|
217
|
+
out[name] = row[i] === true;
|
|
218
|
+
});
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
function analyzeMcdc(options) {
|
|
222
|
+
const { label, conditions, evaluate } = options;
|
|
223
|
+
if (conditions.length === 0)
|
|
224
|
+
throw new Error("analyzeMcdc: pass at least one condition");
|
|
225
|
+
if (conditions.length > MAX_MCDC_CONDITIONS)
|
|
226
|
+
throw new Error(
|
|
227
|
+
`analyzeMcdc: ${conditions.length} conditions exceed the ${MAX_MCDC_CONDITIONS}-condition limit (the truth table would explode) \u2014 pass explicit \`cases\`.`
|
|
228
|
+
);
|
|
229
|
+
if (new Set(conditions).size !== conditions.length)
|
|
230
|
+
throw new Error(
|
|
231
|
+
`analyzeMcdc: duplicate condition name in ${JSON.stringify(conditions)}`
|
|
232
|
+
);
|
|
233
|
+
const assignments = options.cases ?? truthTable(conditions.length);
|
|
234
|
+
for (const row of assignments)
|
|
235
|
+
if (row.length !== conditions.length)
|
|
236
|
+
throw new Error(
|
|
237
|
+
`analyzeMcdc: a case has ${row.length} values for ${conditions.length} conditions`
|
|
238
|
+
);
|
|
239
|
+
const outcomes = assignments.map(
|
|
240
|
+
(row) => evaluate(toAssignment(conditions, row))
|
|
241
|
+
);
|
|
242
|
+
const independent = [];
|
|
243
|
+
const missing = [];
|
|
244
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
245
|
+
let found;
|
|
246
|
+
for (let a = 0; a < assignments.length && !found; a++) {
|
|
247
|
+
for (let b = a + 1; b < assignments.length && !found; b++) {
|
|
248
|
+
const ra = assignments[a];
|
|
249
|
+
const rb = assignments[b];
|
|
250
|
+
if (ra[i] === rb[i]) continue;
|
|
251
|
+
if (!ra.every((v, j) => j === i || v === rb[j])) continue;
|
|
252
|
+
if (outcomes[a] !== outcomes[b])
|
|
253
|
+
found = {
|
|
254
|
+
condition: conditions[i],
|
|
255
|
+
pair: [ra, rb],
|
|
256
|
+
outcomes: [outcomes[a], outcomes[b]]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (found) independent.push(found);
|
|
261
|
+
else missing.push(conditions[i]);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
label,
|
|
265
|
+
conditions,
|
|
266
|
+
assignments,
|
|
267
|
+
outcomes,
|
|
268
|
+
independent,
|
|
269
|
+
missing,
|
|
270
|
+
ok: missing.length === 0
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function describeMcdc(options) {
|
|
274
|
+
const analysis = analyzeMcdc(options);
|
|
275
|
+
describe(`MC/DC: ${analysis.label}`, () => {
|
|
276
|
+
test("every condition has a unique-cause independence pair", () => {
|
|
277
|
+
expect(analysis.missing).toEqual([]);
|
|
278
|
+
});
|
|
279
|
+
for (const pair of analysis.independent) {
|
|
280
|
+
test(`condition "${pair.condition}" is independent`, () => {
|
|
281
|
+
expect(pair.outcomes[0]).not.toBe(pair.outcomes[1]);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
207
286
|
export {
|
|
287
|
+
analyzeMcdc,
|
|
208
288
|
describeCoverageReconcile,
|
|
209
289
|
describeDriverConformance,
|
|
290
|
+
describeMcdc,
|
|
210
291
|
reconcileCoverage
|
|
211
292
|
};
|
|
212
293
|
//# sourceMappingURL=testing.js.map
|
package/lib/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// A shared DRIVER CONFORMANCE suite — the runtime contract a `@better-schemic/<driver>` must satisfy, asserted\n// with `bun:test`. Each driver runs it against its own authoring surface:\n//\n// import { describeDriverConformance } from \"@better-schemic/core/testing\";\n// import { defineTable, s, surrealDriver } from \"@better-schemic/surrealdb\";\n// describeDriverConformance({ name: \"surrealdb\", s, driver: surrealDriver, defineEntity: defineTable });\n//\n// WHY a test, not a type: the zod drop-in builders (`s.string()` = `new <D>Field(z.string())`) are\n// mechanically identical across drivers, but TypeScript has NO higher-kinded types, so a generic core\n// factory can't preserve each driver's field type (it collapses to the base, dropping `$`-methods).\n// Each driver therefore hand-authors its drop-ins, and \"`s` is a Zod SUPERSET\" is enforceable only at\n// runtime. This suite is that enforcement.\n//\n// It DUCK-TYPES fields (a field is \"something with a `.schema` that is a Zod type\") rather than using\n// `instanceof SFieldBase` — a driver may extend its own copy of the base, so identity checks are unsafe.\n\nimport { describe, expect, test } from \"bun:test\";\nimport { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type * as z from \"zod\";\nimport { type Driver, driverNames, getDriver } from \"./driver/driver\";\nimport { emitKinds, type KindRegistry, lowerSchema } from \"./kind\";\n\n/**\n * The driver's authoring namespace (`s`) — a bag of field builders, some NESTED (e.g.\n * `s.iso.{date,time,datetime,duration}`). Intentionally loose: the suite duck-types fields and\n * enforces the real contract at RUNTIME, not via this type (see the header note), so a precise\n * \"function-or-nested-namespace\" shape would only fight the internal `s.<key>()` calls for no gain.\n */\n// biome-ignore lint/suspicious/noExplicitAny: a driver's `s` is dialect-specific + may nest; runtime-duck-typed.\ntype Authoring = Record<string, any>;\n\nexport interface DriverConformanceOptions {\n /** The driver's registry name (e.g. `\"surrealdb\"`). */\n name: string;\n /** The driver's authoring namespace — the `s` each package exports. */\n s: Authoring;\n /** The driver under test (already registered by importing its package). */\n driver: Driver<unknown>;\n /**\n * Authors the driver's primary fielded definable — a table, collection, node-type, … — from a name\n * and a field shape. Used to lower a probe object through the pipeline. Drivers pass their own\n * `define*` for this (e.g. `defineEntity: defineTable`); the suite stays shape-agnostic.\n */\n // biome-ignore lint/suspicious/noExplicitAny: dialect-specific definable/shape types.\n defineEntity: (name: string, shape: Record<string, any>) => any;\n}\n\n/**\n * The canonical zod DROP-IN set every driver's `s` MUST expose — the structural Zod builders that make\n * a `@better-schemic/<driver>` a drop-in for `z`. Each maps to the DB's natural representation (a driver may\n * also offer richer native aliases, e.g. `text`/`varchar` alongside `string`). `object`/`array` nest a\n * `literal` (present everywhere) so a missing `string` doesn't cascade into their tests.\n */\nconst DROP_INS: { key: string; build: (s: Authoring) => unknown }[] = [\n { key: \"string\", build: (s) => s.string() },\n { key: \"number\", build: (s) => s.number() },\n { key: \"boolean\", build: (s) => s.boolean() },\n { key: \"date\", build: (s) => s.date() },\n { key: \"literal\", build: (s) => s.literal(\"a\") },\n { key: \"enum\", build: (s) => s.enum([\"a\", \"b\"]) },\n { key: \"object\", build: (s) => s.object({ inner: s.literal(\"a\") }) },\n { key: \"array\", build: (s) => s.array(s.literal(\"a\")) },\n];\n\n/** Value pairs that prove a scalar drop-in really carries the right Zod schema (unambiguous scalars only). */\nconst SCALAR_CHECKS: { key: string; valid: unknown; invalid: unknown }[] = [\n { key: \"string\", valid: \"hello\", invalid: 123 },\n { key: \"number\", valid: 123, invalid: \"hello\" },\n { key: \"boolean\", valid: true, invalid: \"hello\" },\n];\n\n/** Duck-typed: a field exposes a Zod `.schema`; a raw Zod type IS the schema. Throws if neither. */\nfunction toSchema(v: unknown): z.ZodType {\n const field = v as { schema?: unknown } | null;\n if (field && isZod(field.schema)) return field.schema as z.ZodType;\n if (isZod(v)) return v as z.ZodType;\n throw new Error(\"expected a field (with a `.schema` Zod type) or a Zod type\");\n}\n\nfunction isZod(v: unknown): boolean {\n return !!v && typeof (v as { safeParse?: unknown }).safeParse === \"function\";\n}\n\n/** Is `v` a driver field (has a `.schema` that is a Zod type)? */\nfunction isField(v: unknown): boolean {\n return isZod((v as { schema?: unknown } | null)?.schema);\n}\n\n/**\n * Assert a `@better-schemic/<driver>` conforms to the Better-schemic driver contract: the Driver is registered with\n * the IR pipeline + execution ops, and its `s` is a Zod-drop-in SUPERSET (the canonical drop-in set is\n * present, carries the right schemas, composes through wrappers, and lowers to the portable IR).\n */\nexport function describeDriverConformance(\n opts: DriverConformanceOptions,\n): void {\n const { name, s, driver, defineEntity } = opts;\n\n describe(`driver conformance: ${name}`, () => {\n describe(\"Driver contract\", () => {\n test(\"is registered under its name\", () => {\n expect(driverNames()).toContain(name);\n expect(getDriver(name)).toBe(driver);\n expect(driver.name).toBe(name);\n });\n\n test(\"exposes a kind registry + the schema/execution ops\", () => {\n // Schema ops are generic over `registry`; the driver provides the fan-out + execution.\n expect(driver.registry).toBeDefined();\n expect(typeof driver.registry.entries).toBe(\"function\");\n expect(driver.registry.names().length).toBeGreaterThan(0);\n for (const op of [\n \"explode\",\n \"introspectAll\",\n \"connect\",\n \"apply\",\n \"close\",\n ] as const) {\n expect(typeof driver[op]).toBe(\"function\");\n }\n });\n });\n\n describe(\"zod drop-in surface (s.* is a Zod superset)\", () => {\n for (const { key, build } of DROP_INS) {\n test(`s.${key}() exists and returns a field`, () => {\n expect(typeof s[key]).toBe(\"function\");\n const field = build(s);\n expect(isField(field)).toBe(true);\n });\n }\n\n for (const { key, valid, invalid } of SCALAR_CHECKS) {\n test(`s.${key}() carries a \"${key}\" Zod schema`, () => {\n const schema = toSchema(s[key]());\n expect(schema.safeParse(valid).success).toBe(true);\n expect(schema.safeParse(invalid).success).toBe(false);\n });\n }\n });\n\n describe(\"Zod-clean codecs + wrappers\", () => {\n test(\"decode/encode delegate to the inner Zod schema\", () => {\n const field = s.string() as {\n decode: (v: unknown) => unknown;\n encode: (v: unknown) => unknown;\n };\n expect(field.decode(\"hi\")).toBe(\"hi\");\n expect(field.encode(\"hi\")).toBe(\"hi\");\n });\n\n test(\"wrappers preserve field-ness (optional/array compose)\", () => {\n const field = s.string() as {\n optional: () => unknown;\n array: () => unknown;\n };\n expect(isField(field.optional())).toBe(true);\n expect(isField(field.array())).toBe(true);\n });\n });\n\n describe(\"lowering (drop-in fields → kind registry)\", () => {\n test(\"an entity of drop-in fields explodes + lowers + emits, carrying every field\", () => {\n const shape: Record<string, unknown> = {};\n for (const { key, build } of DROP_INS) shape[`f_${key}`] = build(s);\n const entity = defineEntity(\"better_schemic_conformance_probe\", shape);\n\n // explode (authoring -> kinded definables) -> lowerSchema -> portable objects.\n const portable = lowerSchema(\n driver.registry,\n driver.explode([entity], []),\n );\n // Kind-agnostic: the probe lowers to at least one object (its kind is the driver's own —\n // `table`, `collection`, …); the per-field check below is what proves lowering is faithful.\n expect(portable.length).toBeGreaterThan(0);\n\n // The portable shape is the driver's own, but the emitted DDL is generic: every drop-in\n // field name must appear in it (lowering + emit carried it through).\n const ddl = emitKinds(driver.registry, portable).join(\"\\n\");\n expect(ddl.length).toBeGreaterThan(0);\n for (const { key } of DROP_INS) {\n expect(ddl).toContain(`f_${key}`);\n }\n });\n });\n });\n}\n\n// --- Coverage reconcile -------------------------------------------------------------------------\n//\n// The shared, driver-AGNOSTIC guard that keeps a driver's `docs/COVERAGE.md` (prose discipline) honest\n// against reality — closing the \"done-vs-todo list silently drifts\" gap. A driver declares its coverage\n// as data (a KIND manifest + a FEATURE manifest) and this reconciles it against the LIVE facts: the\n// neutral `registry.names()`/`.entries()` enumeration (so the registered-kind side CAN'T drift from the\n// code) and the actual test titles (so a feature can't be marked done without a real test). The\n// ENFORCEMENT lives here, in ONE place — a driver supplies only its manifest, so a fix propagates to\n// every driver instead of drifting across three copies.\n//\n// DELIBERATELY NOT checked: \"every kind defines `canonical()`\". `KindEngine.canonical` is OPTIONAL by\n// contract (it defaults to `emit(portable).join(\"\\n\")`), so a kind whose `emit` already IS its canonical\n// form correctly omits it — requiring it here would false-fail a conformant driver. A driver that wants\n// the stricter \"all MY kinds define an explicit canonical\" invariant can assert it in a local test.\n\n/** Coverage status, mirroring the `docs/COVERAGE.md` checkbox: full round-trip / partial / not done. */\nexport type CoverageStatus = \"x\" | \"~\" | \" \";\n\n/** A registered KIND and the round-trip status it claims. */\nexport interface KindCoverage {\n name: string;\n status: CoverageStatus;\n note?: string;\n}\n\n/** A finer-grained FEATURE within a kind, and (when done) the test that proves it. */\nexport interface FeatureCoverage {\n key: string;\n kind: string;\n status: CoverageStatus;\n /** A substring of the test title that exercises this feature — REQUIRED when status is `x`. */\n coveredBy?: string;\n note?: string;\n}\n\n/** The live inputs a reconcile runs against (the pure form — no `bun:test`, no filesystem). */\nexport interface CoverageReconcileInput {\n registry: KindRegistry;\n kinds: KindCoverage[];\n features: FeatureCoverage[];\n /** Concatenated source of the driver's `*.test.ts` — real test titles are extracted from it. */\n testSrc: string;\n}\n\n/** One named check and the assertions it failed (empty = passed). */\nexport interface CoverageCheck {\n name: string;\n failures: string[];\n}\n\n/** The reconcile outcome: per-check breakdown + a flattened failure list for a single-assert test. */\nexport interface CoverageReconcileResult {\n checks: CoverageCheck[];\n failures: string[];\n}\n\n/**\n * Extract the titles of the REAL, non-skipped `test(...)`/`it(...)` calls from concatenated test source.\n * Matching against actual titles (rather than a raw `source.includes`) means a mention in a comment or an\n * unrelated string literal can't count as coverage, and a `.skip`/`.todo` test can't satisfy a done claim.\n */\nfunction extractTestTitles(src: string): string[] {\n const re =\n /\\b(?:test|it)(\\.[\\w.]+)?\\s*\\(\\s*([\"'`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/g;\n const titles: string[] = [];\n for (const m of src.matchAll(re)) {\n const modifier = m[1] ?? \"\";\n if (/\\.(?:skip|todo)\\b/.test(modifier)) continue;\n titles.push(m[3]);\n }\n return titles;\n}\n\n/**\n * Reconcile a driver's declared coverage against the live registry + tests. PURE — returns a per-check\n * breakdown; {@link describeCoverageReconcile} is the `bun:test` shell around it. See the section header\n * for what is (and deliberately isn't) checked.\n */\nexport function reconcileCoverage(\n input: CoverageReconcileInput,\n): CoverageReconcileResult {\n const { registry, kinds, features, testSrc } = input;\n const registered = new Set(registry.names());\n const checks: CoverageCheck[] = [];\n\n // 1. Registered kinds EXACTLY equal the manifest, both directions — the neutral registry is the LHS,\n // so registering a kind without listing it (or vice versa) fails by construction.\n const declared = new Set(kinds.map((k) => k.name));\n const kindsFailures: string[] = [];\n for (const name of registered)\n if (!declared.has(name))\n kindsFailures.push(\n `kind \"${name}\" is registered but missing from the manifest`,\n );\n for (const name of declared)\n if (!registered.has(name))\n kindsFailures.push(\n `kind \"${name}\" is in the manifest but not registered`,\n );\n checks.push({\n name: \"registered kinds match the manifest\",\n failures: kindsFailures,\n });\n\n // 2. Every feature references a registered kind (referential integrity).\n checks.push({\n name: \"every feature maps to a registered kind\",\n failures: features\n .filter((f) => !registered.has(f.kind))\n .map((f) => `feature \"${f.key}\" -> unknown kind \"${f.kind}\"`),\n });\n\n // 3. Every DONE feature names a covering test that actually exists.\n const titles = extractTestTitles(testSrc);\n const coverFailures: string[] = [];\n for (const f of features) {\n if (f.status !== \"x\") continue;\n if (!f.coveredBy) {\n coverFailures.push(\n `feature \"${f.key}\" is [x] but declares no coveredBy test`,\n );\n continue;\n }\n if (!titles.some((t) => t.includes(f.coveredBy as string)))\n coverFailures.push(\n `feature \"${f.key}\" coveredBy \"${f.coveredBy}\" — no matching (non-skipped) test title`,\n );\n }\n checks.push({\n name: \"every [x] feature names a covering test\",\n failures: coverFailures,\n });\n\n // 4. No duplicate feature keys / kind entries (hygiene — a dup silently hides one side).\n checks.push({\n name: \"no duplicate feature keys\",\n failures: duplicates(\n features.map((f) => f.key),\n \"feature key\",\n ),\n });\n checks.push({\n name: \"no duplicate kind entries\",\n failures: duplicates(\n kinds.map((k) => k.name),\n \"kind entry\",\n ),\n });\n\n return { checks, failures: checks.flatMap((c) => c.failures) };\n}\n\n/** Names appearing more than once, as failure messages. */\nfunction duplicates(names: string[], label: string): string[] {\n const seen = new Set<string>();\n const dup: string[] = [];\n for (const n of names) {\n if (seen.has(n)) dup.push(`duplicate ${label} \"${n}\"`);\n seen.add(n);\n }\n return dup;\n}\n\n/** Options for the `bun:test` reconcile shell. Supply `testDir` (read for you) OR a pre-read `testSrc`. */\nexport interface CoverageReconcileOptions {\n /** Label for the describe block (typically the driver name). */\n name?: string;\n registry: KindRegistry;\n kinds: KindCoverage[];\n features: FeatureCoverage[];\n /** The dir holding this driver's `*.test.ts`; the helper concatenates them to prove test coverage. */\n testDir?: string;\n /** Pre-read test source, if you'd rather gather it yourself (alternative to `testDir`). */\n testSrc?: string;\n}\n\n/**\n * Register the coverage reconcile as a `bun:test` block — one named `test(...)` per check, so CI reads\n * granularly. A driver calls this from a single `*.test.ts` with its manifest + `testDir`:\n *\n * import { describeCoverageReconcile } from \"@better-schemic/core/testing\";\n * import { registry } from \"../src/kinds\";\n * import { KIND_MANIFEST, FEATURE_MANIFEST } from \"./coverage-manifest\";\n * describeCoverageReconcile({ name: \"sqlite\", registry, kinds: KIND_MANIFEST,\n * features: FEATURE_MANIFEST, testDir: import.meta.dir });\n */\nexport function describeCoverageReconcile(\n opts: CoverageReconcileOptions,\n): void {\n const { name, registry, kinds, features } = opts;\n const testSrc =\n opts.testSrc ?? (opts.testDir ? readTestSrc(opts.testDir) : \"\");\n describe(name ? `coverage reconcile: ${name}` : \"coverage reconcile\", () => {\n for (const check of reconcileCoverage({\n registry,\n kinds,\n features,\n testSrc,\n }).checks) {\n test(check.name, () => {\n expect(check.failures).toEqual([]);\n });\n }\n });\n}\n\n/** Concatenate every `*.test.ts` in `dir` (the source the covering-test check scans). */\nfunction readTestSrc(dir: string): string {\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".test.ts\"))\n .map((f) => readFileSync(join(dir, f), \"utf8\"))\n .join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// A shared DRIVER CONFORMANCE suite — the runtime contract a `@better-schemic/<driver>` must satisfy, asserted\n// with `bun:test`. Each driver runs it against its own authoring surface:\n//\n// import { describeDriverConformance } from \"@better-schemic/core/testing\";\n// import { defineTable, s, surrealDriver } from \"@better-schemic/surrealdb\";\n// describeDriverConformance({ name: \"surrealdb\", s, driver: surrealDriver, defineEntity: defineTable });\n//\n// WHY a test, not a type: the zod drop-in builders (`s.string()` = `new <D>Field(z.string())`) are\n// mechanically identical across drivers, but TypeScript has NO higher-kinded types, so a generic core\n// factory can't preserve each driver's field type (it collapses to the base, dropping `$`-methods).\n// Each driver therefore hand-authors its drop-ins, and \"`s` is a Zod SUPERSET\" is enforceable only at\n// runtime. This suite is that enforcement.\n//\n// It DUCK-TYPES fields (a field is \"something with a `.schema` that is a Zod type\") rather than using\n// `instanceof SFieldBase` — a driver may extend its own copy of the base, so identity checks are unsafe.\n\nimport { describe, expect, test } from \"bun:test\";\nimport { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type * as z from \"zod\";\nimport { type Driver, driverNames, getDriver } from \"./driver/driver\";\nimport { emitKinds, type KindRegistry, lowerSchema } from \"./kind\";\n\n/**\n * The driver's authoring namespace (`s`) — a bag of field builders, some NESTED (e.g.\n * `s.iso.{date,time,datetime,duration}`). Intentionally loose: the suite duck-types fields and\n * enforces the real contract at RUNTIME, not via this type (see the header note), so a precise\n * \"function-or-nested-namespace\" shape would only fight the internal `s.<key>()` calls for no gain.\n */\n// biome-ignore lint/suspicious/noExplicitAny: a driver's `s` is dialect-specific + may nest; runtime-duck-typed.\ntype Authoring = Record<string, any>;\n\nexport interface DriverConformanceOptions {\n /** The driver's registry name (e.g. `\"surrealdb\"`). */\n name: string;\n /** The driver's authoring namespace — the `s` each package exports. */\n s: Authoring;\n /** The driver under test (already registered by importing its package). */\n driver: Driver<unknown>;\n /**\n * Authors the driver's primary fielded definable — a table, collection, node-type, … — from a name\n * and a field shape. Used to lower a probe object through the pipeline. Drivers pass their own\n * `define*` for this (e.g. `defineEntity: defineTable`); the suite stays shape-agnostic.\n */\n // biome-ignore lint/suspicious/noExplicitAny: dialect-specific definable/shape types.\n defineEntity: (name: string, shape: Record<string, any>) => any;\n}\n\n/**\n * The canonical zod DROP-IN set every driver's `s` MUST expose — the structural Zod builders that make\n * a `@better-schemic/<driver>` a drop-in for `z`. Each maps to the DB's natural representation (a driver may\n * also offer richer native aliases, e.g. `text`/`varchar` alongside `string`). `object`/`array` nest a\n * `literal` (present everywhere) so a missing `string` doesn't cascade into their tests.\n */\nconst DROP_INS: { key: string; build: (s: Authoring) => unknown }[] = [\n { key: \"string\", build: (s) => s.string() },\n { key: \"number\", build: (s) => s.number() },\n { key: \"boolean\", build: (s) => s.boolean() },\n { key: \"date\", build: (s) => s.date() },\n { key: \"literal\", build: (s) => s.literal(\"a\") },\n { key: \"enum\", build: (s) => s.enum([\"a\", \"b\"]) },\n { key: \"object\", build: (s) => s.object({ inner: s.literal(\"a\") }) },\n { key: \"array\", build: (s) => s.array(s.literal(\"a\")) },\n];\n\n/** Value pairs that prove a scalar drop-in really carries the right Zod schema (unambiguous scalars only). */\nconst SCALAR_CHECKS: { key: string; valid: unknown; invalid: unknown }[] = [\n { key: \"string\", valid: \"hello\", invalid: 123 },\n { key: \"number\", valid: 123, invalid: \"hello\" },\n { key: \"boolean\", valid: true, invalid: \"hello\" },\n];\n\n/** Duck-typed: a field exposes a Zod `.schema`; a raw Zod type IS the schema. Throws if neither. */\nfunction toSchema(v: unknown): z.ZodType {\n const field = v as { schema?: unknown } | null;\n if (field && isZod(field.schema)) return field.schema as z.ZodType;\n if (isZod(v)) return v as z.ZodType;\n throw new Error(\"expected a field (with a `.schema` Zod type) or a Zod type\");\n}\n\nfunction isZod(v: unknown): boolean {\n return !!v && typeof (v as { safeParse?: unknown }).safeParse === \"function\";\n}\n\n/** Is `v` a driver field (has a `.schema` that is a Zod type)? */\nfunction isField(v: unknown): boolean {\n return isZod((v as { schema?: unknown } | null)?.schema);\n}\n\n/**\n * Assert a `@better-schemic/<driver>` conforms to the Better-schemic driver contract: the Driver is registered with\n * the IR pipeline + execution ops, and its `s` is a Zod-drop-in SUPERSET (the canonical drop-in set is\n * present, carries the right schemas, composes through wrappers, and lowers to the portable IR).\n */\nexport function describeDriverConformance(\n opts: DriverConformanceOptions,\n): void {\n const { name, s, driver, defineEntity } = opts;\n\n describe(`driver conformance: ${name}`, () => {\n describe(\"Driver contract\", () => {\n test(\"is registered under its name\", () => {\n expect(driverNames()).toContain(name);\n expect(getDriver(name)).toBe(driver);\n expect(driver.name).toBe(name);\n });\n\n test(\"exposes a kind registry + the schema/execution ops\", () => {\n // Schema ops are generic over `registry`; the driver provides the fan-out + execution.\n expect(driver.registry).toBeDefined();\n expect(typeof driver.registry.entries).toBe(\"function\");\n expect(driver.registry.names().length).toBeGreaterThan(0);\n for (const op of [\n \"explode\",\n \"introspectAll\",\n \"connect\",\n \"apply\",\n \"close\",\n ] as const) {\n expect(typeof driver[op]).toBe(\"function\");\n }\n });\n });\n\n describe(\"zod drop-in surface (s.* is a Zod superset)\", () => {\n for (const { key, build } of DROP_INS) {\n test(`s.${key}() exists and returns a field`, () => {\n expect(typeof s[key]).toBe(\"function\");\n const field = build(s);\n expect(isField(field)).toBe(true);\n });\n }\n\n for (const { key, valid, invalid } of SCALAR_CHECKS) {\n test(`s.${key}() carries a \"${key}\" Zod schema`, () => {\n const schema = toSchema(s[key]());\n expect(schema.safeParse(valid).success).toBe(true);\n expect(schema.safeParse(invalid).success).toBe(false);\n });\n }\n });\n\n describe(\"Zod-clean codecs + wrappers\", () => {\n test(\"decode/encode delegate to the inner Zod schema\", () => {\n const field = s.string() as {\n decode: (v: unknown) => unknown;\n encode: (v: unknown) => unknown;\n };\n expect(field.decode(\"hi\")).toBe(\"hi\");\n expect(field.encode(\"hi\")).toBe(\"hi\");\n });\n\n test(\"wrappers preserve field-ness (optional/array compose)\", () => {\n const field = s.string() as {\n optional: () => unknown;\n array: () => unknown;\n };\n expect(isField(field.optional())).toBe(true);\n expect(isField(field.array())).toBe(true);\n });\n });\n\n describe(\"lowering (drop-in fields → kind registry)\", () => {\n test(\"an entity of drop-in fields explodes + lowers + emits, carrying every field\", () => {\n const shape: Record<string, unknown> = {};\n for (const { key, build } of DROP_INS) shape[`f_${key}`] = build(s);\n const entity = defineEntity(\"better_schemic_conformance_probe\", shape);\n\n // explode (authoring -> kinded definables) -> lowerSchema -> portable objects.\n const portable = lowerSchema(\n driver.registry,\n driver.explode([entity], []),\n );\n // Kind-agnostic: the probe lowers to at least one object (its kind is the driver's own —\n // `table`, `collection`, …); the per-field check below is what proves lowering is faithful.\n expect(portable.length).toBeGreaterThan(0);\n\n // The portable shape is the driver's own, but the emitted DDL is generic: every drop-in\n // field name must appear in it (lowering + emit carried it through).\n const ddl = emitKinds(driver.registry, portable).join(\"\\n\");\n expect(ddl.length).toBeGreaterThan(0);\n for (const { key } of DROP_INS) {\n expect(ddl).toContain(`f_${key}`);\n }\n });\n });\n });\n}\n\n// --- Coverage reconcile -------------------------------------------------------------------------\n//\n// The shared, driver-AGNOSTIC guard that keeps a driver's `docs/COVERAGE.md` (prose discipline) honest\n// against reality — closing the \"done-vs-todo list silently drifts\" gap. A driver declares its coverage\n// as data (a KIND manifest + a FEATURE manifest) and this reconciles it against the LIVE facts: the\n// neutral `registry.names()`/`.entries()` enumeration (so the registered-kind side CAN'T drift from the\n// code) and the actual test titles (so a feature can't be marked done without a real test). The\n// ENFORCEMENT lives here, in ONE place — a driver supplies only its manifest, so a fix propagates to\n// every driver instead of drifting across three copies.\n//\n// DELIBERATELY NOT checked: \"every kind defines `canonical()`\". `KindEngine.canonical` is OPTIONAL by\n// contract (it defaults to `emit(portable).join(\"\\n\")`), so a kind whose `emit` already IS its canonical\n// form correctly omits it — requiring it here would false-fail a conformant driver. A driver that wants\n// the stricter \"all MY kinds define an explicit canonical\" invariant can assert it in a local test.\n\n/** Coverage status, mirroring the `docs/COVERAGE.md` checkbox: full round-trip / partial / not done. */\nexport type CoverageStatus = \"x\" | \"~\" | \" \";\n\n/** A registered KIND and the round-trip status it claims. */\nexport interface KindCoverage {\n name: string;\n status: CoverageStatus;\n note?: string;\n}\n\n/** A finer-grained FEATURE within a kind, and (when done) the test that proves it. */\nexport interface FeatureCoverage {\n key: string;\n kind: string;\n status: CoverageStatus;\n /** A substring of the test title that exercises this feature — REQUIRED when status is `x`. */\n coveredBy?: string;\n note?: string;\n}\n\n/** The live inputs a reconcile runs against (the pure form — no `bun:test`, no filesystem). */\nexport interface CoverageReconcileInput {\n registry: KindRegistry;\n kinds: KindCoverage[];\n features: FeatureCoverage[];\n /** Concatenated source of the driver's `*.test.ts` — real test titles are extracted from it. */\n testSrc: string;\n}\n\n/** One named check and the assertions it failed (empty = passed). */\nexport interface CoverageCheck {\n name: string;\n failures: string[];\n}\n\n/** The reconcile outcome: per-check breakdown + a flattened failure list for a single-assert test. */\nexport interface CoverageReconcileResult {\n checks: CoverageCheck[];\n failures: string[];\n}\n\n/**\n * Extract the titles of the REAL, non-skipped `test(...)`/`it(...)` calls from concatenated test source.\n * Matching against actual titles (rather than a raw `source.includes`) means a mention in a comment or an\n * unrelated string literal can't count as coverage, and a `.skip`/`.todo` test can't satisfy a done claim.\n */\nfunction extractTestTitles(src: string): string[] {\n const re =\n /\\b(?:test|it)(\\.[\\w.]+)?\\s*\\(\\s*([\"'`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/g;\n const titles: string[] = [];\n for (const m of src.matchAll(re)) {\n const modifier = m[1] ?? \"\";\n if (/\\.(?:skip|todo)\\b/.test(modifier)) continue;\n titles.push(m[3]);\n }\n return titles;\n}\n\n/**\n * Reconcile a driver's declared coverage against the live registry + tests. PURE — returns a per-check\n * breakdown; {@link describeCoverageReconcile} is the `bun:test` shell around it. See the section header\n * for what is (and deliberately isn't) checked.\n */\nexport function reconcileCoverage(\n input: CoverageReconcileInput,\n): CoverageReconcileResult {\n const { registry, kinds, features, testSrc } = input;\n const registered = new Set(registry.names());\n const checks: CoverageCheck[] = [];\n\n // 1. Registered kinds EXACTLY equal the manifest, both directions — the neutral registry is the LHS,\n // so registering a kind without listing it (or vice versa) fails by construction.\n const declared = new Set(kinds.map((k) => k.name));\n const kindsFailures: string[] = [];\n for (const name of registered)\n if (!declared.has(name))\n kindsFailures.push(\n `kind \"${name}\" is registered but missing from the manifest`,\n );\n for (const name of declared)\n if (!registered.has(name))\n kindsFailures.push(\n `kind \"${name}\" is in the manifest but not registered`,\n );\n checks.push({\n name: \"registered kinds match the manifest\",\n failures: kindsFailures,\n });\n\n // 2. Every feature references a registered kind (referential integrity).\n checks.push({\n name: \"every feature maps to a registered kind\",\n failures: features\n .filter((f) => !registered.has(f.kind))\n .map((f) => `feature \"${f.key}\" -> unknown kind \"${f.kind}\"`),\n });\n\n // 3. Every DONE feature names a covering test that actually exists.\n const titles = extractTestTitles(testSrc);\n const coverFailures: string[] = [];\n for (const f of features) {\n if (f.status !== \"x\") continue;\n if (!f.coveredBy) {\n coverFailures.push(\n `feature \"${f.key}\" is [x] but declares no coveredBy test`,\n );\n continue;\n }\n if (!titles.some((t) => t.includes(f.coveredBy as string)))\n coverFailures.push(\n `feature \"${f.key}\" coveredBy \"${f.coveredBy}\" — no matching (non-skipped) test title`,\n );\n }\n checks.push({\n name: \"every [x] feature names a covering test\",\n failures: coverFailures,\n });\n\n // 4. No duplicate feature keys / kind entries (hygiene — a dup silently hides one side).\n checks.push({\n name: \"no duplicate feature keys\",\n failures: duplicates(\n features.map((f) => f.key),\n \"feature key\",\n ),\n });\n checks.push({\n name: \"no duplicate kind entries\",\n failures: duplicates(\n kinds.map((k) => k.name),\n \"kind entry\",\n ),\n });\n\n return { checks, failures: checks.flatMap((c) => c.failures) };\n}\n\n/** Names appearing more than once, as failure messages. */\nfunction duplicates(names: string[], label: string): string[] {\n const seen = new Set<string>();\n const dup: string[] = [];\n for (const n of names) {\n if (seen.has(n)) dup.push(`duplicate ${label} \"${n}\"`);\n seen.add(n);\n }\n return dup;\n}\n\n/** Options for the `bun:test` reconcile shell. Supply `testDir` (read for you) OR a pre-read `testSrc`. */\nexport interface CoverageReconcileOptions {\n /** Label for the describe block (typically the driver name). */\n name?: string;\n registry: KindRegistry;\n kinds: KindCoverage[];\n features: FeatureCoverage[];\n /** The dir holding this driver's `*.test.ts`; the helper concatenates them to prove test coverage. */\n testDir?: string;\n /** Pre-read test source, if you'd rather gather it yourself (alternative to `testDir`). */\n testSrc?: string;\n}\n\n/**\n * Register the coverage reconcile as a `bun:test` block — one named `test(...)` per check, so CI reads\n * granularly. A driver calls this from a single `*.test.ts` with its manifest + `testDir`:\n *\n * import { describeCoverageReconcile } from \"@better-schemic/core/testing\";\n * import { registry } from \"../src/kinds\";\n * import { KIND_MANIFEST, FEATURE_MANIFEST } from \"./coverage-manifest\";\n * describeCoverageReconcile({ name: \"sqlite\", registry, kinds: KIND_MANIFEST,\n * features: FEATURE_MANIFEST, testDir: import.meta.dir });\n */\nexport function describeCoverageReconcile(\n opts: CoverageReconcileOptions,\n): void {\n const { name, registry, kinds, features } = opts;\n const testSrc =\n opts.testSrc ?? (opts.testDir ? readTestSrc(opts.testDir) : \"\");\n describe(name ? `coverage reconcile: ${name}` : \"coverage reconcile\", () => {\n for (const check of reconcileCoverage({\n registry,\n kinds,\n features,\n testSrc,\n }).checks) {\n test(check.name, () => {\n expect(check.failures).toEqual([]);\n });\n }\n });\n}\n\n/** Concatenate every `*.test.ts` in `dir` (the source the covering-test check scans). */\nfunction readTestSrc(dir: string): string {\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".test.ts\"))\n .map((f) => readFileSync(join(dir, f), \"utf8\"))\n .join(\"\\n\");\n}\n\n// --- MC/DC (Modified Condition/Decision Coverage) -----------------------------------------------\n//\n// Tier-1 coverage (statements/branches + oxc's per-operand truthiness) proves a decision's operands\n// were each seen true AND false — but NOT that each operand INDEPENDENTLY affects the outcome. MC/DC\n// is the stronger standard: for every condition there must be a UNIQUE-CAUSE pair — two test cases\n// that differ ONLY in that condition and flip the decision. This helper is the driver-agnostic,\n// pure engine for that proof: it enumerates the condition assignments (or takes the ones your tests\n// actually exercise), evaluates the REAL decision, and reports any condition with no such pair.\n//\n// WHY a helper and not a tool: no mainstream JS/TS tool computes true MC/DC (they stop at\n// condition/decision coverage). `analyzeMcdc` closes that gap at the test level, so a redundant\n// condition (`a && a`), a masked operand, or an untested branch fails a NAMED test instead of\n// silently passing a 100% branch gate.\n\n/** One condition's unique-cause independence pair (assignments differ only in that condition). */\nexport interface McdcIndependence {\n /** The condition name. */\n readonly condition: string;\n /** The two assignments (identical except at `condition`'s index). */\n readonly pair: readonly [readonly boolean[], readonly boolean[]];\n /** The decision outcomes for the pair (always different by construction). */\n readonly outcomes: readonly [boolean, boolean];\n}\n\n/** The MC/DC analysis of one decision. */\nexport interface McdcAnalysis {\n readonly label: string;\n readonly conditions: readonly string[];\n /** Every assignment considered (rows of the truth table / the supplied cases). */\n readonly assignments: readonly (readonly boolean[])[];\n /** The decision outcome for each assignment. */\n readonly outcomes: readonly boolean[];\n /** One unique-cause pair per condition that has one. */\n readonly independent: readonly McdcIndependence[];\n /** Conditions with NO unique-cause pair — MC/DC is NOT satisfied for these. */\n readonly missing: readonly string[];\n readonly ok: boolean;\n}\n\nexport interface McdcOptions {\n /** Label for the describe block / report (`\"isNotFound\"`). */\n readonly label: string;\n /** The named conditions, in the SAME order as each assignment's booleans. */\n readonly conditions: readonly string[];\n /** The REAL decision under test: an assignment -> the decision's boolean outcome. */\n readonly evaluate: (assignment: Readonly<Record<string, boolean>>) => boolean;\n /**\n * The assignments actually exercised (each an array of booleans aligned with `conditions`).\n * Defaults to the FULL truth table (`2^conditions.length`) — use this when your tests only cover a\n * subset, so the analysis reflects REAL coverage rather than a hypothetical enumeration.\n */\n readonly cases?: readonly (readonly boolean[])[];\n}\n\nconst MAX_MCDC_CONDITIONS = 8;\n\n/** Every truth-table assignment for `n` conditions (little-endian: index 0 is the last condition). */\nfunction truthTable(n: number): boolean[][] {\n const rows: boolean[][] = [];\n for (let i = 0; i < 1 << n; i++)\n rows.push(Array.from({ length: n }, (_, bit) => ((i >> bit) & 1) === 1));\n return rows;\n}\n\nfunction toAssignment(\n conditions: readonly string[],\n row: readonly boolean[],\n): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n conditions.forEach((name, i) => {\n out[name] = row[i] === true;\n });\n return out;\n}\n\n/**\n * Analyze a decision for unique-cause MC/DC. PURE — returns the per-condition independence pairs and\n * the conditions that lack one; {@link describeMcdc} is the `bun:test` shell.\n */\nexport function analyzeMcdc(options: McdcOptions): McdcAnalysis {\n const { label, conditions, evaluate } = options;\n if (conditions.length === 0)\n throw new Error(\"analyzeMcdc: pass at least one condition\");\n if (conditions.length > MAX_MCDC_CONDITIONS)\n throw new Error(\n `analyzeMcdc: ${conditions.length} conditions exceed the ${MAX_MCDC_CONDITIONS}-condition limit (the truth table would explode) — pass explicit \\`cases\\`.`,\n );\n if (new Set(conditions).size !== conditions.length)\n throw new Error(\n `analyzeMcdc: duplicate condition name in ${JSON.stringify(conditions)}`,\n );\n const assignments = options.cases ?? truthTable(conditions.length);\n for (const row of assignments)\n if (row.length !== conditions.length)\n throw new Error(\n `analyzeMcdc: a case has ${row.length} values for ${conditions.length} conditions`,\n );\n const outcomes = assignments.map((row) =>\n evaluate(toAssignment(conditions, row)),\n );\n\n const independent: McdcIndependence[] = [];\n const missing: string[] = [];\n for (let i = 0; i < conditions.length; i++) {\n let found: McdcIndependence | undefined;\n for (let a = 0; a < assignments.length && !found; a++) {\n for (let b = a + 1; b < assignments.length && !found; b++) {\n const ra = assignments[a] as readonly boolean[];\n const rb = assignments[b] as readonly boolean[];\n if (ra[i] === rb[i]) continue;\n if (!ra.every((v, j) => j === i || v === rb[j])) continue;\n if (outcomes[a] !== outcomes[b])\n found = {\n condition: conditions[i] as string,\n pair: [ra, rb],\n outcomes: [outcomes[a] as boolean, outcomes[b] as boolean],\n };\n }\n }\n if (found) independent.push(found);\n else missing.push(conditions[i] as string);\n }\n\n return {\n label,\n conditions,\n assignments,\n outcomes,\n independent,\n missing,\n ok: missing.length === 0,\n };\n}\n\n/**\n * Register a decision's MC/DC proof as `bun:test` blocks. A condition with no unique-cause\n * independence pair fails a NAMED test, so a redundant/masked operand is caught even when Tier-1\n * branch coverage is 100%. Supply `cases` (the assignments your tests exercise) for REAL coverage;\n * omit it to prove the decision is non-redundant over the full truth table.\n */\nexport function describeMcdc(options: McdcOptions): void {\n const analysis = analyzeMcdc(options);\n describe(`MC/DC: ${analysis.label}`, () => {\n test(\"every condition has a unique-cause independence pair\", () => {\n expect(analysis.missing).toEqual([]);\n });\n for (const pair of analysis.independent) {\n test(`condition \"${pair.condition}\" is independent`, () => {\n expect(pair.outcomes[0]).not.toBe(pair.outcomes[1]);\n });\n }\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-schemic/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "The dialect-neutral engine for Better-schemic — Driver contract, portable schema IR, and the migration/diff/snapshot engine.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vertio Solutions",
|
|
@@ -58,13 +58,6 @@
|
|
|
58
58
|
"default": "./lib/testing.js"
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
|
-
"./query": {
|
|
62
|
-
"bun": "./src/query.ts",
|
|
63
|
-
"import": {
|
|
64
|
-
"types": "./lib/query.d.ts",
|
|
65
|
-
"default": "./lib/query.js"
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
61
|
"./package.json": "./package.json"
|
|
69
62
|
},
|
|
70
63
|
"scripts": {
|
|
@@ -75,10 +68,11 @@
|
|
|
75
68
|
"test:live": "bun test test/live",
|
|
76
69
|
"test:e2e": "bun test test/e2e",
|
|
77
70
|
"test:types": "bun run ../../scripts/type-perf.ts packages/core",
|
|
78
|
-
"typecheck": "
|
|
71
|
+
"typecheck": "tsgo --noEmit",
|
|
79
72
|
"lint": "biome check .",
|
|
80
73
|
"lint:fix": "biome check --write .",
|
|
81
|
-
"format": "biome format --write ."
|
|
74
|
+
"format": "biome format --write .",
|
|
75
|
+
"typecheck:legacy": "tsc --noEmit"
|
|
82
76
|
},
|
|
83
77
|
"dependencies": {
|
|
84
78
|
"commander": "^14.0.2",
|
|
@@ -96,7 +90,8 @@
|
|
|
96
90
|
"surrealdb": "^2.0.3",
|
|
97
91
|
"tsup": "^8",
|
|
98
92
|
"tsx": "^4.23.0",
|
|
99
|
-
"typescript": "^
|
|
93
|
+
"typescript": "^6.0.3",
|
|
94
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
100
95
|
"zod": "^4.3.5"
|
|
101
96
|
}
|
|
102
97
|
}
|
package/src/driver/driver.ts
CHANGED
|
@@ -156,7 +156,7 @@ export interface ShadowCapability<Conn> {
|
|
|
156
156
|
* `invoke` calls a defined function by name with already-encoded args and returns the function's RAW
|
|
157
157
|
* result (the driver extracts it from its own response shape — surreal `RETURN fn::name($a)` yields the
|
|
158
158
|
* value; a row-returning call yields a row set). The caller decodes that raw value through the
|
|
159
|
-
* function's `.returns(R)` schema
|
|
159
|
+
* function's `.returns(R)` schema (the driver-side call surface decodes it). A defined function still
|
|
160
160
|
* emits/migrates via the schema engine regardless; this capability only adds INVOCATION.
|
|
161
161
|
*/
|
|
162
162
|
export interface CallableFunctions<Conn = unknown> {
|
package/src/testing.ts
CHANGED
|
@@ -400,3 +400,157 @@ function readTestSrc(dir: string): string {
|
|
|
400
400
|
.map((f) => readFileSync(join(dir, f), "utf8"))
|
|
401
401
|
.join("\n");
|
|
402
402
|
}
|
|
403
|
+
|
|
404
|
+
// --- MC/DC (Modified Condition/Decision Coverage) -----------------------------------------------
|
|
405
|
+
//
|
|
406
|
+
// Tier-1 coverage (statements/branches + oxc's per-operand truthiness) proves a decision's operands
|
|
407
|
+
// were each seen true AND false — but NOT that each operand INDEPENDENTLY affects the outcome. MC/DC
|
|
408
|
+
// is the stronger standard: for every condition there must be a UNIQUE-CAUSE pair — two test cases
|
|
409
|
+
// that differ ONLY in that condition and flip the decision. This helper is the driver-agnostic,
|
|
410
|
+
// pure engine for that proof: it enumerates the condition assignments (or takes the ones your tests
|
|
411
|
+
// actually exercise), evaluates the REAL decision, and reports any condition with no such pair.
|
|
412
|
+
//
|
|
413
|
+
// WHY a helper and not a tool: no mainstream JS/TS tool computes true MC/DC (they stop at
|
|
414
|
+
// condition/decision coverage). `analyzeMcdc` closes that gap at the test level, so a redundant
|
|
415
|
+
// condition (`a && a`), a masked operand, or an untested branch fails a NAMED test instead of
|
|
416
|
+
// silently passing a 100% branch gate.
|
|
417
|
+
|
|
418
|
+
/** One condition's unique-cause independence pair (assignments differ only in that condition). */
|
|
419
|
+
export interface McdcIndependence {
|
|
420
|
+
/** The condition name. */
|
|
421
|
+
readonly condition: string;
|
|
422
|
+
/** The two assignments (identical except at `condition`'s index). */
|
|
423
|
+
readonly pair: readonly [readonly boolean[], readonly boolean[]];
|
|
424
|
+
/** The decision outcomes for the pair (always different by construction). */
|
|
425
|
+
readonly outcomes: readonly [boolean, boolean];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** The MC/DC analysis of one decision. */
|
|
429
|
+
export interface McdcAnalysis {
|
|
430
|
+
readonly label: string;
|
|
431
|
+
readonly conditions: readonly string[];
|
|
432
|
+
/** Every assignment considered (rows of the truth table / the supplied cases). */
|
|
433
|
+
readonly assignments: readonly (readonly boolean[])[];
|
|
434
|
+
/** The decision outcome for each assignment. */
|
|
435
|
+
readonly outcomes: readonly boolean[];
|
|
436
|
+
/** One unique-cause pair per condition that has one. */
|
|
437
|
+
readonly independent: readonly McdcIndependence[];
|
|
438
|
+
/** Conditions with NO unique-cause pair — MC/DC is NOT satisfied for these. */
|
|
439
|
+
readonly missing: readonly string[];
|
|
440
|
+
readonly ok: boolean;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export interface McdcOptions {
|
|
444
|
+
/** Label for the describe block / report (`"isNotFound"`). */
|
|
445
|
+
readonly label: string;
|
|
446
|
+
/** The named conditions, in the SAME order as each assignment's booleans. */
|
|
447
|
+
readonly conditions: readonly string[];
|
|
448
|
+
/** The REAL decision under test: an assignment -> the decision's boolean outcome. */
|
|
449
|
+
readonly evaluate: (assignment: Readonly<Record<string, boolean>>) => boolean;
|
|
450
|
+
/**
|
|
451
|
+
* The assignments actually exercised (each an array of booleans aligned with `conditions`).
|
|
452
|
+
* Defaults to the FULL truth table (`2^conditions.length`) — use this when your tests only cover a
|
|
453
|
+
* subset, so the analysis reflects REAL coverage rather than a hypothetical enumeration.
|
|
454
|
+
*/
|
|
455
|
+
readonly cases?: readonly (readonly boolean[])[];
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const MAX_MCDC_CONDITIONS = 8;
|
|
459
|
+
|
|
460
|
+
/** Every truth-table assignment for `n` conditions (little-endian: index 0 is the last condition). */
|
|
461
|
+
function truthTable(n: number): boolean[][] {
|
|
462
|
+
const rows: boolean[][] = [];
|
|
463
|
+
for (let i = 0; i < 1 << n; i++)
|
|
464
|
+
rows.push(Array.from({ length: n }, (_, bit) => ((i >> bit) & 1) === 1));
|
|
465
|
+
return rows;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function toAssignment(
|
|
469
|
+
conditions: readonly string[],
|
|
470
|
+
row: readonly boolean[],
|
|
471
|
+
): Record<string, boolean> {
|
|
472
|
+
const out: Record<string, boolean> = {};
|
|
473
|
+
conditions.forEach((name, i) => {
|
|
474
|
+
out[name] = row[i] === true;
|
|
475
|
+
});
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Analyze a decision for unique-cause MC/DC. PURE — returns the per-condition independence pairs and
|
|
481
|
+
* the conditions that lack one; {@link describeMcdc} is the `bun:test` shell.
|
|
482
|
+
*/
|
|
483
|
+
export function analyzeMcdc(options: McdcOptions): McdcAnalysis {
|
|
484
|
+
const { label, conditions, evaluate } = options;
|
|
485
|
+
if (conditions.length === 0)
|
|
486
|
+
throw new Error("analyzeMcdc: pass at least one condition");
|
|
487
|
+
if (conditions.length > MAX_MCDC_CONDITIONS)
|
|
488
|
+
throw new Error(
|
|
489
|
+
`analyzeMcdc: ${conditions.length} conditions exceed the ${MAX_MCDC_CONDITIONS}-condition limit (the truth table would explode) — pass explicit \`cases\`.`,
|
|
490
|
+
);
|
|
491
|
+
if (new Set(conditions).size !== conditions.length)
|
|
492
|
+
throw new Error(
|
|
493
|
+
`analyzeMcdc: duplicate condition name in ${JSON.stringify(conditions)}`,
|
|
494
|
+
);
|
|
495
|
+
const assignments = options.cases ?? truthTable(conditions.length);
|
|
496
|
+
for (const row of assignments)
|
|
497
|
+
if (row.length !== conditions.length)
|
|
498
|
+
throw new Error(
|
|
499
|
+
`analyzeMcdc: a case has ${row.length} values for ${conditions.length} conditions`,
|
|
500
|
+
);
|
|
501
|
+
const outcomes = assignments.map((row) =>
|
|
502
|
+
evaluate(toAssignment(conditions, row)),
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
const independent: McdcIndependence[] = [];
|
|
506
|
+
const missing: string[] = [];
|
|
507
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
508
|
+
let found: McdcIndependence | undefined;
|
|
509
|
+
for (let a = 0; a < assignments.length && !found; a++) {
|
|
510
|
+
for (let b = a + 1; b < assignments.length && !found; b++) {
|
|
511
|
+
const ra = assignments[a] as readonly boolean[];
|
|
512
|
+
const rb = assignments[b] as readonly boolean[];
|
|
513
|
+
if (ra[i] === rb[i]) continue;
|
|
514
|
+
if (!ra.every((v, j) => j === i || v === rb[j])) continue;
|
|
515
|
+
if (outcomes[a] !== outcomes[b])
|
|
516
|
+
found = {
|
|
517
|
+
condition: conditions[i] as string,
|
|
518
|
+
pair: [ra, rb],
|
|
519
|
+
outcomes: [outcomes[a] as boolean, outcomes[b] as boolean],
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (found) independent.push(found);
|
|
524
|
+
else missing.push(conditions[i] as string);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
label,
|
|
529
|
+
conditions,
|
|
530
|
+
assignments,
|
|
531
|
+
outcomes,
|
|
532
|
+
independent,
|
|
533
|
+
missing,
|
|
534
|
+
ok: missing.length === 0,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Register a decision's MC/DC proof as `bun:test` blocks. A condition with no unique-cause
|
|
540
|
+
* independence pair fails a NAMED test, so a redundant/masked operand is caught even when Tier-1
|
|
541
|
+
* branch coverage is 100%. Supply `cases` (the assignments your tests exercise) for REAL coverage;
|
|
542
|
+
* omit it to prove the decision is non-redundant over the full truth table.
|
|
543
|
+
*/
|
|
544
|
+
export function describeMcdc(options: McdcOptions): void {
|
|
545
|
+
const analysis = analyzeMcdc(options);
|
|
546
|
+
describe(`MC/DC: ${analysis.label}`, () => {
|
|
547
|
+
test("every condition has a unique-cause independence pair", () => {
|
|
548
|
+
expect(analysis.missing).toEqual([]);
|
|
549
|
+
});
|
|
550
|
+
for (const pair of analysis.independent) {
|
|
551
|
+
test(`condition "${pair.condition}" is independent`, () => {
|
|
552
|
+
expect(pair.outcomes[0]).not.toBe(pair.outcomes[1]);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
package/lib/query.d.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { C as CallableFunctions } from './driver-LVldBEhS.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import './config-BYh7WA4P.js';
|
|
4
|
-
import 'jiti';
|
|
5
|
-
import './secrets-BETi5p8g.js';
|
|
6
|
-
import 'commander';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Invoke a defined DB function via the driver's `callable` capability and **decode its result through the
|
|
10
|
-
* function's `.returns(R)` schema** — the neutral half of the query layer's (B) `.call()`. A driver's
|
|
11
|
-
* `defineFunction(args).returns(R).call(db, appArgs)` composes this: it encodes `appArgs` to wire (via
|
|
12
|
-
* the arg schemas) and passes `R` here. Decode-by-default is the differentiator — results come back as
|
|
13
|
-
* real `App` types (`Date`, `RecordId`, …), not wire. A `.raw()` path skips this and returns
|
|
14
|
-
* `callable.invoke(...)` directly.
|
|
15
|
-
*/
|
|
16
|
-
declare function callFunction<S extends z.ZodType>(callable: CallableFunctions, conn: unknown, name: string, args: Record<string, unknown>, returns: S): Promise<z.output<S>>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* One selected projection column: the output key (`as`) plus the source Zod schema to decode it through.
|
|
20
|
-
* The schema may itself be a `z.object(...)` for a nested projection — the driver's builder assembles the
|
|
21
|
-
* tree; core just decodes it.
|
|
22
|
-
*/
|
|
23
|
-
interface ProjectionField {
|
|
24
|
-
readonly as: string;
|
|
25
|
-
readonly schema: z.ZodType;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Build an ad-hoc Zod object codec for a projection (a subset / rename of a table's columns). A full-row
|
|
29
|
-
* read decodes through the driver's `TableDef`; a *projection* isn't a full row, so this assembles a
|
|
30
|
-
* codec from exactly the selected columns' schemas.
|
|
31
|
-
*/
|
|
32
|
-
declare function projectionSchema(fields: readonly ProjectionField[]): z.ZodObject<Record<string, z.ZodType>>;
|
|
33
|
-
/** Decode raw projected rows (DB wire → app values) through the ad-hoc projection codec. */
|
|
34
|
-
declare function decodeProjection<T = Record<string, unknown>>(fields: readonly ProjectionField[], rows: readonly unknown[]): T[];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The neutral carrier a driver's field reference extends so the core projection inference can read its
|
|
38
|
-
* app-value type — the cross-driver contract for `@better-schemic/core/query`. Builders are driver-owned (each
|
|
39
|
-
* driver ships its own `FieldRef` with its own operators at `@better-schemic/<driver>/query`); the ONE thing
|
|
40
|
-
* core needs from any such ref is the *decoded app value* it stands for, carried here as a phantom.
|
|
41
|
-
*
|
|
42
|
-
* A driver's ref does: `interface SurrealRef<T> extends FieldRefBase<T> { eq(v: T): Expr; … }`.
|
|
43
|
-
* `Project` (./project) then reads `T` back out of any ref in a returned projection shape.
|
|
44
|
-
*/
|
|
45
|
-
declare const REF_VALUE: unique symbol;
|
|
46
|
-
interface FieldRefBase<T> {
|
|
47
|
-
/** Phantom — the decoded app-value type this ref projects to. Never present at runtime. */
|
|
48
|
-
readonly [REF_VALUE]: T;
|
|
49
|
-
}
|
|
50
|
-
/** The app-value type carried by a field ref (`never` if it isn't one). */
|
|
51
|
-
type RefValue<R> = R extends FieldRefBase<infer T> ? T : never;
|
|
52
|
-
/**
|
|
53
|
-
* Brand a driver's field-ref implementation with the neutral {@link FieldRefBase} carrier, so `Project`
|
|
54
|
-
* can read its value back out. The phantom symbol is module-private on purpose (drivers can't forge it),
|
|
55
|
-
* so this is the one sanctioned bridge — a runtime identity that saves every driver an `as unknown as`
|
|
56
|
-
* cast. `impl`'s type is inferred; `T` (the carried value type) defaults to `unknown` because refs are
|
|
57
|
-
* untyped at runtime — the per-field type is supplied by the driver's `Row` mapping. Usage:
|
|
58
|
-
* `return brandRef({ eq, neq, gte, … });`.
|
|
59
|
-
*/
|
|
60
|
-
declare function brandRef<I extends object, T = unknown>(impl: I): I & FieldRefBase<T>;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Infer the result element type of a `.return(row => P)` projection: replace every field ref in the
|
|
64
|
-
* returned shape `P` with the decoded app value it carries, recursing into nested objects and arrays.
|
|
65
|
-
* This is the type-level half of result typing (surqlize's `InheritableIntoType` analog); the runtime
|
|
66
|
-
* half is the projection codec (./codec). Generic over ANY driver ref — it only reads the
|
|
67
|
-
* `FieldRefBase` carrier, never a concrete driver type.
|
|
68
|
-
*
|
|
69
|
-
* ```ts
|
|
70
|
-
* type R = Project<{ name: Ref<string>; meta: { at: Ref<Date> } }>;
|
|
71
|
-
* // ^? { name: string; meta: { at: Date } }
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* Refs are matched BEFORE the generic object branch, so a ref (which is itself an object carrying
|
|
75
|
-
* operator methods) is unwrapped to its value rather than mapped field-by-field.
|
|
76
|
-
*/
|
|
77
|
-
type Project<P> = P extends FieldRefBase<infer T> ? T : P extends readonly (infer E)[] ? Project<E>[] : P extends object ? {
|
|
78
|
-
[K in keyof P]: Project<P[K]>;
|
|
79
|
-
} : P;
|
|
80
|
-
|
|
81
|
-
export { CallableFunctions, type FieldRefBase, type Project, type ProjectionField, type RefValue, brandRef, callFunction, decodeProjection, projectionSchema };
|
package/lib/query.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// src/query/call.ts
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
async function callFunction(callable, conn, name, args, returns) {
|
|
4
|
-
const raw = await callable.invoke(conn, name, args);
|
|
5
|
-
return z.decode(returns, raw);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// src/query/codec.ts
|
|
9
|
-
import { z as z2 } from "zod";
|
|
10
|
-
function projectionSchema(fields) {
|
|
11
|
-
const shape = {};
|
|
12
|
-
for (const f of fields) shape[f.as] = f.schema;
|
|
13
|
-
return z2.object(shape);
|
|
14
|
-
}
|
|
15
|
-
function decodeProjection(fields, rows) {
|
|
16
|
-
const schema = projectionSchema(fields);
|
|
17
|
-
return rows.map((r) => z2.decode(schema, r));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// src/query/ref.ts
|
|
21
|
-
function brandRef(impl) {
|
|
22
|
-
return impl;
|
|
23
|
-
}
|
|
24
|
-
export {
|
|
25
|
-
brandRef,
|
|
26
|
-
callFunction,
|
|
27
|
-
decodeProjection,
|
|
28
|
-
projectionSchema
|
|
29
|
-
};
|
|
30
|
-
//# sourceMappingURL=query.js.map
|
package/lib/query.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/query/call.ts","../src/query/codec.ts","../src/query/ref.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { CallableFunctions } from \"../driver/driver\";\n\n/**\n * Invoke a defined DB function via the driver's `callable` capability and **decode its result through the\n * function's `.returns(R)` schema** — the neutral half of the query layer's (B) `.call()`. A driver's\n * `defineFunction(args).returns(R).call(db, appArgs)` composes this: it encodes `appArgs` to wire (via\n * the arg schemas) and passes `R` here. Decode-by-default is the differentiator — results come back as\n * real `App` types (`Date`, `RecordId`, …), not wire. A `.raw()` path skips this and returns\n * `callable.invoke(...)` directly.\n */\nexport async function callFunction<S extends z.ZodType>(\n callable: CallableFunctions,\n conn: unknown,\n name: string,\n args: Record<string, unknown>,\n returns: S,\n): Promise<z.output<S>> {\n const raw = await callable.invoke(conn, name, args);\n return z.decode(returns, raw as never);\n}\n","import { z } from \"zod\";\n\n/**\n * One selected projection column: the output key (`as`) plus the source Zod schema to decode it through.\n * The schema may itself be a `z.object(...)` for a nested projection — the driver's builder assembles the\n * tree; core just decodes it.\n */\nexport interface ProjectionField {\n readonly as: string;\n readonly schema: z.ZodType;\n}\n\n/**\n * Build an ad-hoc Zod object codec for a projection (a subset / rename of a table's columns). A full-row\n * read decodes through the driver's `TableDef`; a *projection* isn't a full row, so this assembles a\n * codec from exactly the selected columns' schemas.\n */\nexport function projectionSchema(\n fields: readonly ProjectionField[],\n): z.ZodObject<Record<string, z.ZodType>> {\n const shape: Record<string, z.ZodType> = {};\n for (const f of fields) shape[f.as] = f.schema;\n return z.object(shape);\n}\n\n/** Decode raw projected rows (DB wire → app values) through the ad-hoc projection codec. */\nexport function decodeProjection<T = Record<string, unknown>>(\n fields: readonly ProjectionField[],\n rows: readonly unknown[],\n): T[] {\n const schema = projectionSchema(fields);\n return rows.map((r) => z.decode(schema, r as never) as T);\n}\n","/**\n * The neutral carrier a driver's field reference extends so the core projection inference can read its\n * app-value type — the cross-driver contract for `@better-schemic/core/query`. Builders are driver-owned (each\n * driver ships its own `FieldRef` with its own operators at `@better-schemic/<driver>/query`); the ONE thing\n * core needs from any such ref is the *decoded app value* it stands for, carried here as a phantom.\n *\n * A driver's ref does: `interface SurrealRef<T> extends FieldRefBase<T> { eq(v: T): Expr; … }`.\n * `Project` (./project) then reads `T` back out of any ref in a returned projection shape.\n */\ndeclare const REF_VALUE: unique symbol;\n\nexport interface FieldRefBase<T> {\n /** Phantom — the decoded app-value type this ref projects to. Never present at runtime. */\n readonly [REF_VALUE]: T;\n}\n\n/** The app-value type carried by a field ref (`never` if it isn't one). */\nexport type RefValue<R> = R extends FieldRefBase<infer T> ? T : never;\n\n/**\n * Brand a driver's field-ref implementation with the neutral {@link FieldRefBase} carrier, so `Project`\n * can read its value back out. The phantom symbol is module-private on purpose (drivers can't forge it),\n * so this is the one sanctioned bridge — a runtime identity that saves every driver an `as unknown as`\n * cast. `impl`'s type is inferred; `T` (the carried value type) defaults to `unknown` because refs are\n * untyped at runtime — the per-field type is supplied by the driver's `Row` mapping. Usage:\n * `return brandRef({ eq, neq, gte, … });`.\n */\nexport function brandRef<I extends object, T = unknown>(\n impl: I,\n): I & FieldRefBase<T> {\n return impl as I & FieldRefBase<T>;\n}\n"],"mappings":";AAAA,SAAS,SAAS;AAWlB,eAAsB,aACpB,UACA,MACA,MACA,MACA,SACsB;AACtB,QAAM,MAAM,MAAM,SAAS,OAAO,MAAM,MAAM,IAAI;AAClD,SAAO,EAAE,OAAO,SAAS,GAAY;AACvC;;;ACpBA,SAAS,KAAAA,UAAS;AAiBX,SAAS,iBACd,QACwC;AACxC,QAAM,QAAmC,CAAC;AAC1C,aAAW,KAAK,OAAQ,OAAM,EAAE,EAAE,IAAI,EAAE;AACxC,SAAOA,GAAE,OAAO,KAAK;AACvB;AAGO,SAAS,iBACd,QACA,MACK;AACL,QAAM,SAAS,iBAAiB,MAAM;AACtC,SAAO,KAAK,IAAI,CAAC,MAAMA,GAAE,OAAO,QAAQ,CAAU,CAAM;AAC1D;;;ACLO,SAAS,SACd,MACqB;AACrB,SAAO;AACT;","names":["z"]}
|
package/src/query/call.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { CallableFunctions } from "../driver/driver";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Invoke a defined DB function via the driver's `callable` capability and **decode its result through the
|
|
6
|
-
* function's `.returns(R)` schema** — the neutral half of the query layer's (B) `.call()`. A driver's
|
|
7
|
-
* `defineFunction(args).returns(R).call(db, appArgs)` composes this: it encodes `appArgs` to wire (via
|
|
8
|
-
* the arg schemas) and passes `R` here. Decode-by-default is the differentiator — results come back as
|
|
9
|
-
* real `App` types (`Date`, `RecordId`, …), not wire. A `.raw()` path skips this and returns
|
|
10
|
-
* `callable.invoke(...)` directly.
|
|
11
|
-
*/
|
|
12
|
-
export async function callFunction<S extends z.ZodType>(
|
|
13
|
-
callable: CallableFunctions,
|
|
14
|
-
conn: unknown,
|
|
15
|
-
name: string,
|
|
16
|
-
args: Record<string, unknown>,
|
|
17
|
-
returns: S,
|
|
18
|
-
): Promise<z.output<S>> {
|
|
19
|
-
const raw = await callable.invoke(conn, name, args);
|
|
20
|
-
return z.decode(returns, raw as never);
|
|
21
|
-
}
|
package/src/query/codec.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* One selected projection column: the output key (`as`) plus the source Zod schema to decode it through.
|
|
5
|
-
* The schema may itself be a `z.object(...)` for a nested projection — the driver's builder assembles the
|
|
6
|
-
* tree; core just decodes it.
|
|
7
|
-
*/
|
|
8
|
-
export interface ProjectionField {
|
|
9
|
-
readonly as: string;
|
|
10
|
-
readonly schema: z.ZodType;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Build an ad-hoc Zod object codec for a projection (a subset / rename of a table's columns). A full-row
|
|
15
|
-
* read decodes through the driver's `TableDef`; a *projection* isn't a full row, so this assembles a
|
|
16
|
-
* codec from exactly the selected columns' schemas.
|
|
17
|
-
*/
|
|
18
|
-
export function projectionSchema(
|
|
19
|
-
fields: readonly ProjectionField[],
|
|
20
|
-
): z.ZodObject<Record<string, z.ZodType>> {
|
|
21
|
-
const shape: Record<string, z.ZodType> = {};
|
|
22
|
-
for (const f of fields) shape[f.as] = f.schema;
|
|
23
|
-
return z.object(shape);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Decode raw projected rows (DB wire → app values) through the ad-hoc projection codec. */
|
|
27
|
-
export function decodeProjection<T = Record<string, unknown>>(
|
|
28
|
-
fields: readonly ProjectionField[],
|
|
29
|
-
rows: readonly unknown[],
|
|
30
|
-
): T[] {
|
|
31
|
-
const schema = projectionSchema(fields);
|
|
32
|
-
return rows.map((r) => z.decode(schema, r as never) as T);
|
|
33
|
-
}
|
package/src/query/index.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@better-schemic/core/query` — the dialect-neutral query toolkit. NOT a query builder: builders are
|
|
3
|
-
* driver-owned (each driver ships its own at `@better-schemic/<driver>/query`). Core owns the *machinery* every
|
|
4
|
-
* driver builder reuses so the hard parts aren't reimplemented per driver:
|
|
5
|
-
*
|
|
6
|
-
* - `FieldRefBase<T>` — the carrier a driver's field ref extends, so result inference is cross-driver.
|
|
7
|
-
* - `Project<P>` — projection result-type inference (`.return(row => P)` → the decoded shape).
|
|
8
|
-
* - the projection codec — decode a projected (subset/renamed) row at runtime.
|
|
9
|
-
* - `callFunction` — invoke a defined DB function via the `callable` capability + decode through
|
|
10
|
-
* `.returns(R)` (the neutral half of the (B) `.call()`).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Re-exported so a driver builds its `.call()` from one import (`@better-schemic/core/query`).
|
|
14
|
-
export type { CallableFunctions } from "../driver/driver";
|
|
15
|
-
export { callFunction } from "./call";
|
|
16
|
-
export {
|
|
17
|
-
decodeProjection,
|
|
18
|
-
type ProjectionField,
|
|
19
|
-
projectionSchema,
|
|
20
|
-
} from "./codec";
|
|
21
|
-
export type { Project } from "./project";
|
|
22
|
-
export { brandRef, type FieldRefBase, type RefValue } from "./ref";
|
package/src/query/project.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { FieldRefBase } from "./ref";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Infer the result element type of a `.return(row => P)` projection: replace every field ref in the
|
|
5
|
-
* returned shape `P` with the decoded app value it carries, recursing into nested objects and arrays.
|
|
6
|
-
* This is the type-level half of result typing (surqlize's `InheritableIntoType` analog); the runtime
|
|
7
|
-
* half is the projection codec (./codec). Generic over ANY driver ref — it only reads the
|
|
8
|
-
* `FieldRefBase` carrier, never a concrete driver type.
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* type R = Project<{ name: Ref<string>; meta: { at: Ref<Date> } }>;
|
|
12
|
-
* // ^? { name: string; meta: { at: Date } }
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* Refs are matched BEFORE the generic object branch, so a ref (which is itself an object carrying
|
|
16
|
-
* operator methods) is unwrapped to its value rather than mapped field-by-field.
|
|
17
|
-
*/
|
|
18
|
-
export type Project<P> =
|
|
19
|
-
P extends FieldRefBase<infer T>
|
|
20
|
-
? T
|
|
21
|
-
: P extends readonly (infer E)[]
|
|
22
|
-
? Project<E>[]
|
|
23
|
-
: P extends object
|
|
24
|
-
? { [K in keyof P]: Project<P[K]> }
|
|
25
|
-
: P;
|
package/src/query/ref.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The neutral carrier a driver's field reference extends so the core projection inference can read its
|
|
3
|
-
* app-value type — the cross-driver contract for `@better-schemic/core/query`. Builders are driver-owned (each
|
|
4
|
-
* driver ships its own `FieldRef` with its own operators at `@better-schemic/<driver>/query`); the ONE thing
|
|
5
|
-
* core needs from any such ref is the *decoded app value* it stands for, carried here as a phantom.
|
|
6
|
-
*
|
|
7
|
-
* A driver's ref does: `interface SurrealRef<T> extends FieldRefBase<T> { eq(v: T): Expr; … }`.
|
|
8
|
-
* `Project` (./project) then reads `T` back out of any ref in a returned projection shape.
|
|
9
|
-
*/
|
|
10
|
-
declare const REF_VALUE: unique symbol;
|
|
11
|
-
|
|
12
|
-
export interface FieldRefBase<T> {
|
|
13
|
-
/** Phantom — the decoded app-value type this ref projects to. Never present at runtime. */
|
|
14
|
-
readonly [REF_VALUE]: T;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** The app-value type carried by a field ref (`never` if it isn't one). */
|
|
18
|
-
export type RefValue<R> = R extends FieldRefBase<infer T> ? T : never;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Brand a driver's field-ref implementation with the neutral {@link FieldRefBase} carrier, so `Project`
|
|
22
|
-
* can read its value back out. The phantom symbol is module-private on purpose (drivers can't forge it),
|
|
23
|
-
* so this is the one sanctioned bridge — a runtime identity that saves every driver an `as unknown as`
|
|
24
|
-
* cast. `impl`'s type is inferred; `T` (the carried value type) defaults to `unknown` because refs are
|
|
25
|
-
* untyped at runtime — the per-field type is supplied by the driver's `Row` mapping. Usage:
|
|
26
|
-
* `return brandRef({ eq, neq, gte, … });`.
|
|
27
|
-
*/
|
|
28
|
-
export function brandRef<I extends object, T = unknown>(
|
|
29
|
-
impl: I,
|
|
30
|
-
): I & FieldRefBase<T> {
|
|
31
|
-
return impl as I & FieldRefBase<T>;
|
|
32
|
-
}
|