UNPKG

kitcn

Version:

kitcn - React Query integration and CLI tools for Convex

156 lines (151 loc) 5.5 kB
//#region src/auth/create-schema.ts const indexFields = { account: [ "accountId", ["accountId", "providerId"], ["providerId", "userId"] ], oauthConsent: [["clientId", "userId"]], passkey: ["credentialID"], ratelimit: ["key"], rateLimit: ["key"], session: ["expiresAt", ["expiresAt", "userId"]], user: [["email", "name"], "name"], verification: ["expiresAt", "identifier"] }; const cloneTables = (tables) => Object.fromEntries(Object.entries(tables).map(([key, table]) => [key, { ...table, fields: { ...table.fields } }])); const ensureField = (tables, tableKey, fieldKey, fieldPatch) => { const table = tables[tableKey]; if (!table) return; const existingField = table.fields[fieldKey]; if (!existingField) { table.fields[fieldKey] = fieldPatch; return; } table.fields[fieldKey] = { ...fieldPatch, ...existingField, references: existingField.references ?? fieldPatch.references }; }; const augmentBetterAuthTables = (sourceTables) => { const tables = cloneTables(sourceTables); if (tables.organization && tables.user) { const organizationReference = { field: "id", model: "organization" }; ensureField(tables, "user", "lastActiveOrganizationId", { references: organizationReference, required: false, type: "string" }); ensureField(tables, "user", "personalOrganizationId", { references: organizationReference, required: false, type: "string" }); ensureField(tables, "session", "activeOrganizationId", { references: organizationReference, required: false, type: "string" }); } if (tables.team) { const teamReference = { field: "id", model: "team" }; ensureField(tables, "session", "activeTeamId", { references: teamReference, required: false, type: "string" }); ensureField(tables, "invitation", "teamId", { references: teamReference, required: false, type: "string" }); } return tables; }; const specialFields = (tables) => Object.fromEntries(Object.entries(tables).map(([key, table]) => { return [key, Object.fromEntries(Object.entries(table.fields).map(([fieldKey, field]) => [field.fieldName ?? fieldKey, { ...field.sortable ? { sortable: true } : {}, ...field.unique ? { unique: true } : {}, ...field.references ? { references: field.references } : {} }]).filter(([_key, value]) => typeof value === "object" ? Object.keys(value).length > 0 : true))]; }).filter(([_key, value]) => typeof value === "object" ? Object.keys(value).length > 0 : true)); const mergedIndexFields = (tables) => Object.fromEntries(Object.entries(tables).map(([key, table]) => { const resolveIndexField = (fieldKey) => { const field = table.fields[fieldKey]; return field ? field.fieldName ?? fieldKey : null; }; const manualIndexes = indexFields[key]?.reduce((indexes, index) => { if (typeof index === "string") { const resolved = resolveIndexField(index); if (resolved) indexes.push(resolved); return indexes; } const resolved = index.map((fieldKey) => resolveIndexField(fieldKey)).filter((fieldName) => fieldName !== null); if (resolved.length === index.length) indexes.push(resolved); return indexes; }, []) || []; const specialFieldIndexes = Object.keys(specialFields(tables)[key] || {}).filter((index) => !manualIndexes.some((m) => Array.isArray(m) ? m[0] === index : m === index)); return [key, manualIndexes.concat(specialFieldIndexes)]; })); const createSchema = async ({ exportName = "tables", file, regenerateCommand, tables }) => { const path = await import(Buffer.from("cGF0aA==", "base64").toString()); if (path.basename(path.resolve(process.cwd(), file ?? "")) === "convex") throw new Error("Better Auth schema must be generated in the Better Auth component directory."); tables = augmentBetterAuthTables(tables); let code = `// This file is auto-generated. Do not edit this file manually. // To regenerate the schema, run: // \`${regenerateCommand ?? `npx @better-auth/cli generate --output ${file} -y`}\` import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export const ${exportName} = { `; for (const [tableKey, table] of Object.entries(tables)) { const modelName = table.modelName; const fields = Object.fromEntries(Object.entries(table.fields).filter(([key]) => key !== "id")); function getType(_name, field) { const type = field.type; return { boolean: "v.boolean()", date: "v.number()", json: "v.string()", number: "v.number()", "number[]": "v.array(v.number())", string: "v.string()", "string[]": "v.array(v.string())" }[type]; } const indexes = mergedIndexFields(tables)[tableKey]?.map((index) => { const indexArray = Array.isArray(index) ? index.sort() : [index]; return `.index("${indexArray.join("_")}", ${JSON.stringify(indexArray)})`; }) || []; const schema = `${modelName}: defineTable({ ${Object.keys(fields).map((field) => { const attr = fields[field]; const type = getType(field, attr); const optional = (fieldSchema) => attr.required ? fieldSchema : `v.optional(v.union(v.null(), ${fieldSchema}))`; return ` ${attr.fieldName ?? field}: ${optional(type)},`; }).join("\n")} })${indexes.length > 0 ? `\n ${indexes.join("\n ")}` : ""},\n`; code += ` ${schema}`; } code += `}; const schema = defineSchema(${exportName}); export default schema; `; return { code, overwrite: true, path: file ?? "./schema.ts" }; }; //#endregion export { augmentBetterAuthTables, createSchema, indexFields };