UNPKG

@compas/code-gen

Version:

Generate various boring parts of your server

184 lines (165 loc) 5.43 kB
import { AppError, isNil } from "@compas/stdlib"; import { DateType, UuidType } from "../../builders/index.js"; import { errorsThrowCombinedError } from "../errors.js"; import { stringFormatNameForError } from "../string-format.js"; import { structureModels } from "./models.js"; import { referenceUtilsGetProperty } from "./reference-utils.js"; /** * Cache primary key lookups, based on the model. * * @type {WeakMap<object, { * primaryKeyName: string, * primaryKeyDefinition: import("../generated/common/types").ExperimentalTypeDefinition * }>} */ const primaryKeyCache = new WeakMap(); /** * Cache searchable keys, based on the model. * * @type {WeakMap<object, string[]>} */ const searchableKeyCache = new WeakMap(); /** * Add or resolve the primary key of a model. * * It tries to find the existing primary key. If it can't find any, we check if we may * add one ('withPrimaryKey: true'). If we also may not add a primary key, we throw an * error. * * @param {import("../generate").GenerateContext} generateContext */ export function modelKeyAddPrimary(generateContext) { /** * @type {import("@compas/stdlib").AppError[]} */ const errors = []; for (const model of structureModels(generateContext)) { try { const { primaryKeyDefinition, primaryKeyName } = modelKeyGetPrimary(model); if (!["uuid", "number", "string"].includes(primaryKeyDefinition.type)) { throw AppError.serverError({ message: `The primary key of '${model.name}' has an invalid type. Expected 'T.uuid().primary()', 'T.number().primary' or 'T.string().primary()'. Found '${primaryKeyDefinition.type}' (at field '${primaryKeyName}'). The generator is able to auto-create a primary key if none is provided.`, }); } } catch (/** @type {any} */ error) { if ( model.queryOptions?.withPrimaryKey === false || !isNil(model.keys.id) ) { errors.push(error); } else { model.keys["id"] = new UuidType() .primary() .docs(`The primary key of the '${model.name}' model.`) .build(); } } } return errorsThrowCombinedError(errors); } /** * Add default date keys to the structureModels. * * Could add `createdAt`, `updatedAt` and `deletedAt` fields. * * @param {import("../generate").GenerateContext} generateContext */ export function modelKeyAddDateKeys(generateContext) { for (const model of structureModels(generateContext)) { // Add date fields if necessary if (model.queryOptions?.withDates || model.queryOptions?.withSoftDeletes) { model.keys["createdAt"] = new DateType() .sqlDefault() .searchable() .docs( "Automatically generated 'createdAt', containing an ISO timestamp.", ) .build(); model.keys["updatedAt"] = new DateType() .sqlDefault() .searchable() .docs( "Automatically generated 'updatedAt', containing an ISO timestamp.", ) .build(); } if (model.queryOptions?.withSoftDeletes) { model.keys["deletedAt"] = new DateType() .optional() .searchable() .docs( "Automatically generated 'deletedAt', containing an ISO timestamp. This is by default filtered on in 'select' queries.", ) .build(); } } } /** * Get the primary key information of a model. * * The result is cached and early returned in the next call with the same model. * * @param {import("../generated/common/types").ExperimentalObjectDefinition} model * @returns {{ * primaryKeyName: string, * primaryKeyDefinition: * import("../generated/common/types").ExperimentalTypeSystemDefinition * }} */ export function modelKeyGetPrimary(model) { if (primaryKeyCache.has(model)) { // @ts-expect-error // // This pretty much can't go wrong... // One of the reasons why I don't use TS. return primaryKeyCache.get(model); } for (const key of Object.keys(model.keys)) { const value = model.keys[key]; if (value.sql?.primary) { const result = { primaryKeyName: key, primaryKeyDefinition: value, }; primaryKeyCache.set(model, result); return result; } } throw AppError.serverError({ message: `Could not resolve the primary key of ${stringFormatNameForError( model, )}. Either add a primary key to the definition like: 'T.uuid().primary()' or remove 'withPrimaryKey: false' from the '.enableQueries()' call.`, }); } /** * Get the searchable model keys for the provided model. * * The result is cached and early returned in the next call with the same model. * * @param {import("../generate").GenerateContext} generateContext * @param {import("../generated/common/types").ExperimentalObjectDefinition} model * @returns {string[]} */ export function modelKeyGetSearchable(generateContext, model) { if (searchableKeyCache.has(model)) { // @ts-expect-error // // This pretty much can't go wrong... // One of the reasons why I don't use TS. return searchableKeyCache.get(model); } const fields = []; for (const key of Object.keys(model.keys)) { if ( referenceUtilsGetProperty(generateContext, model.keys[key], [ "sql", "searchable", ]) ) { fields.push(key); } } searchableKeyCache.set(model, fields); return fields; }