r.unmanagedTable(meta, { reason })
Overview
Section titled “Overview”Use when a feature owns a table that does not fit the
r.entity(...) aggregate-lifecycle: no audit-trail, no
optimistic-version, no tenant-id base-columns. Typical case: a flat
read-side projection of an event-stream (delivery-attempts, job-run
logs).
import { defineUnmanagedTable } from "@cosmicdrift/kumiko-framework/db";import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
const deliveryAttemptsTableMeta = defineUnmanagedTable({ tableName: "read_delivery_attempts", columns: [ { name: "id", pgType: "uuid", notNull: true, primaryKey: true }, { name: "tenant_id", pgType: "uuid", notNull: true }, // ... ],});
createDeliveryFeature = defineFeature("delivery", (r) => { r.unmanagedTable(deliveryAttemptsTableMeta, { reason: "read-side log projection of DELIVERY_ATTEMPT events — flat shape, no aggregate lifecycle.", }); // ...});Why declare via r.unmanagedTable instead of top-level export?
Section titled “Why declare via r.unmanagedTable instead of top-level export?”Because the app-author then needs zero awareness of which features ship
unmanaged-tables. The kumiko schema generate CLI iterates the
composed feature-set, collects every feature.unmanagedTables entry,
and emits the migration — without per-app hand-lists.
Before (manual push in kumiko/schema.ts):
metas.push(deliveryAttemptsTableMeta); // ← framework-internal detail leaks into appmetas.push(jobRunLogsTableMeta);After:
for (const entry of Object.values(feature.unmanagedTables ?? {})) { metas.push(entry.meta); // ← features self-declare}reason is required
Section titled “reason is required”The reason string justifies the bypass at the declaration site and
shows up in audit/ops UIs that list all unmanaged tables. Reviewers can
judge legitimacy without spelunking through history. Empty / whitespace
reason throws at registration time — if you can’t write a reason,
declare data via r.entity() instead.
Cousin: r.rawTable
Section titled “Cousin: r.rawTable”r.rawTable(name, pgTable, { reason }) is the legacy Drizzle-PgTable
variant. Both APIs coexist until Drizzle is fully cut out of the
framework; after that the two will likely merge. Pick r.unmanagedTable
for new code (uses EntityTableMeta, which the migrate-runner consumes
directly).
Cross-feature uniqueness
Section titled “Cross-feature uniqueness”Two features cannot register the same physical tableName —
createRegistry throws at boot with Unmanaged-table "..." registered by both feature "..." and "...". Pick a feature-prefixed tableName to disambiguate.