the extraction kept atelier's own postgres database, because the brief said to when the tables already had rows. the owner reversed it, and the reason is not tidiness: a plugin that behaves differently from every other plugin is a plugin nobody can reason about. the separate database meant a second connection string to set before install, a second thing to back up, a second thing to get wrong on a new machine, and failure modes no other plugin shares — none of it bought anything except avoiding a migration. four tables now: atelier_collections, atelier_collection_items, atelier_providers, atelier_generations. index and check constraint names carry the prefix too — those are global in postgres and are the one thing that would otherwise collide with another plugin. db/schema.ts re-exports the definitions rather than duplicating them: they stay beside the code that queries them in sidecar/db/, and that file exists to be found by gen-plugin-schemas. ATELIER_POSTGRES_URL is gone. db.ts reads OFFICERDEV_POSTGRES_URL ?? POSTGRES_URL, which the platform already sets. the standalone service's rows are NOT here and nothing depends on them being here — the old service is still running against the old database, untouched, and migrating is a separate watched step after this is proven on alpha.
55 lines
2.5 KiB
TypeScript
55 lines
2.5 KiB
TypeScript
import { pgTable, uuid, text, boolean, timestamp, index } from 'drizzle-orm/pg-core';
|
|
|
|
/**
|
|
* A configured connection to a model: adapter + model + credentials.
|
|
*
|
|
* ── Why this is a row and the adapter is not ──
|
|
*
|
|
* "OpenAI" is not a thing you can generate with. "OpenAI's gpt-image-1, with this key" is. The two
|
|
* differ in how often they change: an ADAPTER is code that learns a vendor's HTTP API and is written
|
|
* once; a PROVIDER is a decision — which model, whose key, is it on — and you make several of those
|
|
* per adapter and change them at runtime.
|
|
*
|
|
* So `openai + gpt-image-1` and `openai + dall-e-3` are two rows sharing one adapter and possibly one
|
|
* key. The previous design keyed configuration on the adapter name, which made that unsayable: there
|
|
* was exactly one OpenAI, and its model was a property of it rather than the point of it.
|
|
*
|
|
* Nothing here constrains `model` against the adapter. Vendors ship models faster than a list in this
|
|
* repo is updated, and refusing a new one on release day is a worse failure than passing it through
|
|
* and letting the vendor answer.
|
|
*/
|
|
export const providers = pgTable('atelier_providers',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
|
|
/** What a human calls it — "OpenAI · gpt-image-1". Unique, so it can be named in a request. */
|
|
name: text('name').notNull().unique(),
|
|
|
|
/** Which adapter drives it. Text, not an enum: adding an adapter must not be a schema change. */
|
|
adapter: text('adapter').notNull(),
|
|
|
|
model: text('model').notNull(),
|
|
|
|
/** AES-256-GCM via servers/crypto.ts. Never leaves the server and never appears in a response. */
|
|
apiKeyEncrypted: text('api_key_encrypted'),
|
|
|
|
enabled: boolean('enabled').notNull().default(true),
|
|
|
|
/**
|
|
* The one to use when a request names no provider and no model, or names a model nothing serves.
|
|
*
|
|
* A column rather than "the oldest row" because it is a decision the owner makes and should be
|
|
* able to change without deleting and recreating rows. Exactly one row should carry it; the
|
|
* queries below enforce that by clearing the others when one is set.
|
|
*/
|
|
isDefault: boolean('is_default').notNull().default(false),
|
|
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
// Resolving a request by model is the hot path: every generation does it once.
|
|
index('atelier_providers_model_idx').on(table.model, table.enabled),
|
|
],
|
|
);
|