From 83e11a985e47349b5bc6e5fb07a9a596701dfab3 Mon Sep 17 00:00:00 2001 From: Rick Lubbers Date: Mon, 26 Jan 2026 07:35:53 +0100 Subject: [PATCH] Cache the InputType and OutputType which improves compilation performance --- src/main.ts | 42 ++++++++++++++++++++--------------------- src/types/InputType.ts | 16 ++++++++++------ src/types/OutputType.ts | 17 +++++++++++------ 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/main.ts b/src/main.ts index f8ea0c5..48ad404 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,10 +13,10 @@ import { mergeSql, sql, SqlStatement } from './helpers/sql.js'; import { RelationCache } from './transaction/RelationCache.js'; import { BaseEntityDefinitions } from './types/BaseEntityDefinitions.js'; import { EntityByName, EntityDefinition, EntityName } from './types/EntityTypes.js'; -import { OutputType } from './types/OutputType.js'; +import { OutputTypeByName } from './types/OutputType.js'; import { camelCaseToSnakeCase, snakeCaseToCamelCase, suffixId } from './util/strings.js'; import { validateSchema as validateSchemaActual } from './tools/validateSchema.js'; -import { InputType } from './types/InputType.js'; +import { InputTypeByName } from './types/InputType.js'; import { RawSqlType } from './types/RawSqlType.js'; import { isOrmRelationGetter, ormRelationGetter } from './relations/ormRelationGetter.js'; import { isNullable } from './entities/Nullable.js'; @@ -50,15 +50,15 @@ type TransactionControls = { }; type DbFunctions = { - findOne: >(entityName: N, id: string) => Promise>>, - findOneOrNull: >(entityName: N, id: string) => Promise> | null>, - findByIds: >(entityName: N, ids: string[]) => Promise>[]>, - findMany: >(entityName: N, options: FindManyOptions>) => Promise>[]>, + findOne: >(entityName: N, id: string) => Promise[N]>, + findOneOrNull: >(entityName: N, id: string) => Promise[N] | null>, + findByIds: >(entityName: N, ids: string[]) => Promise[N][]>, + findMany: >(entityName: N, options: FindManyOptions>) => Promise[N][]>, count: >(entityName: N, options?: { where?: WhereClause> }) => Promise, - findManyAndCount: >(entityName: N, options: FindManyOptions>) => Promise<{ results: OutputType>[], total: number }>, + findManyAndCount: >(entityName: N, options: FindManyOptions>) => Promise<{ results: OutputTypeByName[N][], total: number }>, nativeQuery: (sqlStatement: SqlStatement) => Promise, - saveOne: >(entityName: N, entity: InputType>) => Promise>>, - saveMany: >(entityName: N, entities: InputType>[]) => Promise>[]>, + saveOne: >(entityName: N, entity: InputTypeByName[N]) => Promise[N]>, + saveMany: >(entityName: N, entities: InputTypeByName[N][]) => Promise[N][]>, deleteByIds: >(entityName: N, ids: string[]) => Promise, deleteMany: >(entityName: N, options: { where: WhereClause> }) => Promise, validateSchema: (schemaName?: string) => Promise, @@ -212,7 +212,7 @@ export class Farstorm extends EventEmitt * This takes the raw SQL result and converts it into a proper OutputType for the entity * This function is responsible for putting in the Promise getters which will actually fetch any relations */ - const createOutputTypeFromRawSqlType = >(entityName: N, result: RawSqlType>): OutputType> => { + const createOutputTypeFromRawSqlType = >(entityName: N, result: RawSqlType>): OutputTypeByName[N] => { const entityDefinition = this.entityDefinitions[entityName]; const output: Record = {}; // Realistically the type is way stricter, more like Record> | RelationNames>, any> @@ -332,7 +332,7 @@ export class Farstorm extends EventEmitt Object.defineProperty(output, relationName, { enumerable: true, configurable: true, get: getOneToMany }); } - return output as OutputType>; + return output as OutputTypeByName[N]; }; /** @@ -479,7 +479,7 @@ export class Farstorm extends EventEmitt * User facing function, fetches a single entity from the database * This function will throw if the entity is not found */ - async function findOne>(entityName: N, id: string): Promise>> { + async function findOne>(entityName: N, id: string): Promise[N]> { if (transactionControls == null) throw new OrmError('ORM-1000', { entity: entityName as string, operation: 'findOne' }); const rows = await nativeQuery({ sql: `select * from "${camelCaseToSnakeCase(entityName as string)}" where "id" = $1`, params: [ id ] }); @@ -495,7 +495,7 @@ export class Farstorm extends EventEmitt /** * Fetches a single entity from the database, but returns null if the entity is not found */ - async function findOneOrNull>(entityName: N, id: string): Promise> | null> { + async function findOneOrNull>(entityName: N, id: string): Promise[N] | null> { if (transactionControls == null) throw new OrmError('ORM-1000', { entity: entityName as string, operation: 'findOneOrNull' }); const rows = await nativeQuery({ sql: `select * from "${camelCaseToSnakeCase(entityName as string)}" where "id" = $1`, params: [ id ] }); @@ -505,7 +505,7 @@ export class Farstorm extends EventEmitt // Update the loaded entities cache updateCacheWithNewEntities(entityName, rows); - return createOutputTypeFromRawSqlType(entityName, rows[0]) as OutputType>; + return createOutputTypeFromRawSqlType(entityName, rows[0]) as OutputTypeByName[N]; } /** @@ -532,13 +532,13 @@ export class Farstorm extends EventEmitt // Get rows in order of ids passed in const orderedRows = ids.map(id => rowsById[id]); - return orderedRows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputType>[]; + return orderedRows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputTypeByName[N][]; } /** * Fetches multiple entities from the database */ - async function findMany>(entityName: N, options?: FindManyOptions>): Promise>[]> { + async function findMany>(entityName: N, options?: FindManyOptions>): Promise[N][]> { if (transactionControls == null) throw new OrmError('ORM-1000', { entity: entityName as string, operation: 'findMany' }); const empty: SqlStatement = { sql: '', params: [] }; @@ -555,7 +555,7 @@ export class Farstorm extends EventEmitt updateCacheWithNewEntities(entityName, rows); // Output the fetched entities as full entity objects - return rows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputType>[]; + return rows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputTypeByName[N][]; } /** @@ -578,7 +578,7 @@ export class Farstorm extends EventEmitt * Finds both a limited amount of entities and the total amount of entities that match the where clause * This can be useful in paginated contexts= */ - async function findManyAndCount>(entityName: N, options?: FindManyOptions>): Promise<{ results: OutputType>[], total: number }> { + async function findManyAndCount>(entityName: N, options?: FindManyOptions>): Promise<{ results: OutputTypeByName[N][], total: number }> { const results = await findMany(entityName, options); const total = await count(entityName, options == null ? undefined : { where: options?.where }); return { results, total }; @@ -604,7 +604,7 @@ export class Farstorm extends EventEmitt /** * Saves a single entity */ - async function saveOne>(entityName: N, entity: InputType>): Promise>> { + async function saveOne>(entityName: N, entity: InputTypeByName[N]): Promise[N]> { const results = await saveMany(entityName, [ entity ]); if (results.length < 1) throw new OrmError('ORM-1300', { entity: entityName as string, operation: 'saveOne' }, queryStatistics.queries); return results[0]; @@ -615,7 +615,7 @@ export class Farstorm extends EventEmitt * Should always accept OutputType or OutputType[] as inputs so we can always fetch and immediately save the result of a findOne/findMany * In addition, we should define various input types to allow for updates from manually created objects that satisfy the shape of the entity */ - const saveMany = async >(entityName: N, entities: InputType>[]): Promise>[]> => { + const saveMany = async >(entityName: N, entities: InputTypeByName[N][]): Promise[N][]> => { if (transactionControls == null) throw new OrmError('ORM-1000', { entity: entityName as string, operation: 'saveMany' }); const entityDefinition = this.entityDefinitions[entityName]; const tableName = camelCaseToSnakeCase(entityName as string); @@ -843,7 +843,7 @@ export class Farstorm extends EventEmitt }); } - return rows.map(row => createOutputTypeFromRawSqlType(entityName, row)) as OutputType>[]; + return rows.map(row => createOutputTypeFromRawSqlType(entityName, row)) as OutputTypeByName[N][]; }; function prepValueForPgFormat(input: any): any { diff --git a/src/types/InputType.ts b/src/types/InputType.ts index 1752352..f94a8c0 100644 --- a/src/types/InputType.ts +++ b/src/types/InputType.ts @@ -1,23 +1,27 @@ import { IsNullable } from "../entities/Nullable.js"; import { BaseEntityDefinitions } from "./BaseEntityDefinitions.js"; -import { EntityByName, EntityDefinition } from "./EntityTypes.js"; +import { EntityDefinition } from "./EntityTypes.js"; import { EvalGeneric } from "./EvalGeneric.js"; import { FieldType } from "./FieldTypes.js"; -import { OutputTypeRef } from "./OutputType.js"; +import { OutputTypeRefByName } from "./OutputType.js"; import { WithOptionalId } from "./WithOptionalId.js"; +export type InputTypeByName = { + [N in keyof ED]: InputType +}; + // Define input type for save functions type InputTypeFields> = { -readonly [N in keyof E['fields'] as E['fields'][N]['nullableOnInput'] extends IsNullable ? never : N]: FieldType } // mandatory properties & { -readonly [N in keyof E['fields'] as E['fields'][N]['nullableOnInput'] extends IsNullable ? N : never]?: FieldType | null | undefined }; // optionals type InputTypeOneToOneOwned> = - { -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRef> } // mandatory properties - & { -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRef> | null | undefined }; // optionals + { -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRefByName } // mandatory properties + & { -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRefByName | null | undefined }; // optionals type InputTypeManyToOne> = - { -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRef> } // mandatory properties - & { -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRef> | null | undefined }; // optionals + { -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRefByName } // mandatory properties + & { -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRefByName | null | undefined }; // optionals export type InputTypeWithId> = InputTypeFields & InputTypeOneToOneOwned & InputTypeManyToOne & { diff --git a/src/types/OutputType.ts b/src/types/OutputType.ts index c760e4e..a01be85 100644 --- a/src/types/OutputType.ts +++ b/src/types/OutputType.ts @@ -1,22 +1,27 @@ import { BaseEntityDefinitions } from "./BaseEntityDefinitions.js"; -import { EntityByName, EntityDefinition } from "./EntityTypes.js"; +import { EntityDefinition } from "./EntityTypes.js"; import { EvalGeneric } from "./EvalGeneric.js"; import { FieldNullNever, FieldType } from "./FieldTypes.js"; import { IsNullable } from "./IsNullable.js"; +export type OutputTypeByName = { + [N in keyof ED]: OutputType +}; + // Prep the output type for a specific entity // Defines a type for every field on a given entity, including correctly nullability setting // Defines the types for all relations, which will something like Promise or Promise, depending on the relation type, with correct nullability export type OutputType> = EvalGeneric<{ -readonly [N in keyof E['fields']]: FieldType | FieldNullNever } & { - -readonly [N in keyof E['oneToOneOwned']]: Promise>> | IsNullable + -readonly [N in keyof E['oneToOneOwned']]: Promise[E['oneToOneOwned'][N]['entity']]> | IsNullable } & { - -readonly [N in keyof E['oneToOneInverse']]: Promise> | IsNullable> + -readonly [N in keyof E['oneToOneInverse']]: Promise[E['oneToOneInverse'][N]['entity']] | IsNullable> } & { - -readonly [N in keyof E['manyToOne']]: Promise>> | IsNullable + -readonly [N in keyof E['manyToOne']]: Promise[E['manyToOne'][N]['entity']]> | IsNullable } & { - [N in keyof E['oneToMany']]: Promise>[]> + [N in keyof E['oneToMany']]: Promise[E['oneToMany'][N]['entity']][]> }>; -export type OutputTypeRef> = Promise> | OutputType; \ No newline at end of file +export type OutputTypeRef> = Promise> | OutputType; +export type OutputTypeRefByName = Promise[N]> | OutputTypeByName[N]; \ No newline at end of file