UNPKG

@compas/code-gen

Version:

Generate various boring parts of your server

262 lines (231 loc) 7.46 kB
// @ts-nocheck import { isNil } from "@compas/stdlib"; import { AnyOfType, AnyType, ArrayType, BooleanType, NumberType, ObjectType, StringType, } from "../../builders/index.js"; import { ReferenceType } from "../../builders/ReferenceType.js"; import { structureAddType } from "../../structure/structureAddType.js"; import { upperCaseFirst } from "../../utils.js"; import { js } from "../tag/index.js"; import { getTypeNameForType } from "../types.js"; import { getQueryEnabledObjects } from "./utils.js"; export const atomicUpdateFieldsTable = { boolean: ["$negate"], number: ["$add", "$subtract", "$multiply", "$divide"], string: ["$append"], date: ["$add", "$subtract"], jsonb: ["$set", "$remove"], }; /** * Creates an update type and assigns in to the object type for all query enabled objects. * * @param {import("../../generated/common/types").CodeGenContext} context */ export function createUpdateTypes(context) { for (const type of getQueryEnabledObjects(context)) { if (type.queryOptions.isView) { continue; } const updateType = new ObjectType(type.group, `${type.name}Update`).build(); updateType.uniqueName = `${upperCaseFirst(type.group)}${upperCaseFirst( type.name, )}Update`; const updatePartialType = new ObjectType( type.group, `${type.name}UpdatePartial`, ).build(); updatePartialType.uniqueName = `${upperCaseFirst( type.group, )}${upperCaseFirst(type.name)}UpdatePartial`; for (const key of Object.keys(type.keys)) { let fieldType = type.keys[key]; if (fieldType.reference) { fieldType = fieldType.reference; } if (fieldType.sql?.primary || type.keys[key].sql?.primary) { continue; } updatePartialType.keys[key] = { ...new AnyOfType().values(true).optional().build(), values: [ { ...fieldType, isOptional: true, validator: { ...(fieldType?.validator ?? {}), allowNull: fieldType.isOptional && isNil(fieldType.defaultValue) && isNil(type.keys[key].defaultValue), }, }, ], }; if (fieldType.type === "number") { for (const atomicKey of atomicUpdateFieldsTable.number) { const atomicType = new ObjectType() .keys({ [atomicKey]: fieldType.validator.floatingPoint ? new NumberType().float() : new NumberType(), }) .build(); updatePartialType.keys[key].values.push(atomicType); } } else if (fieldType.type === "date" || fieldType.type === "string") { for (const atomicKey of atomicUpdateFieldsTable[fieldType.type]) { const atomicType = new ObjectType() .keys({ [atomicKey]: new StringType(), }) .build(); updatePartialType.keys[key].values.push(atomicType); } } else if (fieldType.type === "boolean") { for (const atomicKey of atomicUpdateFieldsTable.boolean) { const atomicType = new ObjectType() .keys({ [atomicKey]: new BooleanType(), }) .build(); updatePartialType.keys[key].values.push(atomicType); } } else if ( ["any", "anyOf", "array", "generic", "object"].includes(fieldType.type) ) { const pathType = new ArrayType().values( new AnyOfType().values(new NumberType(), new StringType()), ); updatePartialType.keys[key].values.push( new ObjectType() .keys({ $set: new ObjectType().keys({ path: pathType, value: new AnyType(), }), }) .build(), new ObjectType() .keys({ $remove: new ObjectType().keys({ path: pathType, }), }) .build(), ); } } structureAddType(context.structure, updatePartialType); updateType.keys.update = new ReferenceType( type.group, `${type.name}UpdatePartial`, ).build(); updateType.keys.update.reference = context.structure[type.group][`${type.name}UpdatePartial`]; updateType.keys.where = new ReferenceType( type.group, `${type.name}Where`, ).build(); updateType.keys.where.reference = context.structure[type.group][`${type.name}Where`]; updateType.keys.returning = new AnyOfType() .values( new StringType().oneOf("*"), new ArrayType().values( new StringType().oneOf(...Object.keys(type.keys)), ), ) .optional() .build(); structureAddType(context.structure, updateType); type.partial = type.partial ?? {}; type.partial.updateType = getTypeNameForType(context, updateType, "", { useDefaults: false, }); const updateFnType = new AnyType(type.group, `${type.name}UpdateFn`) .raw( `<I extends ${type.uniqueName}Update>( sql: import("@compas/store").Postgres, input: I, ) => Promise< import("@compas/store").Returning<${type.uniqueName}, I["returning"]> >`, ) .build(); updateFnType.uniqueName = `${type.uniqueName}UpdateFn`; structureAddType(context.structure, updateFnType); } } /** * * @param {import("../../generated/common/types").CodeGenContext} context * @param {ImportCreator} imports * @param {CodeGenObjectType} type */ export function getUpdateQuery(context, imports, type) { let entityUpdateSpec = `export const ${type.name}UpdateSpec = { "schemaName": \`${type.queryOptions.schema}\`, "name": "${type.name}", "shortName": "${type.shortName}", "columns": [${Object.keys(type.keys) .map((it) => `"${it}"`) .join(", ")}], "where": ${type.name}WhereSpec, "injectUpdatedAt": ${ type.queryOptions?.withDates || type.queryOptions?.withSoftDeletes }, "fields": {`; for (const key of Object.keys(type.keys)) { let fieldType = type.keys[key]; if (fieldType.reference) { fieldType = fieldType.reference; } const subType = ["number", "boolean", "string", "date", "uuid"].includes( fieldType.type, ) ? fieldType.type : "jsonb"; entityUpdateSpec += `"${key}": { "type": "${subType}", "atomicUpdates": [${( atomicUpdateFieldsTable[subType] ?? [] ) .map((it) => `"${it}"`) .join(", ")}], },`; } entityUpdateSpec += " }};"; imports.destructureImport( `validate${type.uniqueName}Update`, `../${type.group}/validators.js`, ); return js` /** @type {any} */ ${entityUpdateSpec} /** * (Atomic) update queries for ${type.name} * * @type {${type.uniqueName}UpdateFn} */ const ${type.name}Update = async (sql, input) => { const updateValidated = validate${type.uniqueName}Update( input, "$.${type.uniqueName}Update"); if (updateValidated.error) { throw updateValidated.error; } const result = await generatedUpdateHelper(${ type.name }UpdateSpec, input).exec(sql); if (!isNil(input.returning)) { transform${upperCaseFirst(type.name)}(result); // @ts-ignore return result; } // @ts-ignore return undefined; } `; }