import { pgTable, serial, text, integer, timestamp, jsonb, uniqueIndex } from 'drizzle-orm/pg-core'; /** * A design, and the editor state that goes with it. * * ── Why the document is `jsonb` and not columns ── * * A `Doc` belongs to the vendored editor, not to Officer. Upstream adds fields to it constantly — * `theme`, `platform` and `dynamicColor` all arrived after the first release — and every one of those * would be a migration here for a shape we do not own and cannot influence. * * So it is stored whole. Officer never reads inside it except to show a title, which means an upstream * field addition is a re-vendor rather than a schema change. The cost is honest: no querying by * anything inside a design, which nothing wants to do. * * ── Per user, and enforced here ── * * `userId` is on the row and every query filters by it. That is the multi-user answer being a property * of the schema rather than a filter somebody has to remember on each new endpoint — the same reasoning * `service_connections` uses, and the mistake `dashboards.id` made by being a global primary key fed by * `slugify(name)`, so that two members naming a thing the same way collide. */ export const m3eCanvasDocuments = pgTable( 'm3e_canvas_documents', { id: serial('id').primaryKey(), userId: integer('user_id').notNull(), /** Lifted out of the document so a list does not have to parse every design to draw itself. */ title: text('title').notNull().default('Untitled'), /** The whole upstream `Doc`. Officer does not look inside it. */ doc: jsonb('doc').notNull(), /** * Panel widths, favourites, which language — upstream's `UI_KEY` state. * * Separate from the document because it is not the work: losing it costs a person their panel * sizes, and losing a design costs them the design. Different things deserve different care. */ ui: jsonb('ui'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => ({ // Composite via uniqueIndex, never `unique().on(...)` — drizzle-kit mis-diffs named composite // unique CONSTRAINTS and re-creates them on every push. `databases/CLAUDE.md`, "Composite keys". byUser: uniqueIndex('m3e_canvas_documents_user_title').on(table.userId, table.title), }), ); export type M3eCanvasDocument = typeof m3eCanvasDocuments.$inferSelect;