Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -50,15 +50,15 @@ type TransactionControls = {
};

type DbFunctions<ED extends BaseEntityDefinitions> = {
findOne: <N extends EntityName<ED>>(entityName: N, id: string) => Promise<OutputType<ED, EntityByName<ED, N>>>,
findOneOrNull: <N extends EntityName<ED>>(entityName: N, id: string) => Promise<OutputType<ED, EntityByName<ED, N>> | null>,
findByIds: <N extends EntityName<ED>>(entityName: N, ids: string[]) => Promise<OutputType<ED, EntityByName<ED, N>>[]>,
findMany: <N extends EntityName<ED>>(entityName: N, options: FindManyOptions<ED, EntityByName<ED, N>>) => Promise<OutputType<ED, EntityByName<ED, N>>[]>,
findOne: <N extends EntityName<ED>>(entityName: N, id: string) => Promise<OutputTypeByName<ED>[N]>,
findOneOrNull: <N extends EntityName<ED>>(entityName: N, id: string) => Promise<OutputTypeByName<ED>[N] | null>,
findByIds: <N extends EntityName<ED>>(entityName: N, ids: string[]) => Promise<OutputTypeByName<ED>[N][]>,
findMany: <N extends EntityName<ED>>(entityName: N, options: FindManyOptions<ED, EntityByName<ED, N>>) => Promise<OutputTypeByName<ED>[N][]>,
count: <N extends EntityName<ED>>(entityName: N, options?: { where?: WhereClause<ED, EntityByName<ED, N>> }) => Promise<number>,
findManyAndCount: <N extends EntityName<ED>>(entityName: N, options: FindManyOptions<ED, EntityByName<ED, N>>) => Promise<{ results: OutputType<ED, EntityByName<ED, N>>[], total: number }>,
findManyAndCount: <N extends EntityName<ED>>(entityName: N, options: FindManyOptions<ED, EntityByName<ED, N>>) => Promise<{ results: OutputTypeByName<ED>[N][], total: number }>,
nativeQuery: (sqlStatement: SqlStatement) => Promise<any[]>,
saveOne: <N extends EntityName<ED>>(entityName: N, entity: InputType<ED, EntityByName<ED, N>>) => Promise<OutputType<ED, EntityByName<ED, N>>>,
saveMany: <N extends EntityName<ED>>(entityName: N, entities: InputType<ED, EntityByName<ED, N>>[]) => Promise<OutputType<ED, EntityByName<ED, N>>[]>,
saveOne: <N extends EntityName<ED>>(entityName: N, entity: InputTypeByName<ED>[N]) => Promise<OutputTypeByName<ED>[N]>,
saveMany: <N extends EntityName<ED>>(entityName: N, entities: InputTypeByName<ED>[N][]) => Promise<OutputTypeByName<ED>[N][]>,
deleteByIds: <N extends EntityName<ED>>(entityName: N, ids: string[]) => Promise<void>,
deleteMany: <N extends EntityName<ED>>(entityName: N, options: { where: WhereClause<ED, EntityByName<ED, N>> }) => Promise<void>,
validateSchema: (schemaName?: string) => Promise<SchemaValidationResult>,
Expand Down Expand Up @@ -212,7 +212,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> 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 = <N extends EntityName<ED>>(entityName: N, result: RawSqlType<ED, EntityByName<ED, N>>): OutputType<ED, EntityByName<ED, N>> => {
const createOutputTypeFromRawSqlType = <N extends EntityName<ED>>(entityName: N, result: RawSqlType<ED, EntityByName<ED, N>>): OutputTypeByName<ED>[N] => {
const entityDefinition = this.entityDefinitions[entityName];
const output: Record<string, any> = {}; // Realistically the type is way stricter, more like Record<FieldNames<EntityByName<N>> | RelationNames<EntityByName<N>>, any>

Expand Down Expand Up @@ -332,7 +332,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
Object.defineProperty(output, relationName, { enumerable: true, configurable: true, get: getOneToMany });
}

return output as OutputType<ED, EntityByName<ED, N>>;
return output as OutputTypeByName<ED>[N];
};

/**
Expand Down Expand Up @@ -479,7 +479,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> 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<N extends EntityName<ED>>(entityName: N, id: string): Promise<OutputType<ED, EntityByName<ED, N>>> {
async function findOne<N extends EntityName<ED>>(entityName: N, id: string): Promise<OutputTypeByName<ED>[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 ] });
Expand All @@ -495,7 +495,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
/**
* Fetches a single entity from the database, but returns null if the entity is not found
*/
async function findOneOrNull<N extends EntityName<ED>>(entityName: N, id: string): Promise<OutputType<ED, EntityByName<ED, N>> | null> {
async function findOneOrNull<N extends EntityName<ED>>(entityName: N, id: string): Promise<OutputTypeByName<ED>[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 ] });
Expand All @@ -505,7 +505,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
// Update the loaded entities cache
updateCacheWithNewEntities(entityName, rows);

return createOutputTypeFromRawSqlType(entityName, rows[0]) as OutputType<ED, EntityByName<ED, N>>;
return createOutputTypeFromRawSqlType(entityName, rows[0]) as OutputTypeByName<ED>[N];
}

/**
Expand All @@ -532,13 +532,13 @@ export class Farstorm<const ED extends BaseEntityDefinitions> 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<ED, EntityByName<ED, N>>[];
return orderedRows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputTypeByName<ED>[N][];
}

/**
* Fetches multiple entities from the database
*/
async function findMany<N extends EntityName<ED>>(entityName: N, options?: FindManyOptions<ED, EntityByName<ED, N>>): Promise<OutputType<ED, EntityByName<ED, N>>[]> {
async function findMany<N extends EntityName<ED>>(entityName: N, options?: FindManyOptions<ED, EntityByName<ED, N>>): Promise<OutputTypeByName<ED>[N][]> {
if (transactionControls == null) throw new OrmError('ORM-1000', { entity: entityName as string, operation: 'findMany' });

const empty: SqlStatement = { sql: '', params: [] };
Expand All @@ -555,7 +555,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
updateCacheWithNewEntities(entityName, rows);

// Output the fetched entities as full entity objects
return rows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputType<ED, EntityByName<ED, N>>[];
return rows.map(r => createOutputTypeFromRawSqlType(entityName, r)) as OutputTypeByName<ED>[N][];
}

/**
Expand All @@ -578,7 +578,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> 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<N extends EntityName<ED>>(entityName: N, options?: FindManyOptions<ED, EntityByName<ED, N>>): Promise<{ results: OutputType<ED, EntityByName<ED, N>>[], total: number }> {
async function findManyAndCount<N extends EntityName<ED>>(entityName: N, options?: FindManyOptions<ED, EntityByName<ED, N>>): Promise<{ results: OutputTypeByName<ED>[N][], total: number }> {
const results = await findMany(entityName, options);
const total = await count(entityName, options == null ? undefined : { where: options?.where });
return { results, total };
Expand All @@ -604,7 +604,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
/**
* Saves a single entity
*/
async function saveOne<N extends EntityName<ED>>(entityName: N, entity: InputType<ED, EntityByName<ED, N>>): Promise<OutputType<ED, EntityByName<ED, N>>> {
async function saveOne<N extends EntityName<ED>>(entityName: N, entity: InputTypeByName<ED>[N]): Promise<OutputTypeByName<ED>[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];
Expand All @@ -615,7 +615,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
* Should always accept OutputType<E> or OutputType<E>[] 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 <N extends EntityName<ED>>(entityName: N, entities: InputType<ED, EntityByName<ED, N>>[]): Promise<OutputType<ED, EntityByName<ED, N>>[]> => {
const saveMany = async <N extends EntityName<ED>>(entityName: N, entities: InputTypeByName<ED>[N][]): Promise<OutputTypeByName<ED>[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);
Expand Down Expand Up @@ -843,7 +843,7 @@ export class Farstorm<const ED extends BaseEntityDefinitions> extends EventEmitt
});
}

return rows.map(row => createOutputTypeFromRawSqlType(entityName, row)) as OutputType<ED, EntityByName<ED, N>>[];
return rows.map(row => createOutputTypeFromRawSqlType(entityName, row)) as OutputTypeByName<ED>[N][];
};

function prepValueForPgFormat(input: any): any {
Expand Down
16 changes: 10 additions & 6 deletions src/types/InputType.ts
Original file line number Diff line number Diff line change
@@ -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<ED extends BaseEntityDefinitions> = {
[N in keyof ED]: InputType<ED, ED[N]>
};

// Define input type for save functions
type InputTypeFields<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> =
{ -readonly [N in keyof E['fields'] as E['fields'][N]['nullableOnInput'] extends IsNullable ? never : N]: FieldType<ED, E, N> } // mandatory properties
& { -readonly [N in keyof E['fields'] as E['fields'][N]['nullableOnInput'] extends IsNullable ? N : never]?: FieldType<ED, E, N> | null | undefined }; // optionals

type InputTypeOneToOneOwned<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> =
{ -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRef<ED, EntityByName<ED, E['oneToOneOwned'][N]['entity']>> } // mandatory properties
& { -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRef<ED, EntityByName<ED, E['oneToOneOwned'][N]['entity']>> | null | undefined }; // optionals
{ -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRefByName<ED, E['oneToOneOwned'][N]['entity']> } // mandatory properties
& { -readonly [N in keyof E['oneToOneOwned'] as E['oneToOneOwned'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRefByName<ED, E['oneToOneOwned'][N]['entity']> | null | undefined }; // optionals

type InputTypeManyToOne<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> =
{ -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRef<ED, EntityByName<ED, E['manyToOne'][N]['entity']>> } // mandatory properties
& { -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRef<ED, EntityByName<ED, E['manyToOne'][N]['entity']>> | null | undefined }; // optionals
{ -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? never : N]: OutputTypeRefByName<ED, E['manyToOne'][N]['entity']> } // mandatory properties
& { -readonly [N in keyof E['manyToOne'] as E['manyToOne'][N]['nullable'] extends IsNullable ? N : never]?: OutputTypeRefByName<ED, E['manyToOne'][N]['entity']> | null | undefined }; // optionals

export type InputTypeWithId<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> =
InputTypeFields<ED, E> & InputTypeOneToOneOwned<ED, E> & InputTypeManyToOne<ED, E> & {
Expand Down
17 changes: 11 additions & 6 deletions src/types/OutputType.ts
Original file line number Diff line number Diff line change
@@ -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<ED extends BaseEntityDefinitions> = {
[N in keyof ED]: OutputType<ED, ED[N]>
};

// 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<TargetEntity> or Promise<TargetEntity[]>, depending on the relation type, with correct nullability
export type OutputType<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> = EvalGeneric<{
-readonly [N in keyof E['fields']]: FieldType<ED, E, N> | FieldNullNever<ED, E, N>
} & {
-readonly [N in keyof E['oneToOneOwned']]: Promise<OutputType<ED, EntityByName<ED, E['oneToOneOwned'][N]['entity']>>> | IsNullable<E['oneToOneOwned'][N]['nullable']>
-readonly [N in keyof E['oneToOneOwned']]: Promise<OutputTypeByName<ED>[E['oneToOneOwned'][N]['entity']]> | IsNullable<E['oneToOneOwned'][N]['nullable']>
} & {
-readonly [N in keyof E['oneToOneInverse']]: Promise<OutputType<ED, EntityByName<ED, E['oneToOneInverse'][N]['entity']>> | IsNullable<E['oneToOneInverse'][N]['nullable']>>
-readonly [N in keyof E['oneToOneInverse']]: Promise<OutputTypeByName<ED>[E['oneToOneInverse'][N]['entity']] | IsNullable<E['oneToOneInverse'][N]['nullable']>>
} & {
-readonly [N in keyof E['manyToOne']]: Promise<OutputType<ED, EntityByName<ED, E['manyToOne'][N]['entity']>>> | IsNullable<E['manyToOne'][N]['nullable']>
-readonly [N in keyof E['manyToOne']]: Promise<OutputTypeByName<ED>[E['manyToOne'][N]['entity']]> | IsNullable<E['manyToOne'][N]['nullable']>
} & {
[N in keyof E['oneToMany']]: Promise<OutputType<ED, EntityByName<ED, E['oneToMany'][N]['entity']>>[]>
[N in keyof E['oneToMany']]: Promise<OutputTypeByName<ED>[E['oneToMany'][N]['entity']][]>
}>;

export type OutputTypeRef<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> = Promise<OutputType<ED, E>> | OutputType<ED, E>;
export type OutputTypeRef<ED extends BaseEntityDefinitions, E extends EntityDefinition<ED>> = Promise<OutputType<ED, E>> | OutputType<ED, E>;
export type OutputTypeRefByName<ED extends BaseEntityDefinitions, N extends keyof ED> = Promise<OutputTypeByName<ED>[N]> | OutputTypeByName<ED>[N];
Loading