Files
website/src/lib/server/db/schema/lists.ts
Stan Runge 64fb8df262 db: Drizzle schemas on domain Postgres schemas, baseline migration, reorg SQL
- pgSchema tables for auth, money, house, ref, games, cs, animaldex, site, meta
  (lists excluded via schemaFilter; runtime list tables are managed by meta.*)
- drizzle/ baseline matches the reorged DB exactly (catalog diff = 0 on rehearsal)
- db/reorg: one-time SQL that moves the flat public schema (rehearsed on a prod copy)
- scripts/db-migrate.ts: migrator with --init to record the baseline and a guard
  against running the baseline on a non-empty, unrecorded database
2026-10-23 22:03:42 +00:00

93 lines
2.9 KiB
TypeScript

import {
boolean,
check,
index,
integer,
jsonb,
text,
timestamp,
unique
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { metaSchema } from './schemas';
// Registry of GUI-visible tables. Runtime lists live in schema `lists` (one real
// table per list, created through meta.create_list()); that schema is excluded
// from drizzle-kit via schemaFilter. Structure truth is pg_catalog; these rows are
// presentation. See src/lib/server/lists.ts for the list engine.
const ts = (name: string) => timestamp(name, { withTimezone: true });
export const metaTables = metaSchema.table(
'tables',
{
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
schemaName: text('schema_name').notNull(),
tableName: text('table_name').notNull(),
slug: text('slug').notNull().unique('tables_slug_key'),
displayName: text('display_name').notNull(),
description: text('description'),
icon: text('icon'),
pictureUrl: text('picture_url'),
kind: text('kind', { enum: ['list', 'core', 'ref'] }).notNull(),
editable: boolean('editable').notNull().default(true),
hasDone: boolean('has_done').notNull().default(false),
defaultSort: text('default_sort'),
sortOrder: integer('sort_order').notNull().default(0),
archivedAt: ts('archived_at'),
createdAt: ts('created_at').notNull().defaultNow(),
updatedAt: ts('updated_at').notNull().defaultNow()
},
(t) => [
unique('tables_schema_name_table_name_key').on(t.schemaName, t.tableName),
check('tables_slug_check', sql`slug ~ '^[a-z0-9][a-z0-9-]{0,62}$'`),
check('tables_kind_check', sql`kind IN ('list','core','ref')`)
]
);
export const uiTypes = [
'text',
'longtext',
'url',
'image',
'number',
'cents',
'date',
'datetime',
'bool',
'select',
'multiselect',
'fk',
'json'
] as const;
export type UiType = (typeof uiTypes)[number];
export const metaColumns = metaSchema.table(
'columns',
{
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
tableId: integer('table_id')
.notNull()
.references(() => metaTables.id, { onDelete: 'cascade' }),
columnName: text('column_name').notNull(),
label: text('label'),
uiType: text('ui_type', { enum: uiTypes }).notNull(),
options: jsonb('options').$type<{ values?: string[] }>().notNull().default({}),
position: integer('position').notNull().default(0),
hidden: boolean('hidden').notNull().default(false),
helpText: text('help_text'),
createdAt: ts('created_at').notNull().defaultNow(),
updatedAt: ts('updated_at').notNull().defaultNow()
},
(t) => [
unique('columns_table_id_column_name_key').on(t.tableId, t.columnName),
index('columns_table_id_index').on(t.tableId),
check(
'columns_ui_type_check',
sql`ui_type IN ('text','longtext','url','image','number','cents','date','datetime','bool','select','multiselect','fk','json')`
)
]
);
export type MetaTable = typeof metaTables.$inferSelect;
export type MetaColumn = typeof metaColumns.$inferSelect;