@nest-admin/nestjs 0.11.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/drizzle.ts","../../core/src/auth/account.ts","../../core/src/metadata/created-field.ts","../../core/src/metadata/display-field.ts","../../core/src/metadata/relation-shape.ts","../../core/src/config/resources.ts","../../core/src/config/overrides.ts","../../core/src/errors/errors.ts","../../drizzle/src/adapter.ts","../../drizzle/src/errors/constraints.ts","../../drizzle/src/metadata/to-metadata.ts","../../drizzle/src/query/build.ts","../../drizzle/src/schema/introspect.ts"],"sourcesContent":["/**\n * `@nest-admin/nestjs/drizzle` - the Drizzle adapter subpath.\n *\n * Beside `./prisma` and arranged the same way: an application that never\n * touches Drizzle never loads Drizzle code, and neither subpath knows the other\n * exists. This is the arrangement `./prisma` was built to allow, and this is\n * the first thing to actually use it.\n */\n\nexport * from '@nest-admin/drizzle'\n","/**\n * Where admin accounts live, as a contract.\n *\n * ## Why this exists at all\n *\n * Until 0.9.0 the answer to \"who may open the admin?\" was always the host\n * application's: it already had sessions, and `AdminAuth` asked it one\n * question. That is still right for a team that has an identity system, and\n * nothing about it changes.\n *\n * It is a wall for everyone else. An application with no login of its own had\n * to write a password hash, a session cookie and a form before the admin could\n * go anywhere near production - which is a strange thing to ask of a package\n * whose whole claim is that you do not build an admin.\n *\n * ## Why it is a contract rather than a table\n *\n * The same reason `OrmAdapter` is. An admin whose accounts can only live in\n * Prisma has learned about Prisma, and the second ORM would find out the hard\n * way. Everything here is plain data and promises; nothing knows what a\n * database is.\n *\n * ## These accounts are not the application's users\n *\n * Deliberately, and this is the point most worth getting right. The people who\n * administer a system are usually not rows in the table they administer, and\n * conflating the two means a customer record with a password that opens the\n * admin. The store is separate storage - a different model, or a different\n * database entirely - and the admin never reads or writes the application's\n * own users to decide who may sign in.\n */\n\n/** One account that may sign in to the admin. */\nexport interface AdminAccount {\n readonly id: string\n\n /**\n * What is typed into the login form.\n *\n * Called `email` because that is what it almost always is, and a name people\n * recognise is worth more than one that covers a case nobody has. A store is\n * free to hold usernames in it.\n */\n readonly email: string\n\n /** Shown in the interface. Falls back to the email when absent. */\n readonly name?: string | undefined\n\n /**\n * The stored password hash, in whatever form the hasher produced.\n *\n * Read by the sign-in check and by nothing else. It must never reach a\n * response, and the account the interface is told about is a projection that\n * does not include it.\n */\n readonly passwordHash: string\n\n /**\n * Suspended without being deleted.\n *\n * Distinct from removing the row: an account that has done things is worth\n * keeping for the record, and \"cannot sign in\" is not the same fact as\n * \"never existed\".\n */\n readonly disabled?: boolean | undefined\n}\n\n/**\n * The account as the interface may see it.\n *\n * A separate type rather than a comment on {@link AdminAccount}, because \"do\n * not send the hash\" is a rule that gets forgotten and a type that cannot\n * carry it does not.\n */\nexport interface AdminAccountSummary {\n readonly id: string\n readonly email: string\n readonly name?: string | undefined\n}\n\n/** Everything but the hash. The only shape that may reach a client. */\nexport function summarise(account: AdminAccount): AdminAccountSummary {\n return {\n id: account.id,\n email: account.email,\n ...(account.name !== undefined ? { name: account.name } : {}),\n }\n}\n\n/**\n * How the admin reaches its accounts.\n *\n * Read-only by design. Creating and editing accounts is the application's\n * business: it owns the storage, it knows whether that is a migration, a seed\n * script or a form somewhere else, and an admin that could mint its own\n * administrators is an escalation waiting for its first mistake.\n */\nexport interface AdminAccountStore {\n /**\n * Find an account by what was typed into the login form.\n *\n * Matching is the store's decision, and it should be case-insensitive on the\n * local part in practice: someone who registered as `Ada@example.com` will\n * type `ada@example.com` eventually.\n *\n * Returns `null` when there is none. The caller must not behave observably\n * differently for `null` than for a wrong password - see the sign-in code.\n */\n findByEmail(email: string): Promise<AdminAccount | null>\n\n /**\n * Find an account by its id, for a request that arrives with a session.\n *\n * Called on every authenticated request, so it should be cheap. It is also\n * what makes a disabled or deleted account stop working immediately rather\n * than when its session happens to expire.\n */\n findById(id: string): Promise<AdminAccount | null>\n\n /**\n * How many accounts exist.\n *\n * Used once, at startup, to say so when the answer is zero - an admin nobody\n * can sign in to is a configuration mistake that otherwise announces itself\n * as a login form that rejects everything.\n */\n count(): Promise<number>\n\n /**\n * Note that an account signed in. Optional.\n *\n * A store that does not care about this can leave it out; the sign-in path\n * does not wait for it and a failure is logged rather than surfaced, because\n * \"your login worked but we could not write down that it did\" is not\n * something the person signing in can act on.\n */\n recordLogin?(id: string): Promise<void>\n\n /**\n * What this store reads, for diagnostics. Optional.\n *\n * A model name, a table, a directory - whatever names the storage in a way a\n * person would recognise. It exists so a startup check can say something\n * useful rather than something generic: an admin that exposes its own\n * account model as an editable resource is an escalation, and a warning that\n * cannot name the model is a warning nobody acts on.\n *\n * Never used to decide anything, and never sent to a client.\n */\n readonly describes?: string\n}\n","/**\n * Which field records when a row appeared.\n *\n * A dashboard's most useful question is \"how much of this arrived recently\",\n * and answering it needs one column: the timestamp a record was created. Every\n * conventional schema has one and none of them declare it as such.\n *\n * ## Why this is a guess\n *\n * The metadata cannot tell a creation timestamp from any other generated date.\n * Prisma reports both `@default(now())` and `@updatedAt` the same way - the\n * adapter collapses them into `isGenerated`, because for *editing* they are the\n * same thing: neither is asked of a person. That is the right call for a form\n * and leaves nothing to distinguish them here.\n *\n * So this reads names, exactly as `displayFieldFor` does, and for the same\n * reason: the convention is near-universal and the alternative is a dashboard\n * that shows nothing until every application has annotated its schema.\n *\n * ## What it refuses to guess\n *\n * `updatedAt` and its variants are excluded rather than merely ranked lower. A\n * chart of \"records updated per day\" plotted under the heading \"new records\"\n * is worse than no chart: it is confidently wrong, and nothing about it looks\n * wrong. Where the convention is not followed, the answer is `undefined` and\n * the widget is simply not offered.\n */\nimport type { FieldMetadata, ModelMetadata } from './model.js'\n\n/**\n * Names that mean \"when this was created\", most conventional first.\n *\n * Snake case is included because a schema mapped onto an existing database\n * often keeps the column names it found there.\n */\nconst CREATED = ['createdAt', 'created_at', 'created', 'createdOn', 'insertedAt', 'inserted_at']\n\n/**\n * Names that must never be taken for it.\n *\n * Checked case-insensitively and by prefix, so `updatedAt`, `updated_at` and\n * `updateTime` are all excluded. Being wrong here is silent: a chart titled\n * \"new this month\" that is actually counting edits.\n */\nconst NOT_CREATED = ['updated', 'modified', 'deleted', 'archived', 'expires', 'expired']\n\nfunction isDate(field: FieldMetadata): boolean {\n return field.kind === 'datetime' && !field.isList && !field.relation\n}\n\nfunction excluded(name: string): boolean {\n const lower = name.toLowerCase()\n return NOT_CREATED.some((word) => lower.startsWith(word))\n}\n\n/**\n * The field that records when a record of this model was created, if the model\n * follows the convention.\n *\n * Order of preference:\n *\n * 1. a conventional name, most conventional first;\n * 2. the only remaining generated date on the model - a model with exactly\n * one date the database fills in has no ambiguity to resolve;\n * 3. nothing.\n *\n * The third is a real answer. A widget that cannot be built correctly is not\n * offered, which is a better outcome than one built on a column that means\n * something else.\n */\nexport function createdFieldFor(model: ModelMetadata): string | undefined {\n const dates = model.fields.filter(isDate)\n\n for (const conventional of CREATED) {\n const match = dates.find((field) => field.name.toLowerCase() === conventional.toLowerCase())\n if (match) return match.name\n }\n\n const generated = dates.filter((field) => field.isGenerated && !excluded(field.name))\n return generated.length === 1 ? generated[0]?.name : undefined\n}\n","/**\n * Which field names a record when it has to be referred to in one line.\n *\n * A relation is stored as an id, and an id is not something a person can read.\n * An admin that renders `cmtf50g710000mocjbygyfyfr` where it means \"Ada\n * Lovelace\" is technically correct and useless, so every model needs one field\n * that stands for the record.\n *\n * The rule lives in Core rather than in an adapter because it is a question\n * about a *model*, not about an ORM: the same reasoning applies whatever\n * produced the metadata. Two places need the answer and must agree on it - the\n * adapter, which selects the column when loading a relation, and the metadata\n * document, which tells the UI what to render.\n */\nimport type { FieldMetadata, ModelMetadata } from './model.js'\n\n/**\n * Conventional names for \"the human-readable one\", most specific first.\n *\n * Ordered by how strongly the name implies a label. `name` and `title` are\n * unambiguous; `email` is a real identifier people recognise; `slug` is a\n * last resort among the conventional names because it is machine-shaped, but\n * it is still readable, which an id is not.\n */\nconst CONVENTIONAL = ['name', 'title', 'label', 'displayName', 'username', 'email', 'slug']\n\n/** Could this field stand in for the record in a list or a dropdown? */\nfunction isReadable(field: FieldMetadata): boolean {\n return (\n field.kind === 'string' &&\n !field.isList &&\n !field.relation &&\n // A generated string is a cuid or a uuid: readable characters, no meaning.\n !field.isGenerated\n )\n}\n\n/**\n * Pick the field that names a record of this model.\n *\n * Order of preference:\n *\n * 1. a conventional name (`name`, `title`, ...), most specific first;\n * 2. any other unique string - unique suggests it identifies the record;\n * 3. any other plain string;\n * 4. the first primary-key field.\n *\n * The last step is the honest fallback rather than a good answer: a model with\n * nothing but an id and a timestamp has no readable field, and showing the id\n * is better than showing nothing. Adapters and applications may override the\n * result; this is the default, not a rule.\n */\nexport function displayFieldFor(model: ModelMetadata): string {\n // A declared choice wins outright. The rule below is a guess, and the\n // application knows things the schema does not.\n if (model.displayField !== undefined) return model.displayField\n\n const readable = model.fields.filter(isReadable)\n\n for (const candidate of CONVENTIONAL) {\n const match = readable.find((field) => field.name === candidate)\n if (match) return match.name\n }\n\n const unique = readable.find((field) => field.isUnique && !field.isId)\n if (unique) return unique.name\n\n const plain = readable.find((field) => !field.isId)\n if (plain) return plain.name\n\n return model.primaryKey[0] ?? model.fields[0]?.name ?? 'id'\n}\n","/**\n * Reading a to-many relation from the parent's side.\n *\n * From `User`, both `posts` (one-to-many) and `Post.tags` (many-to-many) look\n * identical: a list of related records. What differs is where the link is\n * stored, and therefore what changing it means.\n *\n * one-to-many the child owns a column. Attaching a post to a user rewrites\n * `post.authorId`, which also *detaches it from whoever had it*.\n * Detaching means clearing that column, which is impossible if\n * it is required.\n *\n * many-to-many neither side owns a column; the link lives in a join table.\n * Attaching and detaching add and remove a row there and change\n * nothing about either record.\n *\n * An interface that offers the same buttons for both is lying about one of\n * them, so the difference is resolved here, once, from metadata both adapters\n * already produce.\n */\nimport type { FieldMetadata, ModelMetadata } from './model.js'\n\nexport type RelationShape = 'to-one' | 'one-to-many' | 'many-to-many'\n\n/**\n * The field on the target model that is the other half of this relation.\n *\n * Matched by relation name, which is the only reliable pairing: two relations\n * between the same models (`author` and `reviewer`, both to `User`) are\n * otherwise indistinguishable. Returns `undefined` when the name is absent -\n * an adapter that does not supply one - or when the target is not in `models`.\n */\nexport function inverseRelationField(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): FieldMetadata | undefined {\n const relation = field.relation\n if (!relation?.name) return undefined\n\n const target = models.find((model) => model.name === relation.targetModel)\n if (!target) return undefined\n\n return target.fields.find(\n (candidate) => candidate.relation?.name === relation.name && candidate !== field,\n )\n}\n\n/**\n * What kind of relation this is, from the side the field is declared on.\n *\n * A to-many whose other half is also a list is a many-to-many. Without an\n * inverse to look at - no relation name, or a target outside this admin - a\n * to-many is reported as `one-to-many`, the more conservative answer: it is the\n * shape whose write operations have preconditions, so treating a many-to-many\n * as one costs a refused detach rather than a corrupted record.\n */\nexport function relationShape(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): RelationShape | undefined {\n const relation = field.relation\n if (!relation) return undefined\n if (relation.cardinality === 'one') return 'to-one'\n\n const inverse = inverseRelationField(field, models)\n return inverse?.relation?.cardinality === 'many' ? 'many-to-many' : 'one-to-many'\n}\n\n/**\n * Why a one-to-many relation cannot be detached, or `undefined` if it can.\n *\n * Detaching means clearing the child's foreign key, and a required column\n * cannot be cleared. The database would refuse it; saying so first is the\n * difference between \"you cannot remove this here, delete the record instead\"\n * and a constraint violation.\n */\nexport function detachBlockedReason(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): string | undefined {\n if (relationShape(field, models) !== 'one-to-many') return undefined\n\n const inverse = inverseRelationField(field, models)\n if (!inverse?.isRequired) return undefined\n\n const target = field.relation?.targetModel ?? 'the related model'\n return (\n `${target}.${inverse.name} is required, so a ${target} record cannot exist ` +\n `without one. Delete the record, or point it at something else, instead of ` +\n `detaching it.`\n )\n}\n","/**\n * Which models the admin exposes at all.\n *\n * Distinct from resource authorization, and the two answer different questions.\n * A `ResourceSelection` is structural: it decides what the admin *is*, the same\n * for everyone, and a model outside it does not exist as far as the admin is\n * concerned. `AdminResourceAuth` is per-principal: the model exists, and this\n * caller may or may not act on it.\n *\n * That difference is visible in the response. An excluded model answers 404 -\n * there is no such resource - where a denied one answers 403.\n */\n\nexport interface ResourceSelection {\n /**\n * When present, only these models are exposed. Everything else is dropped,\n * including models added to the schema later - which is the point: an\n * allow-list does not quietly grow when someone edits the schema.\n */\n readonly include?: readonly string[]\n\n /**\n * Models removed from the selection, applied after `include`.\n *\n * The usual reason is a table that is not domain data: session stores,\n * migration bookkeeping, queue tables.\n */\n readonly exclude?: readonly string[]\n}\n\n/** Anything with a name - `ModelMetadata`, or a test's stand-in for one. */\ninterface Named {\n readonly name: string\n}\n\n/**\n * Apply a selection, preserving the adapter's own order.\n *\n * Order comes from the schema rather than from `include`, so that adding a name\n * to the list does not silently reshuffle the admin. Deciding the order models\n * appear in is a separate feature and is not this option's job.\n */\nexport function selectModels<T extends Named>(\n models: readonly T[],\n selection?: ResourceSelection,\n): readonly T[] {\n if (!selection) return models\n\n const included = selection.include ? new Set(selection.include) : undefined\n const excluded = new Set(selection.exclude ?? [])\n\n return models.filter(\n (model) => (included === undefined || included.has(model.name)) && !excluded.has(model.name),\n )\n}\n\n/**\n * Names in the selection that no model answers to.\n *\n * Worth reporting rather than ignoring: a typo in `exclude` leaves the model\n * exposed, which is the opposite of what was asked for and is invisible until\n * someone finds the table in the admin. A typo in `include` is louder - the\n * model simply never appears - but has the same cause.\n */\nexport function unknownSelectionNames<T extends Named>(\n models: readonly T[],\n selection?: ResourceSelection,\n): readonly string[] {\n if (!selection) return []\n\n const known = new Set(models.map((model) => model.name))\n const referenced = [...(selection.include ?? []), ...(selection.exclude ?? [])]\n\n return [...new Set(referenced.filter((name) => !known.has(name)))]\n}\n","/**\n * Per-model and per-field configuration.\n *\n * The schema says what a model *is*; this says how the admin should treat it.\n * Two different questions, so they are two different inputs - a column being a\n * string is a fact about the database, and that column being a password is a\n * fact about the application.\n *\n * The overrides divide into two kinds, and the difference matters:\n *\n * behaviour `hidden`, `readOnly`, `displayField`. Enforced. A hidden\n * field is removed from the metadata every layer reads, so it\n * cannot be filtered, sorted, written or returned - see\n * `applyOverrides`.\n *\n * behaviour `writeOnly` too - accepted on a write and stripped from\n * every read.\n *\n * presentation `label`, `widget`, `order`. Passed to the client, which is\n * free to ignore them. Nothing depends on them being honoured.\n *\n * Anything in the first group that were only presentation would be a security\n * hole with a reassuring name.\n */\nimport type { FieldMetadata, ModelMetadata } from '../metadata/model.js'\n\n/**\n * How a field should be edited, when its type does not say enough.\n *\n * A `string` column may be a sentence, a password, an address or a colour, and\n * the schema cannot tell them apart. Deliberately a closed list: a client has\n * to know how to render each one, so an open string would mean silently\n * falling back to a plain input and no way to notice.\n */\nexport type FieldWidget = 'textarea' | 'password' | 'email' | 'url' | 'color' | 'json'\n\nexport interface FieldOverride {\n /**\n * Remove the field from the admin entirely.\n *\n * **Enforced, not cosmetic.** The field is dropped from the metadata before\n * anything reads it, so it is absent from the schema document, rejected in\n * filters and sorts, refused in writes, and stripped from every response.\n * A password hash is the reason this exists.\n */\n readonly hidden?: boolean\n\n /** Show the field, refuse to write it. Generated columns are already this. */\n readonly readOnly?: boolean\n\n /**\n * Write the field, never read it back. The mirror of `readOnly`.\n *\n * **Enforced, not cosmetic.** The column is left out of the query the adapter\n * makes and out of the projection applied to the result, so it is absent from\n * a list, from a detail page and from the record a write returns - while\n * still being accepted in the write itself.\n *\n * A password is what this is for. `hidden` is the wrong tool: it refuses the\n * field in both directions, so a hidden password column can never be set.\n */\n readonly writeOnly?: boolean\n\n /** What to call it, when the column name is not what people call the thing. */\n readonly label?: string\n\n /** How to edit it. See {@link FieldWidget}. */\n readonly widget?: FieldWidget\n\n /** Where it sits among the others. Lower comes first; unset comes last. */\n readonly order?: number\n}\n\n/**\n * Icons a model may be given in the navigation.\n *\n * A closed list, for the same reason `FieldWidget` is one: the interface has to\n * know how to draw each name, so an open string would mean silently rendering\n * nothing and no way to notice. It is also a bundle decision - the icon set has\n * about fifteen hundred entries, and only the ones named here are shipped.\n *\n * Chosen to cover what an admin's resources usually are rather than to be\n * complete. A model with no icon is drawn without one, which is the default and\n * is not a lesser state: identical icons down a column are decoration, and the\n * navigation reads better with none than with thirty of the same shape.\n */\nexport type ModelIcon =\n | 'users'\n | 'user'\n | 'building'\n | 'box'\n | 'package'\n | 'tag'\n | 'shopping-cart'\n | 'credit-card'\n | 'receipt'\n | 'file-text'\n | 'folder'\n | 'image'\n | 'calendar'\n | 'clock'\n | 'mail'\n | 'message-square'\n | 'bell'\n | 'star'\n | 'map-pin'\n | 'globe'\n | 'settings'\n | 'key'\n | 'shield'\n | 'database'\n | 'table'\n | 'layers'\n | 'list'\n | 'chart-bar'\n | 'activity'\n | 'truck'\n | 'gift'\n | 'bookmark'\n | 'link'\n\nexport interface ModelOverride {\n /** What to call the model. */\n readonly label?: string\n\n /**\n * Which icon to show beside it in the navigation.\n *\n * Presentational: the client may ignore it, and nothing depends on it being\n * honoured. See {@link ModelIcon} for why the list is closed.\n */\n readonly icon?: ModelIcon\n\n /**\n * Which field names a record, overriding what would be detected.\n *\n * The detection rule guesses well on conventional schemas and has no way to\n * know that a `code` column is the one people recognise.\n */\n readonly displayField?: string\n\n /** Where the model sits in the resource list. Lower first; unset last. */\n readonly order?: number\n\n readonly fields?: Readonly<Record<string, FieldOverride>>\n}\n\nexport type ModelOverrides = Readonly<Record<string, ModelOverride>>\n\n/** The override for one field, if the application declared one. */\nexport function fieldOverride(\n overrides: ModelOverrides | undefined,\n model: string,\n field: string,\n): FieldOverride | undefined {\n return overrides?.[model]?.fields?.[field]\n}\n\n/** Is this field one the application refuses to write? */\nexport function isReadOnly(\n overrides: ModelOverrides | undefined,\n model: string,\n field: FieldMetadata,\n): boolean {\n // Generated values are read-only whatever the configuration says: they are\n // the database's to produce, and were never writable.\n return field.isGenerated || fieldOverride(overrides, model, field.name)?.readOnly === true\n}\n\n/**\n * The models as the admin should see them.\n *\n * Hidden fields are **removed** rather than marked, so that every layer\n * downstream is correct without knowing this option exists. The query parser\n * rejects a filter on a field it cannot find; the metadata mapper cannot\n * describe one; write validation refuses one. A flag would have needed each of\n * those to remember to check it.\n *\n * A declared `displayField` is carried through the same way, so the adapter and\n * the metadata document agree on it without either consulting the config.\n */\nexport function applyOverrides(\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): readonly ModelMetadata[] {\n if (!overrides) return models\n\n return models.map((model) => {\n const override = overrides[model.name]\n if (!override) return model\n\n const hidden = new Set(\n Object.entries(override.fields ?? {})\n .filter(([, field]) => field.hidden === true)\n .map(([name]) => name),\n )\n\n const writeOnly = new Set(\n Object.entries(override.fields ?? {})\n .filter(([, field]) => field.writeOnly === true)\n .map(([name]) => name),\n )\n\n const kept = hidden.size === 0 ? model.fields : model.fields.filter((f) => !hidden.has(f.name))\n\n return {\n ...model,\n ...(override.displayField !== undefined ? { displayField: override.displayField } : {}),\n // Carried onto the metadata rather than looked up again later, so\n // everything downstream - the field scope, the projection, the DTO -\n // reads one flag instead of each re-deriving it from the configuration.\n fields:\n writeOnly.size === 0\n ? kept\n : kept.map((field) =>\n writeOnly.has(field.name) ? { ...field, writeOnly: true } : field,\n ),\n }\n })\n}\n\n/**\n * Names in the configuration that no model or field answers to.\n *\n * Reported so a typo fails at startup. The cost of ignoring one is not\n * symmetrical: a mistyped `label` is invisible and harmless, but a mistyped\n * `passwordHash` leaves the real column exposed while the configuration looks\n * like it is protecting it.\n */\nexport function unknownOverrideNames(\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): readonly string[] {\n if (!overrides) return []\n\n const unknown: string[] = []\n\n for (const [modelName, override] of Object.entries(overrides)) {\n const model = models.find((candidate) => candidate.name === modelName)\n if (!model) {\n unknown.push(modelName)\n continue\n }\n\n const names = new Set(model.fields.map((field) => field.name))\n\n if (override.displayField !== undefined && !names.has(override.displayField)) {\n unknown.push(`${modelName}.${override.displayField}`)\n }\n\n for (const fieldName of Object.keys(override.fields ?? {})) {\n if (!names.has(fieldName)) unknown.push(`${modelName}.${fieldName}`)\n }\n }\n\n return unknown\n}\n\n/**\n * Hidden fields that make creating a record impossible.\n *\n * A column that is required, is not produced by the database, and has no\n * default is a value the *caller* must supply. Hiding it removes the only way\n * to supply it, so every create fails - and fails in the database, as a\n * constraint violation the admin can only report as an internal error.\n *\n * Reported at startup for that reason: the configuration is self-defeating, and\n * the symptom otherwise appears far from the cause.\n */\nexport function unwritableHiddenFields(\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): readonly string[] {\n if (!overrides) return []\n\n const blocked: string[] = []\n\n for (const [modelName, override] of Object.entries(overrides)) {\n const model = models.find((candidate) => candidate.name === modelName)\n if (!model) continue\n\n for (const [fieldName, field] of Object.entries(override.fields ?? {})) {\n if (field.hidden !== true) continue\n\n const declared = model.fields.find((candidate) => candidate.name === fieldName)\n if (!declared) continue\n\n if (declared.isRequired && !declared.isGenerated && declared.defaultValue === undefined) {\n blocked.push(`${modelName}.${fieldName}`)\n }\n }\n }\n\n return blocked\n}\n","/**\n * Framework error vocabulary.\n *\n * Deliberately small. These exist so that adapters raise ORM-independent\n * errors and the transport layer can map them to status codes without knowing\n * which ORM produced them. Resist growing this taxonomy - add a new type only\n * when a caller genuinely needs to branch on it.\n *\n * ## Why these are not identified with `instanceof`\n *\n * A published bundle can contain more than one copy of this module. The\n * package ships two CommonJS entrypoints and each inlines its own copy of\n * Core, so an error thrown inside the Prisma adapter is an instance of a\n * *different* `FieldNotFoundError` class than the one the exception filter\n * holds. `instanceof` compares class identity, so it answered `false` and\n * every adapter-raised error was mapped to a generic 500 - a caller who\n * mistyped a sort field got \"internal error\" instead of \"unknown field\".\n *\n * That was invisible to this repository's own tests, which resolve Core to a\n * single source module, and only appeared when the built package was installed\n * and run. So errors are identified by *value* rather than identity: a\n * `Symbol.for` brand, which duplicate copies agree on by definition, plus a\n * stable `kind` string. Neither depends on which copy created the object.\n *\n * `scripts/verify-packed-consumer.mjs` asserts the arrangement every release:\n * one shared copy in ESM, one per entrypoint in CJS. If that ever changes, the\n * count changes there first.\n *\n * @experimental Draft contract. Expected to change during MVP implementation.\n */\n\n/**\n * Cross-copy brand.\n *\n * `Symbol.for` resolves through the global symbol registry, so two copies of\n * this file agree on the key where two `Symbol()` calls would not.\n */\nconst BRAND = Symbol.for('nest-admin.error')\n\n/**\n * Stable discriminator for each error type.\n *\n * A declared string rather than the class, so it survives duplicate bundles,\n * and rather than `name`, so it survives minification.\n */\nexport type AdminErrorKind =\n | 'unauthorized'\n | 'forbidden'\n | 'model-not-found'\n | 'field-not-found'\n | 'record-not-found'\n | 'invalid-query'\n /** Application code refused the input. Its message reaches the client. */\n | 'validation'\n /** The database refused the write: unique, foreign key, or required. */\n | 'constraint'\n | 'adapter'\n /** A subclass that declared no kind of its own. Treated as internal. */\n | 'unknown'\n\n/**\n * Base error type. Every error raised by Nest Admin extends it so that the\n * NestJS integration can distinguish framework errors from application errors\n * without depending on concrete subclasses.\n */\nexport class NestAdminError extends Error {\n /**\n * Which error this is.\n *\n * Subclasses override it with a literal. The base value covers anything that\n * extends this class without declaring one - the Prisma schema errors, for\n * instance - which the transport layer treats as internal.\n */\n readonly kind: AdminErrorKind = 'unknown'\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = new.target.name\n // Non-enumerable, so it can never reach a serialised response body.\n Object.defineProperty(this, BRAND, { value: true, enumerable: false })\n }\n}\n\n/**\n * Is this one of ours?\n *\n * Works across duplicate copies of this module, which `instanceof` does not.\n */\nexport function isNestAdminError(value: unknown): value is NestAdminError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[BRAND] === true\n )\n}\n\n/** The requested model is not part of the admin's resource set. */\nexport class ModelNotFoundError extends NestAdminError {\n override readonly kind = 'model-not-found' as const\n\n constructor(\n readonly model: string,\n readonly availableModels: readonly string[] = [],\n ) {\n const known = availableModels.length > 0 ? ` Known models: ${availableModels.join(', ')}.` : ''\n super(`Unknown model \"${model}\".${known}`)\n }\n}\n\n/** A referenced field does not exist on the model, or cannot be used this way. */\nexport class FieldNotFoundError extends NestAdminError {\n override readonly kind = 'field-not-found' as const\n\n constructor(\n readonly model: string,\n readonly field: string,\n reason?: string,\n ) {\n super(`Unknown field \"${field}\" on model \"${model}\".${reason ? ` ${reason}` : ''}`)\n }\n}\n\n/** No record matched the given identifier. */\nexport class RecordNotFoundError extends NestAdminError {\n override readonly kind = 'record-not-found' as const\n\n constructor(\n readonly model: string,\n readonly id: unknown,\n ) {\n super(`No ${model} record found for id ${JSON.stringify(id)}.`)\n }\n}\n\n/**\n * The query is structurally invalid - an unusable operator/field combination,\n * a malformed value, or a request the adapter cannot express.\n */\nexport class InvalidQueryError extends NestAdminError {\n override readonly kind = 'invalid-query' as const\n}\n\n/**\n * The input is not acceptable, and the caller should be told why.\n *\n * Raised by application code - a hook rejecting a value, a rule the schema\n * cannot express - rather than by the framework. It exists because such a\n * refusal has to reach the person who typed the value, and the alternatives are\n * wrong in one direction or the other: `InvalidQueryError` claims the *query*\n * was malformed, and anything unrecognised becomes a generic 500 with the\n * message withheld.\n *\n * The message **is** forwarded to the client, which is the point of it and also\n * the responsibility that comes with it: whatever goes in is published.\n *\n * Naming the fields it is about is optional and worth doing. An interface that\n * knows which input was refused can say so next to that input, where the person\n * is looking, instead of in a banner above a form they then have to re-read.\n */\nexport class ValidationError extends NestAdminError {\n override readonly kind = 'validation' as const\n\n constructor(\n message: string,\n /** The inputs this is about. Empty when it is about the record as a whole. */\n readonly fields: readonly string[] = [],\n options?: { cause?: unknown },\n ) {\n super(message, options)\n }\n}\n\n/**\n * What the database refused, and about which fields.\n *\n * The distinction that matters is between a request that is *wrong* and a\n * database that is *broken*. A duplicate email, a foreign key pointing at\n * nothing, a missing required value - these are ordinary mistakes a person\n * makes in a form, and until they were told apart from a real failure the admin\n * answered every one of them with \"an internal error occurred\".\n *\n * The message is built here, from the constraint and the field names, rather\n * than taken from the ORM. An ORM's own text carries file paths, generated\n * query fragments and the values that collided, none of which should be\n * published - which is exactly why the generic 500 existed in the first place.\n */\nexport type ConstraintKind =\n /** A value that has to be unique is not. */\n | 'unique'\n /** A reference points at a record that is not there, or is still referenced. */\n | 'foreign-key'\n /** A value the database requires was not supplied. */\n | 'required'\n\nexport class ConstraintError extends NestAdminError {\n override readonly kind = 'constraint' as const\n\n constructor(\n readonly constraint: ConstraintKind,\n readonly model: string,\n /** The columns involved. Empty when the ORM did not say. */\n readonly fields: readonly string[] = [],\n ) {\n super(describeConstraint(constraint, model, fields))\n }\n}\n\n/**\n * A sentence for the person who filled in the form.\n *\n * Written from the field names alone, so it is safe to forward. Where the ORM\n * did not name a field the wording stays true rather than guessing at one.\n */\nfunction describeConstraint(\n constraint: ConstraintKind,\n model: string,\n fields: readonly string[],\n): string {\n const named = fields.length > 0 ? fields.join(', ') : undefined\n\n switch (constraint) {\n case 'unique':\n return named\n ? `Another ${model} already has this ${named}.`\n : `Another ${model} already has one of these values.`\n\n case 'foreign-key':\n return named\n ? `The ${named} does not refer to an existing record, or the record it refers to is still in use.`\n : `A reference on this ${model} does not point at an existing record, or is still in use.`\n\n case 'required':\n return named ? `${named} is required.` : `A required value on this ${model} is missing.`\n }\n}\n\n/**\n * The underlying ORM or database failed. Always wraps the original error as\n * `cause` so the real failure is never lost.\n */\nexport class AdapterError extends NestAdminError {\n override readonly kind = 'adapter' as const\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n }\n}\n\n/**\n * No authenticated identity was presented with the request.\n *\n * Raised by the host application's admin auth implementation, never by Core\n * itself - Core has no notion of a request, a header or a session, and must\n * not acquire one. It exists here so the transport layer can map it without\n * knowing which framework produced it.\n *\n * The default message is deliberately uninformative. An authentication failure\n * must not reveal whether a credential was absent, malformed, expired or\n * simply wrong.\n */\nexport class UnauthorizedError extends NestAdminError {\n override readonly kind = 'unauthorized' as const\n\n constructor(message = 'Authentication is required to access the admin API.') {\n super(message)\n }\n}\n\n/**\n * An identity was established, but it is not permitted to do this.\n *\n * Deliberately distinct from {@link UnauthorizedError}: collapsing the two\n * leaves a client unable to tell \"log in\" from \"you cannot do this\", and\n * pushes that guesswork into every consumer.\n */\nexport class ForbiddenError extends NestAdminError {\n override readonly kind = 'forbidden' as const\n\n constructor(message = 'You do not have permission to access the admin API.') {\n super(message)\n }\n}\n","/**\n * The Drizzle implementation of `OrmAdapter`.\n *\n * This package exists to answer a question the Prisma adapter cannot: is\n * `OrmAdapter` a contract, or is it a description of Prisma? Writing a second\n * implementation against a genuinely different ORM - a query builder with no\n * generated client, no DMMF and no normalised errors - is the only way to find\n * out before 1.0 freezes it.\n *\n * The answer, recorded here because it is the point of the package: Core needed\n * no changes. What differs is entirely inside this directory, and each\n * difference is documented where it is handled.\n *\n * ## What Drizzle does not give us, and what is done instead\n *\n * | Prisma | Drizzle | Handled in |\n * | --- | --- | --- |\n * | DMMF describing every model | the schema object itself | `schema/introspect.ts` |\n * | `P2xxx` codes with `meta` | the driver's own error | `errors/constraints.ts` |\n * | `mode: 'insensitive'` | `lower()` on both sides | `query/build.ts` |\n * | escaped `contains` | escaped by hand | `query/build.ts` |\n * | relations always named | named only if declared | `schema/introspect.ts` |\n *\n * ## Relations are not loaded with the record\n *\n * The Prisma adapter includes a to-one's target so a list can show a person's\n * name rather than their id. Drizzle can do the same with a join, but only with\n * the relational query API, which needs `relations()` declared - and this\n * adapter deliberately works without them. So a to-one arrives as its foreign\n * key, and the interface resolves the label through the relation picker, which\n * it already does for every relation it cannot see inline.\n */\nimport {\n AdapterError,\n FieldNotFoundError,\n isNestAdminError,\n ModelNotFoundError,\n RecordNotFoundError,\n type ListQuery,\n type ModelMetadata,\n type OrmAdapter,\n type Page,\n type RecordData,\n type RecordId,\n} from '@nest-admin/core'\nimport { and, count, eq, type SQL } from 'drizzle-orm'\n\nimport { toConstraintError } from './errors/constraints.js'\nimport { toModelMetadata } from './metadata/to-metadata.js'\nimport { buildOrderBy, buildWhere, resolvePagination } from './query/build.js'\nimport { readSchema, type DrizzleSchema, type DrizzleTable } from './schema/introspect.js'\n\n/** The parts of a Drizzle database this adapter uses. */\ninterface DrizzleDatabase {\n select(fields?: Record<string, unknown>): {\n from(table: object): QueryBuilder\n }\n insert(table: object): {\n values(data: Record<string, unknown>): { returning(): Promise<Record<string, unknown>[]> }\n }\n update(table: object): {\n set(data: Record<string, unknown>): {\n where(condition: SQL): { returning(): Promise<Record<string, unknown>[]> }\n }\n }\n delete(table: object): {\n where(condition: SQL): { returning(): Promise<Record<string, unknown>[]> }\n }\n}\n\ninterface QueryBuilder extends Promise<Record<string, unknown>[]> {\n where(condition: SQL | undefined): QueryBuilder\n orderBy(...rules: SQL[]): QueryBuilder\n limit(value: number): QueryBuilder\n offset(value: number): QueryBuilder\n}\n\nexport interface DrizzleAdapterOptions {\n /** A constructed Drizzle database, from any dialect's `drizzle()`. */\n readonly db: unknown\n /**\n * The schema module.\n *\n * Passed separately from `db` even though `drizzle(client, { schema })` also\n * takes it, because that form is optional and a database built without it\n * carries nothing to introspect.\n */\n readonly schema: Readonly<Record<string, unknown>>\n}\n\nexport class DrizzleAdapter implements OrmAdapter {\n readonly name = 'drizzle'\n\n readonly #db: DrizzleDatabase\n readonly #schemaModule: Readonly<Record<string, unknown>>\n\n #schema: DrizzleSchema | undefined\n #models: readonly ModelMetadata[] | undefined\n\n constructor(options: DrizzleAdapterOptions) {\n if (options.db === null || options.db === undefined) {\n throw new AdapterError(\n 'DrizzleAdapter requires a constructed Drizzle database. ' +\n 'Pass one via `new DrizzleAdapter({ db, schema })`.',\n )\n }\n if (options.schema === null || options.schema === undefined) {\n throw new AdapterError(\n 'DrizzleAdapter requires the schema module. ' +\n \"Import it with `import * as schema from './schema.js'` and pass it as `schema`.\",\n )\n }\n\n this.#db = options.db as DrizzleDatabase\n this.#schemaModule = options.schema\n }\n\n async getModels(): Promise<readonly ModelMetadata[]> {\n if (this.#models) return this.#models\n\n const schema = await readSchema(this.#schemaModule)\n\n if (schema.dialect === 'mysql') {\n // MySQL has no `RETURNING`, so `create` and `update` cannot report what\n // they wrote without a second query and a way to identify the new row -\n // which for a generated key means reading `insertId`, which is dialect\n // and driver specific. Refused here rather than shipped untested: an\n // adapter that silently returns the submitted data instead of the stored\n // row would hide every default and every trigger.\n throw new AdapterError(\n 'The Drizzle adapter does not support MySQL yet, because MySQL has no ' +\n 'RETURNING clause and writes could not report the stored row. ' +\n 'SQLite and PostgreSQL are supported.',\n )\n }\n\n this.#schema = schema\n this.#models = toModelMetadata({\n schema,\n compositeKeys: await this.#compositeKeys(schema),\n })\n\n return this.#models\n }\n\n async list(model: string, query: ListQuery): Promise<Page<RecordData>> {\n const { metadata, entry } = await this.#require(model)\n\n const where = buildWhere(metadata, entry, query)\n const orderBy = buildOrderBy(metadata, entry, query.sort)\n const { page, perPage, offset, limit } = resolvePagination(query)\n\n return this.#run(model, async () => {\n let rows = this.#db.select().from(entry.table).where(where)\n if (orderBy.length > 0) rows = rows.orderBy(...orderBy)\n\n const [data, totals] = await Promise.all([\n rows.limit(limit).offset(offset),\n this.#db.select({ value: count() }).from(entry.table).where(where),\n ])\n\n const total = Number(totals[0]?.['value'] ?? 0)\n return { data: data as RecordData[], total, page, perPage }\n })\n }\n\n async findOne(model: string, id: RecordId): Promise<RecordData | null> {\n const { metadata, entry } = await this.#require(model)\n\n const rows = await this.#run(model, () =>\n this.#db\n .select()\n .from(entry.table)\n .where(this.#byId(metadata, entry, id))\n .limit(1),\n )\n\n return (rows[0] as RecordData | undefined) ?? null\n }\n\n async create(model: string, data: RecordData): Promise<RecordData> {\n const { metadata, entry } = await this.#require(model)\n const writable = this.#writable(metadata, entry, data)\n\n const rows = await this.#run(model, () =>\n this.#db.insert(entry.table).values(writable).returning(),\n )\n\n const created = rows[0]\n if (created === undefined) {\n throw new AdapterError(`Creating a ${model} returned no row.`)\n }\n return created as RecordData\n }\n\n async update(model: string, id: RecordId, data: RecordData): Promise<RecordData> {\n const { metadata, entry } = await this.#require(model)\n const writable = this.#writable(metadata, entry, data)\n\n // An update with nothing to set is a request to see the record, and every\n // dialect rejects `SET` with no assignments.\n if (Object.keys(writable).length === 0) {\n const existing = await this.findOne(model, id)\n if (existing === null) throw new RecordNotFoundError(model, id)\n return existing\n }\n\n const rows = await this.#run(model, () =>\n this.#db\n .update(entry.table)\n .set(writable)\n .where(this.#byId(metadata, entry, id))\n .returning(),\n )\n\n const updated = rows[0]\n // Drizzle updates nothing and says nothing when the row is absent; Prisma\n // raises P2025. The contract expects the second, so it is produced here.\n if (updated === undefined) throw new RecordNotFoundError(model, id)\n return updated as RecordData\n }\n\n async delete(model: string, id: RecordId): Promise<void> {\n const { metadata, entry } = await this.#require(model)\n\n const rows = await this.#run(model, () =>\n this.#db\n .delete(entry.table)\n .where(this.#byId(metadata, entry, id))\n .returning(),\n )\n\n if (rows.length === 0) throw new RecordNotFoundError(model, id)\n }\n\n async listRelated(\n model: string,\n id: RecordId,\n relationField: string,\n query: ListQuery,\n ): Promise<Page<RecordData>> {\n const link = await this.#link(model, relationField)\n const { metadata: targetMetadata, entry: target } = await this.#require(link.targetModel)\n\n // The parent's own key value, which the children's foreign key holds. Read\n // rather than assumed equal to `id`, because a relation may reference a\n // unique column that is not the primary key.\n const parentValue = await this.#referencedValue(model, id, link.to)\n\n const foreignKey = target.columns.get(link.from)\n if (foreignKey === undefined) {\n throw new FieldNotFoundError(link.targetModel, link.from)\n }\n\n const declared = buildWhere(targetMetadata, target, query)\n const belongs = eq(foreignKey as never, parentValue)\n const where = declared ? and(belongs, declared) : belongs\n\n const orderBy = buildOrderBy(targetMetadata, target, query.sort)\n const { page, perPage, offset, limit } = resolvePagination(query)\n\n return this.#run(link.targetModel, async () => {\n let rows = this.#db.select().from(target.table).where(where)\n if (orderBy.length > 0) rows = rows.orderBy(...orderBy)\n\n const [data, totals] = await Promise.all([\n rows.limit(limit).offset(offset),\n this.#db.select({ value: count() }).from(target.table).where(where),\n ])\n\n return {\n data: data as RecordData[],\n total: Number(totals[0]?.['value'] ?? 0),\n page,\n perPage,\n }\n })\n }\n\n async attachRelated(\n model: string,\n id: RecordId,\n relationField: string,\n targetId: RecordId,\n ): Promise<void> {\n const link = await this.#link(model, relationField)\n const parentValue = await this.#referencedValue(model, id, link.to)\n\n // Rewriting the child's key, which also removes it from whoever held it.\n // The contract says so, and says the warning is the transport's job.\n await this.#setForeignKey(link, targetId, parentValue)\n }\n\n async detachRelated(\n model: string,\n id: RecordId,\n relationField: string,\n targetId: RecordId,\n ): Promise<void> {\n const link = await this.#link(model, relationField)\n await this.#setForeignKey(link, targetId, null)\n }\n\n /* ---------------------------------------------------------------------- */\n\n async #require(model: string): Promise<{ metadata: ModelMetadata; entry: DrizzleTable }> {\n const models = await this.getModels()\n const metadata = models.find((candidate) => candidate.name === model)\n if (!metadata) throw new ModelNotFoundError(model)\n\n const entry = this.#schema?.tables.find((candidate) => candidate.model === model)\n if (!entry) throw new ModelNotFoundError(model)\n\n return { metadata, entry }\n }\n\n /**\n * The two ends of a to-many, resolved to property names.\n *\n * `from` is the foreign key on the child, `to` the column on this model it\n * points at. Only the `one` side carries them, so a `many` is resolved by\n * finding its partner - which is why both sides are given the same relation\n * name when the schema is read.\n */\n async #link(\n model: string,\n relationField: string,\n ): Promise<{ targetModel: string; from: string; to: string }> {\n const { metadata } = await this.#require(model)\n\n const field = metadata.fields.find((candidate) => candidate.name === relationField)\n if (!field?.relation) throw new FieldNotFoundError(model, relationField)\n\n if (field.relation.cardinality !== 'many') {\n throw new FieldNotFoundError(\n model,\n relationField,\n 'Only to-many relations can be listed or modified through this route.',\n )\n }\n\n const models = await this.getModels()\n const target = models.find((candidate) => candidate.name === field.relation?.targetModel)\n const inverse = target?.fields.find(\n (candidate) =>\n candidate.relation?.cardinality === 'one' &&\n candidate.relation.name === field.relation?.name,\n )\n\n const from = inverse?.relation?.from\n const to = inverse?.relation?.to\n\n if (from === undefined || to === undefined) {\n // A many-to-many, or a relation whose owning side is not in this admin.\n // Drizzle has no first-class many-to-many: a join table is a table, and\n // appears in the admin as one, with a to-one on each side.\n throw new FieldNotFoundError(\n model,\n relationField,\n 'This relation has no foreign key on the far side. A many-to-many in ' +\n 'Drizzle is a join table, and is administered as its own resource.',\n )\n }\n\n return { targetModel: field.relation.targetModel, from, to }\n }\n\n async #referencedValue(model: string, id: RecordId, column: string): Promise<unknown> {\n const record = await this.findOne(model, id)\n if (record === null) throw new RecordNotFoundError(model, id)\n return record[column]\n }\n\n async #setForeignKey(\n link: { targetModel: string; from: string },\n targetId: RecordId,\n value: unknown,\n ): Promise<void> {\n const { metadata, entry } = await this.#require(link.targetModel)\n\n await this.#run(link.targetModel, () =>\n this.#db\n .update(entry.table)\n .set({ [link.from]: value })\n .where(this.#byId(metadata, entry, targetId))\n .returning(),\n )\n }\n\n #byId(metadata: ModelMetadata, entry: DrizzleTable, id: RecordId): SQL {\n const key = metadata.primaryKey[0]\n if (key === undefined || metadata.primaryKey.length > 1) {\n throw new AdapterError(\n `${metadata.name} has ${metadata.primaryKey.length === 0 ? 'no' : 'a composite'} ` +\n 'primary key. Records are addressed by a single key in this version.',\n )\n }\n\n const column = entry.columns.get(key)\n if (column === undefined) throw new FieldNotFoundError(metadata.name, key)\n\n // A numeric key arrives from the URL as a string, and `=` on an integer\n // column would compare against text.\n const field = metadata.fields.find((candidate) => candidate.name === key)\n const value = field?.kind === 'number' ? Number(id) : id\n\n return eq(column as never, value)\n }\n\n /**\n * The submitted data, restricted to columns that exist and may be written.\n *\n * Generated columns are dropped rather than refused: a form that round-trips\n * a record would otherwise fail on the id it was shown. Unknown keys *are*\n * refused, because silently ignoring a field is how a value appears to save\n * and does not.\n */\n #writable(metadata: ModelMetadata, entry: DrizzleTable, data: RecordData): RecordData {\n const writable: RecordData = {}\n\n for (const [key, value] of Object.entries(data)) {\n const field = metadata.fields.find((candidate) => candidate.name === key)\n\n if (!field || !entry.columns.has(key)) {\n // A relation field is a legitimate name that is not a column; anything\n // else is a mistake worth reporting.\n if (field?.kind === 'relation') continue\n throw new FieldNotFoundError(metadata.name, key)\n }\n\n if (field.isGenerated && field.isId) continue\n\n writable[key] =\n field.kind === 'datetime' && typeof value === 'string' ? new Date(value) : value\n }\n\n return writable\n }\n\n /** Composite primary keys, which are only reachable through the dialect's config. */\n async #compositeKeys(schema: DrizzleSchema): Promise<ReadonlyMap<string, readonly string[]>> {\n const core = (await import(\n schema.dialect === 'pg' ? 'drizzle-orm/pg-core' : 'drizzle-orm/sqlite-core'\n )) as {\n getTableConfig: (table: object) => {\n primaryKeys: readonly { columns: readonly { name: string }[] }[]\n }\n }\n\n const keys = new Map<string, readonly string[]>()\n\n for (const entry of schema.tables) {\n const declared = core.getTableConfig(entry.table).primaryKeys[0]\n if (declared === undefined) continue\n\n const names: string[] = []\n for (const column of declared.columns) {\n for (const [key, candidate] of entry.columns) {\n if (candidate.name === column.name) names.push(key)\n }\n }\n\n if (names.length > 0) keys.set(entry.model, names)\n }\n\n return keys\n }\n\n /**\n * Run a query, and translate whatever it throws.\n *\n * Core's own errors pass through: they were raised by this adapter and\n * already say what is wrong. Everything else is the driver's, and becomes\n * either a constraint the interface can put beside a field or an\n * `AdapterError`, which the HTTP layer reports without its message.\n */\n async #run<T>(model: string, operation: () => Promise<T>): Promise<T> {\n try {\n return await operation()\n } catch (cause) {\n if (isNestAdminError(cause)) throw cause\n\n const entry = this.#schema?.tables.find((candidate) => candidate.model === model)\n const toFieldNames = (sqlNames: readonly string[]): readonly string[] => {\n if (entry === undefined) return sqlNames\n const named: string[] = []\n for (const sqlName of sqlNames) {\n for (const [key, column] of entry.columns) {\n if (column.name === sqlName) named.push(key)\n }\n }\n return named\n }\n\n const constraint = toConstraintError(cause, model, toFieldNames)\n if (constraint) throw constraint\n\n throw new AdapterError(\n `The database refused a ${model} operation: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n )\n }\n }\n}\n","/**\n * A driver's refusal, as a `ConstraintError`.\n *\n * Drizzle does not normalise driver errors - it is a query builder, and what\n * reaches this code is whatever `better-sqlite3`, `pg` or `mysql2` threw. That\n * is the opposite of Prisma, which turns every one of them into a `P2xxx` code\n * with a `meta` object, and it is the single largest difference between writing\n * these two adapters.\n *\n * So this reads the driver's own report. Codes where the driver provides one;\n * the message only where it does not, and only to recover column names, which\n * are the difference between \"that was refused\" and \"that email is taken\".\n *\n * ## Why a wrong guess here is safe\n *\n * The field names only decide where the interface draws the message. Naming no\n * field puts it in a banner over the form, which is the fallback for every\n * shape not recognised below - so an unparsed message degrades to a correct\n * error in a less convenient place, never to a wrong one.\n */\nimport { ConstraintError, type ConstraintKind } from '@nest-admin/core'\n\ninterface DriverError {\n readonly code?: unknown\n readonly message?: unknown\n readonly constraint?: unknown\n readonly column?: unknown\n readonly detail?: unknown\n}\n\nfunction driverErrorOf(cause: unknown): DriverError | undefined {\n if (typeof cause !== 'object' || cause === null) return undefined\n\n // Drizzle rethrows the driver's error, sometimes wrapped once.\n const error = cause as DriverError & { cause?: unknown }\n const inner = error.cause\n if (typeof error.code !== 'string' && typeof inner === 'object' && inner !== null) {\n return inner as DriverError\n }\n return error\n}\n\n/**\n * SQLite reports the kind in the code and the columns in the message:\n *\n * SQLITE_CONSTRAINT_UNIQUE \"UNIQUE constraint failed: users.email\"\n * SQLITE_CONSTRAINT_NOTNULL \"NOT NULL constraint failed: users.name\"\n * SQLITE_CONSTRAINT_FOREIGNKEY \"FOREIGN KEY constraint failed\"\n */\nconst SQLITE_KINDS: Readonly<Record<string, ConstraintKind>> = {\n SQLITE_CONSTRAINT_UNIQUE: 'unique',\n SQLITE_CONSTRAINT_PRIMARYKEY: 'unique',\n SQLITE_CONSTRAINT_NOTNULL: 'required',\n SQLITE_CONSTRAINT_FOREIGNKEY: 'foreign-key',\n SQLITE_CONSTRAINT_TRIGGER: 'foreign-key',\n}\n\n/** Postgres and MySQL both use numeric-ish codes, and both name the constraint. */\nconst CODE_KINDS: Readonly<Record<string, ConstraintKind>> = {\n // Postgres\n '23505': 'unique',\n '23503': 'foreign-key',\n '23502': 'required',\n // MySQL\n ER_DUP_ENTRY: 'unique',\n ER_NO_REFERENCED_ROW_2: 'foreign-key',\n ER_ROW_IS_REFERENCED_2: 'foreign-key',\n ER_BAD_NULL_ERROR: 'required',\n}\n\n/** `users.email` in a SQLite message. Several, for a composite constraint. */\nfunction sqliteColumns(message: string): readonly string[] {\n const listed = /constraint failed: (.+)$/i.exec(message)?.[1]\n if (listed === undefined) return []\n\n return listed\n .split(',')\n .map((entry) => entry.trim().split('.').at(-1) ?? '')\n .filter((entry) => entry !== '')\n}\n\n/**\n * The column a Postgres error is about.\n *\n * `column` is populated for a not-null violation. For a unique violation the\n * column names are only in `detail` - `Key (email)=(a@b.c) already exists.` -\n * and the constraint name is a convention (`users_email_key`) rather than a\n * promise, so it is only trusted when the shape matches exactly.\n */\nfunction postgresColumns(error: DriverError): readonly string[] {\n if (typeof error.column === 'string' && error.column !== '') return [error.column]\n\n if (typeof error.detail === 'string') {\n const key = /^Key \\(([^)]+)\\)=/.exec(error.detail)?.[1]\n if (key !== undefined) return key.split(',').map((entry) => entry.trim())\n }\n\n if (typeof error.constraint === 'string') {\n const index = /^(.+?)_(.+)_(key|pkey|fkey)$/.exec(error.constraint)\n if (index?.[2] !== undefined) return index[2].split('_')\n }\n\n return []\n}\n\n/**\n * Column names in the schema's terms, not the database's.\n *\n * A driver reports SQL column names (`author_id`); every other layer of the\n * admin speaks in the schema's property names (`authorId`). Reporting the\n * former would name a field the form does not have, so the message would land\n * in the banner anyway - and be wrong about which box to look at.\n */\nexport type ColumnNames = (sqlNames: readonly string[]) => readonly string[]\n\nexport function toConstraintError(\n cause: unknown,\n model: string,\n toFieldNames: ColumnNames,\n): ConstraintError | undefined {\n const error = driverErrorOf(cause)\n if (error === undefined) return undefined\n\n const code = typeof error.code === 'string' ? error.code : undefined\n const message = typeof error.message === 'string' ? error.message : ''\n\n if (code !== undefined && code in SQLITE_KINDS) {\n return new ConstraintError(SQLITE_KINDS[code]!, model, toFieldNames(sqliteColumns(message)))\n }\n\n if (code !== undefined && code in CODE_KINDS) {\n return new ConstraintError(CODE_KINDS[code]!, model, toFieldNames(postgresColumns(error)))\n }\n\n // Some SQLite builds report only `SQLITE_CONSTRAINT`; the message still says\n // which kind it was, and it is the only thing left to read.\n if (code === 'SQLITE_CONSTRAINT' || (code === undefined && /constraint failed/i.test(message))) {\n const kind: ConstraintKind = /UNIQUE/i.test(message)\n ? 'unique'\n : /NOT NULL/i.test(message)\n ? 'required'\n : 'foreign-key'\n return new ConstraintError(kind, model, toFieldNames(sqliteColumns(message)))\n }\n\n return undefined\n}\n","/**\n * A Drizzle schema, as `ModelMetadata`.\n *\n * The mapping is small because Core's vocabulary was chosen to be ORM-neutral,\n * and this is the first time that claim has been tested by something other than\n * Prisma. The places where the two ORMs disagree are noted where they occur;\n * none of them needed a change to Core.\n */\nimport { is, SQL } from 'drizzle-orm'\nimport type { FieldKind, FieldMetadata, ModelMetadata } from '@nest-admin/core'\n\nimport type { DrizzleColumn, DrizzleSchema, DrizzleTable } from '../schema/introspect.js'\n\n/**\n * Drizzle's `dataType` to Core's `FieldKind`.\n *\n * `dataType` is the neutral one of the two type fields a column carries -\n * `columnType` is dialect-specific (`SQLiteText`, `PgVarchar`) and would make\n * this a table per dialect for no gain.\n *\n * `bigint` becomes `string`: it arrives as a `BigInt`, which does not survive\n * `JSON.stringify`, and the admin's transport is JSON. Calling it a number\n * would promise arithmetic that silently loses precision.\n */\nconst KINDS: Readonly<Record<string, FieldKind>> = {\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n date: 'datetime',\n json: 'json',\n bigint: 'string',\n buffer: 'unknown',\n array: 'unknown',\n custom: 'unknown',\n}\n\nfunction kindOf(column: DrizzleColumn): FieldKind {\n // An enum in Drizzle is a text column with a list of allowed values, whatever\n // the dialect calls it underneath.\n if (column.enumValues !== undefined && column.enumValues.length > 0) return 'enum'\n return KINDS[column.dataType] ?? 'unknown'\n}\n\n/**\n * Whether the database or the ORM supplies this value.\n *\n * The same rule the Prisma adapter uses, stated in Drizzle's terms: a value\n * produced by *running something* is generated, a literal is a pre-fill for the\n * create form.\n *\n * `.default('USER')` literal - a default value, offered in forms\n * `` .default(sql`now()`) `` generated - the database fills it in\n * `.$defaultFn(...)` generated - Drizzle fills it in\n * `.$onUpdateFn(...)` generated - the equivalent of `@updatedAt`\n * `.primaryKey({autoIncrement})` generated\n */\nfunction isGenerated(column: DrizzleColumn): boolean {\n return (\n column.autoIncrement === true ||\n typeof column.defaultFn === 'function' ||\n typeof column.onUpdateFn === 'function' ||\n is(column.default, SQL)\n )\n}\n\nfunction literalDefault(column: DrizzleColumn): unknown {\n if (!column.hasDefault || isGenerated(column)) return undefined\n return column.default\n}\n\nfunction toField(key: string, column: DrizzleColumn, primaryKey: readonly string[]): FieldMetadata {\n const isId = primaryKey.includes(key)\n const defaultValue = literalDefault(column)\n\n return {\n name: key,\n kind: kindOf(column),\n isId,\n isRequired: column.notNull,\n // A primary key is unique whether or not anyone said so.\n isUnique: column.isUnique || isId,\n // Drizzle has no list columns outside Postgres arrays, which arrive as\n // `dataType: 'array'` and are mapped to `unknown` above.\n isList: false,\n isGenerated: isGenerated(column),\n ...(defaultValue !== undefined ? { defaultValue } : {}),\n ...(column.enumValues !== undefined && column.enumValues.length > 0\n ? { enumValues: [...column.enumValues] }\n : {}),\n }\n}\n\nfunction primaryKeyOf(\n entry: DrizzleTable,\n composite: ReadonlyMap<string, readonly string[]>,\n): readonly string[] {\n const declared = composite.get(entry.model)\n if (declared && declared.length > 0) return declared\n\n const inline: string[] = []\n for (const [key, column] of entry.columns) {\n if (column.primary) inline.push(key)\n }\n return inline\n}\n\nexport interface ToMetadataInput {\n readonly schema: DrizzleSchema\n /** Composite keys, resolved by the caller because reading them needs the dialect. */\n readonly compositeKeys: ReadonlyMap<string, readonly string[]>\n}\n\nexport function toModelMetadata(input: ToMetadataInput): readonly ModelMetadata[] {\n const { schema, compositeKeys } = input\n\n return schema.tables.map((entry) => {\n const primaryKey = primaryKeyOf(entry, compositeKeys)\n\n const columns: FieldMetadata[] = []\n for (const [key, column] of entry.columns) {\n columns.push(toField(key, column, primaryKey))\n }\n\n const relations: FieldMetadata[] = schema.relations\n .filter((relation) => relation.model === entry.model)\n .map((relation) => ({\n name: relation.field,\n kind: 'relation' as const,\n isId: false,\n // A to-one is required exactly when its foreign key is. A to-many never\n // is: a parent with no children is a parent.\n isRequired:\n relation.cardinality === 'one' && relation.from !== undefined\n ? (entry.columns.get(relation.from)?.notNull ?? false)\n : false,\n isUnique: false,\n isList: relation.cardinality === 'many',\n isGenerated: false,\n relation: {\n targetModel: relation.targetModel,\n cardinality: relation.cardinality,\n name: relation.name,\n ...(relation.from !== undefined ? { from: relation.from } : {}),\n ...(relation.to !== undefined ? { to: relation.to } : {}),\n },\n }))\n\n return { name: entry.model, primaryKey, fields: [...columns, ...relations] }\n })\n}\n","/**\n * `ListQuery` into a Drizzle query.\n *\n * The rules enforced here are the ones the Prisma adapter enforces, and they\n * are enforced again rather than shared because they are about *this* ORM's\n * capabilities: which fields can be filtered, which operators a kind admits,\n * what a search box searches. Where the two adapters agree, they agree because\n * Core's contract says the same thing to both.\n *\n * ## Two places Drizzle needs work Prisma did for us\n *\n * **Case insensitivity.** Prisma has `mode: 'insensitive'`, on the providers\n * that support it. Drizzle has `ilike`, on Postgres only. Rather than branch per\n * dialect, both sides of the comparison go through `lower()`, which every\n * dialect this adapter supports has. It costs an index unless one is declared on\n * the expression - noted here because that is a real trade and not a free one.\n *\n * **`LIKE` metacharacters.** Prisma escapes `%` and `_` inside `contains`.\n * Building the pattern by hand means doing it here, or a search for `100%`\n * silently matches every row.\n */\nimport {\n FieldNotFoundError,\n InvalidQueryError,\n type FieldMetadata,\n type FilterRule,\n type ListQuery,\n type ModelMetadata,\n type SortRule,\n} from '@nest-admin/core'\nimport { and, asc, desc, eq, gt, gte, inArray, lt, lte, ne, or, sql, type SQL } from 'drizzle-orm'\n\nimport type { DrizzleColumn, DrizzleTable } from '../schema/introspect.js'\n\n/** Kept in step with `MAX_PER_PAGE` in the Prisma adapter and the UI's page-size list. */\nexport const DEFAULT_PER_PAGE = 25\nexport const MAX_PER_PAGE = 100\n\nconst STRING_ONLY = new Set(['contains', 'startsWith', 'endsWith'])\nconst COMPARISON = new Set(['gt', 'gte', 'lt', 'lte'])\n\ntype Purpose = 'filter' | 'sort'\n\n/**\n * The field a rule may address, and the reason when it may not.\n *\n * Filtering by a to-one relation is answered by filtering its foreign key,\n * which is the same record with a name the caller is more likely to have. The\n * refusals mirror the Prisma adapter's, message for message, because they are\n * about what the admin promises rather than about either ORM.\n */\nfunction queryable(model: ModelMetadata, fieldName: string, purpose: Purpose): FieldMetadata {\n const field = model.fields.find((candidate) => candidate.name === fieldName)\n if (!field) throw new FieldNotFoundError(model.name, fieldName)\n\n if (field.kind === 'relation') {\n const owned = field.relation?.from\n if (owned !== undefined && field.relation?.cardinality === 'one') {\n if (purpose === 'filter') return queryable(model, owned, purpose)\n throw new FieldNotFoundError(\n model.name,\n fieldName,\n `Sorting by a relation is not supported in this version. ` +\n `Sorting by \"${owned}\" would order by an opaque key rather than by ` +\n `anything readable.`,\n )\n }\n throw new FieldNotFoundError(\n model.name,\n fieldName,\n 'Relation fields cannot be filtered or sorted in this version.',\n )\n }\n\n if (field.isList) {\n throw new FieldNotFoundError(\n model.name,\n fieldName,\n 'List fields cannot be filtered or sorted in this version.',\n )\n }\n\n return field\n}\n\nfunction columnOf(entry: DrizzleTable, field: FieldMetadata): DrizzleColumn {\n const column = entry.columns.get(field.name)\n if (column === undefined) {\n // Metadata and schema came from the same object, so this is unreachable\n // short of a schema mutated after `getModels`.\n throw new FieldNotFoundError(entry.model, field.name)\n }\n return column\n}\n\n/** `%`, `_` and the escape character itself, so a search for \"100%\" means it. */\nfunction escapeLike(term: string): string {\n return term.replace(/[\\\\%_]/g, (match) => `\\\\${match}`)\n}\n\n/** A case-insensitive LIKE that works on every dialect this adapter supports. */\nfunction insensitiveLike(column: unknown, pattern: string): SQL {\n return sql`lower(${column}) LIKE lower(${pattern}) ESCAPE '\\\\'`\n}\n\nfunction condition(model: ModelMetadata, entry: DrizzleTable, rule: FilterRule): SQL {\n const field = queryable(model, rule.field, 'filter')\n const column = columnOf(entry, field)\n\n if (STRING_ONLY.has(rule.operator) && field.kind !== 'string' && field.kind !== 'enum') {\n throw new InvalidQueryError(\n `Operator \"${rule.operator}\" requires a string field, but ` +\n `\"${model.name}.${field.name}\" is of kind \"${field.kind}\".`,\n )\n }\n\n if (COMPARISON.has(rule.operator) && field.kind === 'boolean') {\n throw new InvalidQueryError(\n `Operator \"${rule.operator}\" cannot be applied to boolean field ` +\n `\"${model.name}.${field.name}\".`,\n )\n }\n\n const value = coerce(field, rule.value, model.name)\n\n switch (rule.operator) {\n case 'in': {\n if (!Array.isArray(rule.value)) {\n throw new InvalidQueryError(\n `Operator \"in\" requires an array value for \"${model.name}.${field.name}\".`,\n )\n }\n return inArray(\n column as never,\n rule.value.map((entryValue) => coerce(field, entryValue, model.name)),\n )\n }\n case 'eq':\n return eq(column as never, value)\n case 'ne':\n return ne(column as never, value)\n case 'gt':\n return gt(column as never, value)\n case 'gte':\n return gte(column as never, value)\n case 'lt':\n return lt(column as never, value)\n case 'lte':\n return lte(column as never, value)\n case 'contains':\n return insensitiveLike(column, `%${escapeLike(String(rule.value))}%`)\n case 'startsWith':\n return insensitiveLike(column, `${escapeLike(String(rule.value))}%`)\n case 'endsWith':\n return insensitiveLike(column, `%${escapeLike(String(rule.value))}`)\n }\n}\n\n/**\n * A value in the shape Drizzle's column mapper expects.\n *\n * Prisma accepts an ISO string for a `DateTime`; Drizzle's timestamp columns\n * expect a `Date` and will store the string as-is otherwise, which reads back\n * as an invalid date. The HTTP layer's coercion is type-directed but produces\n * JSON values, so the last step happens here - where the column is known.\n */\nfunction coerce(field: FieldMetadata, value: unknown, model: string): unknown {\n if (value === null || value === undefined) return value\n\n if (field.kind === 'datetime' && !(value instanceof Date)) {\n const parsed = new Date(String(value))\n if (Number.isNaN(parsed.getTime())) {\n throw new InvalidQueryError(`\"${value}\" is not a date, for \"${model}.${field.name}\".`)\n }\n return parsed\n }\n\n return value\n}\n\n/**\n * What the search box searches.\n *\n * The same exclusions the Prisma adapter makes: generated columns, and foreign\n * keys. A foreign key is a string column holding an opaque id, so leaving it in\n * makes a one-letter search match nearly every row of any model that references\n * another.\n */\nexport function searchCondition(\n model: ModelMetadata,\n entry: DrizzleTable,\n term: string,\n): SQL | undefined {\n const foreignKeys = new Set(\n model.fields.map((field) => field.relation?.from).filter((name) => name !== undefined),\n )\n\n const searchable = model.fields.filter(\n (field) =>\n (field.kind === 'string' || field.kind === 'enum') &&\n !field.isList &&\n !field.isGenerated &&\n !foreignKeys.has(field.name) &&\n entry.columns.has(field.name),\n )\n\n if (searchable.length === 0) return undefined\n\n const pattern = `%${escapeLike(term)}%`\n return or(...searchable.map((field) => insensitiveLike(columnOf(entry, field), pattern)))\n}\n\nexport function buildWhere(\n model: ModelMetadata,\n entry: DrizzleTable,\n query: Pick<ListQuery, 'filters' | 'search'>,\n): SQL | undefined {\n const conditions: SQL[] = []\n\n for (const rule of query.filters ?? []) {\n conditions.push(condition(model, entry, rule))\n }\n\n const term = query.search?.trim()\n if (term) {\n const search = searchCondition(model, entry, term)\n if (search) conditions.push(search)\n }\n\n if (conditions.length === 0) return undefined\n return conditions.length === 1 ? conditions[0] : and(...conditions)\n}\n\nexport function buildOrderBy(\n model: ModelMetadata,\n entry: DrizzleTable,\n rules: readonly SortRule[] | undefined,\n): readonly SQL[] {\n return (rules ?? []).map((rule) => {\n const field = queryable(model, rule.field, 'sort')\n const column = columnOf(entry, field)\n return (rule.direction === 'desc' ? desc(column as never) : asc(column as never)) as SQL\n })\n}\n\nexport function resolvePagination(query: Pick<ListQuery, 'page' | 'perPage'>): {\n page: number\n perPage: number\n offset: number\n limit: number\n} {\n const rawPage = query.page ?? 1\n const rawPerPage = query.perPage ?? DEFAULT_PER_PAGE\n\n const page = Number.isFinite(rawPage) && rawPage >= 1 ? Math.floor(rawPage) : 1\n // Clamped rather than refused, matching the Prisma adapter: a page size above\n // the ceiling is a request for \"as many as you will give me\".\n const perPage =\n Number.isFinite(rawPerPage) && rawPerPage >= 1\n ? Math.min(Math.floor(rawPerPage), MAX_PER_PAGE)\n : DEFAULT_PER_PAGE\n\n return { page, perPage, offset: (page - 1) * perPage, limit: perPage }\n}\n","/**\n * Reading a Drizzle schema.\n *\n * Drizzle has no DMMF and no generated client to interrogate. What it has is\n * the schema module itself - a plain object of table definitions - and the\n * table objects carry everything needed, because Drizzle Kit reads them the\n * same way to write migrations.\n *\n * ## Where the names come from\n *\n * A Drizzle table has two names: the SQL one (`users`) and the key it is\n * exported under (`users`, or `Users`, or whatever the developer wrote). This\n * uses the **export key** as the model name, and the **property key** as the\n * field name.\n *\n * That is deliberate. Those are the names the developer typed, the names their\n * own queries use, and - since rows come back keyed by property rather than by\n * column - the names the data already arrives under. Using the SQL names would\n * mean an admin whose URLs and configuration disagree with the schema file the\n * developer is looking at.\n *\n * ## Dialect\n *\n * `getTableConfig` is exported per dialect, not generically, and foreign keys\n * are only reachable through it. The dialect is discoverable without importing\n * any of them: every column's `columnType` is prefixed with it (`SQLiteText`,\n * `PgInteger`, `MySqlInt`). So the dialect is detected from the schema and the\n * matching module is imported once, on demand - which also means a SQLite\n * application never loads the Postgres core.\n */\nimport { AdapterError } from '@nest-admin/core'\nimport {\n createTableRelationsHelpers,\n getTableColumns,\n getTableName,\n is,\n Many,\n One,\n Relations,\n SQL,\n Table,\n} from 'drizzle-orm'\n\n/** What a Drizzle column exposes. Structural, because the generic type is not usable here. */\nexport interface DrizzleColumn {\n readonly name: string\n readonly dataType: string\n readonly columnType: string\n readonly notNull: boolean\n readonly hasDefault: boolean\n readonly primary: boolean\n readonly isUnique: boolean\n readonly autoIncrement?: boolean\n readonly enumValues?: readonly string[] | undefined\n readonly default?: unknown\n readonly defaultFn?: unknown\n readonly onUpdateFn?: unknown\n}\n\nexport type Dialect = 'sqlite' | 'pg' | 'mysql'\n\n/** A table, under the name the developer exported it as. */\nexport interface DrizzleTable {\n /** The export key. This is the model name the admin uses everywhere. */\n readonly model: string\n /** The SQL table name. Only used in diagnostics. */\n readonly sqlName: string\n readonly table: object\n /** Property key to column. The property key is the field name. */\n readonly columns: ReadonlyMap<string, DrizzleColumn>\n}\n\n/** One side of a relation, resolved to property keys. */\nexport interface DrizzleRelation {\n /** The model this field is on. */\n readonly model: string\n /** The field name it appears under. */\n readonly field: string\n readonly targetModel: string\n readonly cardinality: 'one' | 'many'\n /**\n * Shared by both sides, so `inverseRelationField` can pair them.\n *\n * Built from the foreign key rather than from either field name, because the\n * two sides are named independently and a name derived from one of them\n * would not match the other.\n */\n readonly name: string\n /** On the `one` side: the foreign key on this model, and what it points at. */\n readonly from?: string\n readonly to?: string\n}\n\nexport interface DrizzleSchema {\n readonly dialect: Dialect\n readonly tables: readonly DrizzleTable[]\n readonly relations: readonly DrizzleRelation[]\n}\n\ninterface TableConfig {\n readonly primaryKeys: readonly { readonly columns: readonly DrizzleColumn[] }[]\n readonly foreignKeys: readonly {\n reference(): {\n readonly columns: readonly DrizzleColumn[]\n readonly foreignTable: object\n readonly foreignColumns: readonly DrizzleColumn[]\n }\n }[]\n}\n\nfunction dialectOf(tables: readonly DrizzleTable[]): Dialect {\n for (const entry of tables) {\n for (const column of entry.columns.values()) {\n if (column.columnType.startsWith('SQLite')) return 'sqlite'\n if (column.columnType.startsWith('Pg')) return 'pg'\n if (column.columnType.startsWith('MySql')) return 'mysql'\n }\n }\n\n throw new AdapterError(\n 'Could not tell which SQL dialect this Drizzle schema uses. ' +\n 'Pass a schema containing at least one table with at least one column.',\n )\n}\n\nconst CORES: Readonly<Record<Dialect, string>> = {\n sqlite: 'drizzle-orm/sqlite-core',\n pg: 'drizzle-orm/pg-core',\n mysql: 'drizzle-orm/mysql-core',\n}\n\nasync function configReader(dialect: Dialect): Promise<(table: object) => TableConfig> {\n const core = (await import(CORES[dialect])) as {\n getTableConfig: (table: object) => TableConfig\n }\n return core.getTableConfig\n}\n\n/** The property key a column is exported under, given its SQL name. */\nfunction keyOf(entry: DrizzleTable, column: DrizzleColumn): string | undefined {\n for (const [key, candidate] of entry.columns) {\n if (candidate.name === column.name) return key\n }\n return undefined\n}\n\n/**\n * Relations the developer declared with `relations()`.\n *\n * Read by calling the config with Drizzle's own helpers, which is how Drizzle's\n * relational queries read it too. When they are declared, their names are\n * authoritative: `author` is what the developer called it, and no heuristic\n * should overrule that.\n */\nfunction declaredRelations(\n schema: Readonly<Record<string, unknown>>,\n tables: readonly DrizzleTable[],\n nameOfFk: (child: string, fk: string) => string,\n): readonly DrizzleRelation[] {\n const byTable = new Map(tables.map((entry) => [entry.table, entry]))\n const found: DrizzleRelation[] = []\n\n for (const value of Object.values(schema)) {\n if (!is(value, Relations)) continue\n\n const owner = byTable.get(value.table)\n if (owner === undefined) continue\n\n const built = value.config(createTableRelationsHelpers(value.table)) as Record<string, unknown>\n\n for (const [field, relation] of Object.entries(built)) {\n const one = is(relation, One)\n if (!one && !is(relation, Many)) continue\n\n const target = byTable.get((relation as { referencedTable: object }).referencedTable)\n if (target === undefined) continue\n\n if (!one) {\n // A `many` declares no columns; the foreign key lives on the far side,\n // and the pairing name is worked out once both sides are known.\n found.push({\n model: owner.model,\n field,\n targetModel: target.model,\n cardinality: 'many',\n name: '',\n })\n continue\n }\n\n const config = (relation as { config?: { fields?: readonly DrizzleColumn[] } }).config\n const fk = config?.fields?.[0]\n const reference = (relation as { config?: { references?: readonly DrizzleColumn[] } }).config\n ?.references?.[0]\n const fkKey = fk ? keyOf(owner, fk) : undefined\n const refKey = reference ? keyOf(target, reference) : undefined\n\n found.push({\n model: owner.model,\n field,\n targetModel: target.model,\n cardinality: 'one',\n name: fkKey ? nameOfFk(owner.model, fkKey) : '',\n ...(fkKey !== undefined ? { from: fkKey } : {}),\n ...(refKey !== undefined ? { to: refKey } : {}),\n })\n }\n }\n\n return found\n}\n\n/**\n * Read a Drizzle schema module into something ORM-neutral.\n *\n * Relations come from `relations()` where the developer declared them, and from\n * foreign keys where they did not - so an admin works against a schema that has\n * never heard of Drizzle's relational API, and uses the developer's own names\n * the moment it has.\n */\nexport async function readSchema(\n schema: Readonly<Record<string, unknown>>,\n): Promise<DrizzleSchema> {\n const tables: DrizzleTable[] = []\n\n for (const [model, value] of Object.entries(schema)) {\n if (!is(value, Table)) continue\n\n const columns = new Map<string, DrizzleColumn>(\n Object.entries(getTableColumns(value) as Record<string, DrizzleColumn>),\n )\n\n tables.push({ model, sqlName: getTableName(value), table: value, columns })\n }\n\n if (tables.length === 0) {\n throw new AdapterError(\n 'This Drizzle schema exports no tables. Pass the schema module itself, ' +\n \"for example `new DrizzleAdapter({ db, schema })` after `import * as schema from './schema.js'`.\",\n )\n }\n\n const dialect = dialectOf(tables)\n const getTableConfig = await configReader(dialect)\n const byTable = new Map(tables.map((entry) => [entry.table, entry]))\n\n const nameOfFk = (child: string, fk: string): string => `${child}.${fk}`\n\n const declared = declaredRelations(schema, tables, nameOfFk)\n const declaredOn = new Set(declared.map((relation) => `${relation.model}.${relation.field}`))\n\n const relations: DrizzleRelation[] = []\n\n // Foreign keys, read from both ends. Each one is two fields: a `one` on the\n // table that holds the key, and a `many` on the table it points at.\n for (const child of tables) {\n for (const foreign of getTableConfig(child.table).foreignKeys) {\n const reference = foreign.reference()\n const parent = byTable.get(reference.foreignTable)\n const column = reference.columns[0]\n const target = reference.foreignColumns[0]\n if (parent === undefined || column === undefined || target === undefined) continue\n\n const fkKey = keyOf(child, column)\n const refKey = keyOf(parent, target)\n if (fkKey === undefined || refKey === undefined) continue\n\n const name = nameOfFk(child.model, fkKey)\n\n // Only where the developer declared nothing. A declared relation carries\n // the name they chose, and inventing a second field beside it would show\n // the same link twice.\n const declaredOne = declared.find(\n (relation) => relation.cardinality === 'one' && relation.name === name,\n )\n\n if (declaredOne === undefined) {\n relations.push({\n model: child.model,\n // `authorId` describes a column; `author` describes the thing on the\n // other end, which is what a person reading a record wants named.\n field: relationFieldName(fkKey, child.columns),\n targetModel: parent.model,\n cardinality: 'one',\n name,\n from: fkKey,\n to: refKey,\n })\n }\n\n const declaredMany = declared.find(\n (relation) =>\n relation.cardinality === 'many' &&\n relation.model === parent.model &&\n relation.targetModel === child.model &&\n relation.name === '',\n )\n\n if (declaredMany === undefined) {\n relations.push({\n model: parent.model,\n field: manyFieldName(child.model, parent, declaredOn),\n targetModel: child.model,\n cardinality: 'many',\n name,\n })\n } else {\n // Pair the declared `many` with this key, so both sides share a name.\n relations.push({ ...declaredMany, name })\n }\n }\n }\n\n // Declared `one` relations keep their own field names.\n for (const relation of declared) {\n if (relation.cardinality === 'one' && relation.name !== '') relations.push(relation)\n }\n\n return { dialect, tables, relations }\n}\n\n/**\n * `authorId` becomes `author`, unless something is already called that.\n *\n * The suffix is stripped rather than the field being called `authorId` twice,\n * because the relation and the key are two different things: one is a record,\n * the other is an opaque string, and the interface renders them differently.\n */\nfunction relationFieldName(fkKey: string, columns: ReadonlyMap<string, DrizzleColumn>): string {\n const stripped = /^(.+?)(Id|_id|ID)$/.exec(fkKey)?.[1]\n if (stripped === undefined || stripped === '') return `${fkKey}Ref`\n return columns.has(stripped) ? `${fkKey}Ref` : stripped\n}\n\n/** `Post` on `User` becomes `posts`, unless the table already has that column. */\nfunction manyFieldName(\n childModel: string,\n parent: DrizzleTable,\n declaredOn: ReadonlySet<string>,\n): string {\n const base = `${childModel.charAt(0).toLowerCase()}${childModel.slice(1)}`\n const plural = base.endsWith('s') ? base : `${base}s`\n const taken = parent.columns.has(plural) || declaredOn.has(`${parent.model}.${plural}`)\n return taken ? `${plural}Related` : plural\n}\n\nexport { SQL }\n"],"mappings":"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","names":["import_drizzle_orm","import_drizzle_orm","import_drizzle_orm","is","AdapterError","FieldNotFoundError","eq","and"]}
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Normalised, ORM-independent description of a model and its fields.
3
+ *
4
+ * Every ORM adapter translates its own schema representation (Prisma DMMF,
5
+ * TypeORM entity metadata, a Drizzle table object, ...) into these shapes.
6
+ * Nothing downstream - the CRUD engine, the HTTP API, the admin UI - is
7
+ * allowed to look at anything else.
8
+ *
9
+ * @experimental Draft contract. Expected to change during MVP implementation.
10
+ */
11
+ /** ORM-independent classification of a scalar or relation field. */
12
+ type FieldKind = 'string' | 'number' | 'boolean' | 'datetime' | 'enum' | 'json' | 'relation'
13
+ /** The adapter recognised the field but cannot map it onto a known kind. */
14
+ | 'unknown';
15
+ /** Cardinality of a relation from the owning model's point of view. */
16
+ type RelationCardinality = 'one' | 'many';
17
+ /**
18
+ * A relation, and how to act on it.
19
+ *
20
+ * `from` and `to` are what turn a relation from something an admin can only
21
+ * display into something it can filter and write. A to-one relation is stored
22
+ * as an ordinary scalar column - `Post.authorId` - and that column is what a
23
+ * query has to be expressed in terms of. Without knowing its name, a filter on
24
+ * `author` cannot be translated, and a form has no field to submit.
25
+ *
26
+ * Both are absent on to-many relations, which have no column on this side.
27
+ */
28
+ interface RelationMetadata {
29
+ /** `name` of the {@link ModelMetadata} on the other side of the relation. */
30
+ readonly targetModel: string;
31
+ readonly cardinality: RelationCardinality;
32
+ /**
33
+ * Scalar field on **this** model holding the foreign key, for a to-one
34
+ * relation - `authorId` on `Post.author`.
35
+ *
36
+ * Absent when the relation has no column on this side: every to-many, and
37
+ * the non-owning half of a one-to-one.
38
+ */
39
+ readonly from?: string;
40
+ /** Field on the target model that `from` points at - usually its id. */
41
+ readonly to?: string;
42
+ /**
43
+ * Name shared by both halves of the relation.
44
+ *
45
+ * The only reliable way to pair `User.posts` with `Post.author`, which two
46
+ * things need. Distinguishing a many-to-many from a one-to-many requires
47
+ * looking at the other side - both are `'many'` from here, but only one has
48
+ * no column anywhere. And knowing whether a child's key is required decides
49
+ * whether it can be detached at all.
50
+ *
51
+ * Two relations between the same pair of models are told apart by it too:
52
+ * `Post.author` and `Post.reviewer` both target `User`.
53
+ */
54
+ readonly name?: string;
55
+ }
56
+ interface FieldMetadata {
57
+ readonly name: string;
58
+ readonly kind: FieldKind;
59
+ /** Part of the model's primary key. */
60
+ readonly isId: boolean;
61
+ readonly isRequired: boolean;
62
+ readonly isUnique: boolean;
63
+ /** The field holds a list of {@link FieldKind} values. */
64
+ readonly isList: boolean;
65
+ /**
66
+ * The value is produced by the database or the ORM and is not asked of the
67
+ * user - `@default(cuid())`, `@default(now())`, `@default(autoincrement())`,
68
+ * `@updatedAt`. Such fields are displayed but not editable.
69
+ *
70
+ * This is NOT "has a default". A field with a literal default
71
+ * (`active Boolean @default(true)`) is an ordinary editable field that
72
+ * happens to arrive pre-filled; see {@link FieldMetadata.defaultValue}.
73
+ *
74
+ * NAME COLLISION - read before implementing an adapter. Prisma's DMMF also
75
+ * has a field called `isGenerated`, and it does NOT mean this. Measured
76
+ * against Prisma 7.10.0, DMMF reports `isGenerated: false` for
77
+ * `id String @id @default(cuid())`. Mapping it across directly produces
78
+ * editable primary keys. The correct derivation - a *function* default, or
79
+ * an updated-at column - is in `packages/prisma/src/metadata/to-metadata.ts`
80
+ * and `packages/drizzle/src/metadata/to-metadata.ts`, which state it in each
81
+ * ORM's own terms.
82
+ */
83
+ readonly isGenerated: boolean;
84
+ /**
85
+ * Accepted on a write, never returned on a read.
86
+ *
87
+ * Set by `writeOnly` in the configuration. A password is the reason it
88
+ * exists: it has to be typed into a form and must never come back out, and
89
+ * `hidden` cannot express that - it refuses the field in both directions, so
90
+ * a hidden password column leaves no way to set one.
91
+ *
92
+ * Enforced twice, deliberately: the field is left out of the columns the
93
+ * adapter is asked for, *and* out of the projection applied to whatever comes
94
+ * back. One of those is enough; two is what it takes for a future adapter
95
+ * that ignores the field scope not to become a leak.
96
+ */
97
+ readonly writeOnly?: boolean;
98
+ /**
99
+ * Literal default the admin should pre-fill on create, when the schema
100
+ * declares one (`@default(true)`, `@default(0)`, `@default("USER")`).
101
+ *
102
+ * Absent for generated values: there is no literal to pre-fill for
103
+ * `@default(now())`, and {@link FieldMetadata.isGenerated} is `true` instead.
104
+ */
105
+ readonly defaultValue?: unknown;
106
+ /** Populated when `kind` is `'enum'`. */
107
+ readonly enumValues?: readonly string[];
108
+ /** Populated when `kind` is `'relation'`. */
109
+ readonly relation?: RelationMetadata;
110
+ }
111
+ interface ModelMetadata {
112
+ /** Adapter-facing identifier, e.g. the Prisma model name `User`. */
113
+ readonly name: string;
114
+ /**
115
+ * Field names forming the primary key. Modelled as a list rather than a
116
+ * single `id` so composite keys do not require a breaking change later,
117
+ * even though the MVP will only support single-column keys.
118
+ */
119
+ readonly primaryKey: readonly string[];
120
+ readonly fields: readonly FieldMetadata[];
121
+ /**
122
+ * Field that names a record of this model in one line, when the application
123
+ * has declared one.
124
+ *
125
+ * A slot rather than a value: left unset, `displayFieldFor` works it out from
126
+ * the fields. It is here so that a declared choice travels with the model and
127
+ * reaches the adapter and the metadata document alike, without either of them
128
+ * having to read configuration.
129
+ */
130
+ readonly displayField?: string;
131
+ }
132
+
133
+ /**
134
+ * ORM-independent query description.
135
+ *
136
+ * The admin UI and the HTTP layer speak only this vocabulary; each adapter is
137
+ * responsible for translating it into its own query language.
138
+ *
139
+ * @experimental Draft contract. Expected to change during MVP implementation.
140
+ */
141
+ type SortDirection = 'asc' | 'desc';
142
+ interface SortRule {
143
+ readonly field: string;
144
+ readonly direction: SortDirection;
145
+ }
146
+ /**
147
+ * The deliberately small operator set the MVP targets. Anything richer
148
+ * (nested relation filters, OR/AND trees, full-text) is a later concern and
149
+ * should extend this union rather than bypass it.
150
+ */
151
+ type FilterOperator = 'eq' | 'ne' | 'contains' | 'startsWith' | 'endsWith' | 'gt' | 'gte' | 'lt' | 'lte' | 'in';
152
+ interface FilterRule {
153
+ readonly field: string;
154
+ readonly operator: FilterOperator;
155
+ readonly value: unknown;
156
+ }
157
+ /** Page-number based pagination. Cursor pagination is a later addition. */
158
+ interface ListQuery {
159
+ readonly page?: number;
160
+ readonly perPage?: number;
161
+ readonly sort?: readonly SortRule[];
162
+ readonly filters?: readonly FilterRule[];
163
+ /** Free-text term the adapter applies across searchable string fields. */
164
+ readonly search?: string;
165
+ /**
166
+ * The fields this query may touch, and the only ones it should return.
167
+ *
168
+ * Set by the caller that knows which fields the admin exposes - the adapter
169
+ * reads a schema, not a configuration. Without it, a field the application
170
+ * hid would still be searched by free text, sortable, filterable and
171
+ * returned, because from the adapter's side it is an ordinary column.
172
+ *
173
+ * Omitted means "every field the model has".
174
+ */
175
+ readonly fields?: readonly string[];
176
+ }
177
+ interface Page<T> {
178
+ readonly data: readonly T[];
179
+ readonly total: number;
180
+ readonly page: number;
181
+ readonly perPage: number;
182
+ }
183
+
184
+ /**
185
+ * The single seam between Nest Admin and any ORM.
186
+ *
187
+ * Adding support for a new ORM means writing one implementation of
188
+ * {@link OrmAdapter} and nothing else. Core, the NestJS integration, the HTTP
189
+ * contract and the admin UI stay untouched.
190
+ *
191
+ * @experimental Draft contract. Expected to change during MVP implementation.
192
+ */
193
+
194
+ /**
195
+ * Primary key value of a single record. Composite keys are represented by
196
+ * {@link ModelMetadata.primaryKey}; supporting them at this level is a
197
+ * post-MVP change.
198
+ */
199
+ type RecordId = string | number;
200
+ /** An untyped record as it crosses the adapter boundary. */
201
+ type RecordData = Record<string, unknown>;
202
+ interface OrmAdapter {
203
+ /** Stable identifier used in diagnostics, e.g. `'prisma'`. */
204
+ readonly name: string;
205
+ /**
206
+ * Discover the models the adapter can serve. Asynchronous because an adapter
207
+ * may need to read a schema file or import a generated client.
208
+ */
209
+ getModels(): Promise<readonly ModelMetadata[]>;
210
+ list(model: string, query: ListQuery): Promise<Page<RecordData>>;
211
+ findOne(model: string, id: RecordId): Promise<RecordData | null>;
212
+ create(model: string, data: RecordData): Promise<RecordData>;
213
+ update(model: string, id: RecordId, data: RecordData): Promise<RecordData>;
214
+ delete(model: string, id: RecordId): Promise<void>;
215
+ /**
216
+ * A page of the records on the far side of a to-many relation.
217
+ *
218
+ * Paginated for the same reason a list is: the number of children is a
219
+ * property of the data, not of the schema, and a parent with fifty thousand
220
+ * of them must not be a page that never loads.
221
+ *
222
+ * Kept separate from `list` rather than expressed as a filter because a
223
+ * many-to-many has no column to filter on - the link lives in a join table.
224
+ * A one-to-many could be asked for either way; going through one method means
225
+ * the caller does not have to know which it is looking at.
226
+ */
227
+ listRelated(model: string, id: RecordId, relationField: string, query: ListQuery): Promise<Page<RecordData>>;
228
+ /**
229
+ * Link an existing record to this one.
230
+ *
231
+ * Across a many-to-many this adds a row to the join table and changes
232
+ * neither record. Across a one-to-many it rewrites the child's foreign key,
233
+ * which also **removes it from whatever parent held it** - the same operation
234
+ * with a consequence the caller should have been told about. Deciding whether
235
+ * to warn is the transport layer's job; the adapter performs what it is asked.
236
+ */
237
+ attachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
238
+ /**
239
+ * Unlink a record from this one, without deleting either.
240
+ *
241
+ * Across a one-to-many this clears the child's foreign key, which is
242
+ * impossible when that column is required - see `detachBlockedReason`. The
243
+ * adapter may assume the caller has checked, and will surface the database's
244
+ * own refusal if it has not.
245
+ */
246
+ detachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
247
+ }
248
+
249
+ /**
250
+ * The Drizzle implementation of `OrmAdapter`.
251
+ *
252
+ * This package exists to answer a question the Prisma adapter cannot: is
253
+ * `OrmAdapter` a contract, or is it a description of Prisma? Writing a second
254
+ * implementation against a genuinely different ORM - a query builder with no
255
+ * generated client, no DMMF and no normalised errors - is the only way to find
256
+ * out before 1.0 freezes it.
257
+ *
258
+ * The answer, recorded here because it is the point of the package: Core needed
259
+ * no changes. What differs is entirely inside this directory, and each
260
+ * difference is documented where it is handled.
261
+ *
262
+ * ## What Drizzle does not give us, and what is done instead
263
+ *
264
+ * | Prisma | Drizzle | Handled in |
265
+ * | --- | --- | --- |
266
+ * | DMMF describing every model | the schema object itself | `schema/introspect.ts` |
267
+ * | `P2xxx` codes with `meta` | the driver's own error | `errors/constraints.ts` |
268
+ * | `mode: 'insensitive'` | `lower()` on both sides | `query/build.ts` |
269
+ * | escaped `contains` | escaped by hand | `query/build.ts` |
270
+ * | relations always named | named only if declared | `schema/introspect.ts` |
271
+ *
272
+ * ## Relations are not loaded with the record
273
+ *
274
+ * The Prisma adapter includes a to-one's target so a list can show a person's
275
+ * name rather than their id. Drizzle can do the same with a join, but only with
276
+ * the relational query API, which needs `relations()` declared - and this
277
+ * adapter deliberately works without them. So a to-one arrives as its foreign
278
+ * key, and the interface resolves the label through the relation picker, which
279
+ * it already does for every relation it cannot see inline.
280
+ */
281
+
282
+ interface DrizzleAdapterOptions {
283
+ /** A constructed Drizzle database, from any dialect's `drizzle()`. */
284
+ readonly db: unknown;
285
+ /**
286
+ * The schema module.
287
+ *
288
+ * Passed separately from `db` even though `drizzle(client, { schema })` also
289
+ * takes it, because that form is optional and a database built without it
290
+ * carries nothing to introspect.
291
+ */
292
+ readonly schema: Readonly<Record<string, unknown>>;
293
+ }
294
+ declare class DrizzleAdapter implements OrmAdapter {
295
+ #private;
296
+ readonly name = "drizzle";
297
+ constructor(options: DrizzleAdapterOptions);
298
+ getModels(): Promise<readonly ModelMetadata[]>;
299
+ list(model: string, query: ListQuery): Promise<Page<RecordData>>;
300
+ findOne(model: string, id: RecordId): Promise<RecordData | null>;
301
+ create(model: string, data: RecordData): Promise<RecordData>;
302
+ update(model: string, id: RecordId, data: RecordData): Promise<RecordData>;
303
+ delete(model: string, id: RecordId): Promise<void>;
304
+ listRelated(model: string, id: RecordId, relationField: string, query: ListQuery): Promise<Page<RecordData>>;
305
+ attachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
306
+ detachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
307
+ }
308
+
309
+ /**
310
+ * `ListQuery` into a Drizzle query.
311
+ *
312
+ * The rules enforced here are the ones the Prisma adapter enforces, and they
313
+ * are enforced again rather than shared because they are about *this* ORM's
314
+ * capabilities: which fields can be filtered, which operators a kind admits,
315
+ * what a search box searches. Where the two adapters agree, they agree because
316
+ * Core's contract says the same thing to both.
317
+ *
318
+ * ## Two places Drizzle needs work Prisma did for us
319
+ *
320
+ * **Case insensitivity.** Prisma has `mode: 'insensitive'`, on the providers
321
+ * that support it. Drizzle has `ilike`, on Postgres only. Rather than branch per
322
+ * dialect, both sides of the comparison go through `lower()`, which every
323
+ * dialect this adapter supports has. It costs an index unless one is declared on
324
+ * the expression - noted here because that is a real trade and not a free one.
325
+ *
326
+ * **`LIKE` metacharacters.** Prisma escapes `%` and `_` inside `contains`.
327
+ * Building the pattern by hand means doing it here, or a search for `100%`
328
+ * silently matches every row.
329
+ */
330
+
331
+ /** Kept in step with `MAX_PER_PAGE` in the Prisma adapter and the UI's page-size list. */
332
+ declare const DEFAULT_PER_PAGE = 25;
333
+ declare const MAX_PER_PAGE = 100;
334
+
335
+ export { DEFAULT_PER_PAGE, DrizzleAdapter, type DrizzleAdapterOptions, MAX_PER_PAGE };