1st pass done

This commit is contained in:
Alexandre Bodin 2023-09-15 09:54:49 +02:00
parent 4761ec70c8
commit 22ea624841
8 changed files with 116 additions and 77 deletions

View File

@ -4,6 +4,10 @@ import type { ID } from '../typings';
type Params = Record<string, unknown>;
type Data = Record<string, unknown>;
type Entity = {
id: ID;
[key: string]: any;
};
const withDefaultPagination = (params: Params) => {
const { page = 1, pageSize = 10, ...rest } = params;
@ -121,15 +125,15 @@ export const createRepository = (uid: string, db: Database) => {
return db.entityManager.cloneRelations(uid, targetId, sourceId, params);
},
populate(entity: Data, populate: unknown) {
populate(entity: Entity, populate: unknown) {
return db.entityManager.populate(uid, entity, populate);
},
load(entity: Data, fields: string[], params: Params) {
load(entity: Entity, fields: string[], params: Params) {
return db.entityManager.load(uid, entity, fields, params);
},
async loadPages(entity: Data, field: string[], params: Params) {
async loadPages(entity: Entity, field: string[], params: Params) {
if (!isString(field)) {
throw new Error(`Invalid load. Expected ${field} to be a string`);
}

View File

@ -63,6 +63,11 @@ type Params = {
orderBy?: any;
_q?: string;
data?: any;
page?: number;
pageSize?: number;
limit?: number;
offset?: number;
count?: boolean;
};
type Entity = {
@ -574,9 +579,10 @@ export const createEntityManager = (db: Database) => {
uid: string,
id: ID,
data: Record<string, any>,
{ transaction: trx }: { transaction: Knex.Transaction }
options?: { transaction?: Knex.Transaction }
) {
const { attributes } = db.metadata.get(uid);
const { transaction: trx } = options ?? {};
for (const attributeName of Object.keys(attributes)) {
const attribute = attributes[attributeName];
@ -806,13 +812,10 @@ export const createEntityManager = (db: Database) => {
uid: string,
id: ID,
data: any,
{
transaction: trx,
}: {
transaction: Knex.Transaction;
}
options?: { transaction?: Knex.Transaction }
) {
const { attributes } = db.metadata.get(uid);
const { transaction: trx } = options ?? {};
for (const attributeName of Object.keys(attributes)) {
const attribute = attributes[attributeName];
@ -1233,13 +1236,12 @@ export const createEntityManager = (db: Database) => {
async deleteRelations(
uid: string,
id: ID,
{
transaction: trx,
}: {
transaction: Knex.Transaction;
options?: {
transaction?: Knex.Transaction;
}
) {
const { attributes } = db.metadata.get(uid);
const { transaction: trx } = options ?? {};
for (const attributeName of Object.keys(attributes)) {
const attribute = attributes[attributeName];
@ -1367,15 +1369,13 @@ export const createEntityManager = (db: Database) => {
targetId: ID,
sourceId: ID,
data: any,
{
cloneAttrs = [],
transaction,
}: {
options?: {
cloneAttrs?: string[];
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}
) {
const { attributes } = db.metadata.get(uid);
const { cloneAttrs = [], transaction } = options ?? {};
if (!attributes) {
return;

View File

@ -48,7 +48,7 @@ export const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (
attributeName: string;
joinTable: MorphJoinTable;
db: Database;
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}
) => {
const { morphColumn } = joinTable;

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { randomBytes } from 'crypto';
import { map, isEmpty } from 'lodash/fp';
import type { Knex } from 'knex';
@ -16,6 +17,14 @@ import type { Database } from '..';
import type { ID } from '../typings';
import type { Relation } from '../metadata/types';
declare module 'knex' {
namespace Knex {
interface ChainableInterface {
transacting(trx?: Knex.Transaction): this;
}
}
}
/**
* If some relations currently exist for this oneToX relation, on the one side, this function removes them and update the inverse order if needed.
*/
@ -30,7 +39,7 @@ const deletePreviousOneToAnyRelations = async ({
attribute: Relation.Bidirectional;
relIdsToadd: ID[];
db: Database;
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
if (!(isBidirectional(attribute) && isOneToAny(attribute))) {
throw new Error(
@ -67,7 +76,7 @@ const deletePreviousAnyToOneRelations = async ({
attribute: Relation.Bidirectional;
relIdToadd: ID;
db: Database;
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
@ -132,7 +141,7 @@ const deleteRelations = async ({
db: Database;
relIdsToNotDelete?: ID[];
relIdsToDelete?: ID[] | 'all';
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
@ -203,7 +212,7 @@ const cleanOrderColumns = async ({
attribute: Relation.Bidirectional;
db: Database;
inverseRelIds?: ID[];
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
if (
!(hasOrderColumn(attribute) && id) &&
@ -356,7 +365,7 @@ const cleanOrderColumnsForOldDatabases = async ({
attribute: Relation.Bidirectional;
db: Database;
inverseRelIds: ID[];
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;

View File

@ -1,4 +1,4 @@
import type { Knex } from 'knex';
import { Knex } from 'knex';
import { cleanInverseOrderColumn } from '../../regular-relations';
import type { Relation } from '../../../metadata/types';
@ -15,7 +15,7 @@ const replaceRegularRelations = async ({
sourceId: ID;
attribute: Relation.Bidirectional;
omitIds: ID[];
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
const { joinTable } = attribute;
@ -42,7 +42,7 @@ const cloneRegularRelations = async ({
targetId: ID;
sourceId: ID;
attribute: Relation.Bidirectional;
transaction: Knex.Transaction;
transaction?: Knex.Transaction;
}) => {
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
@ -92,7 +92,7 @@ const cloneRegularRelations = async ({
await cleanInverseOrderColumn({
id: targetId,
attribute,
trx,
trx: trx as Knex.Transaction,
});
}
};

View File

@ -116,6 +116,8 @@ export namespace Relation {
export type OneToMany = BaseBidirectional & {
relation: 'oneToMany';
joinTable: OrderedJoinTable;
joinColumn?: JoinColumn;
owner?: boolean;
};
export type ManyToOne = BaseBidirectional & {

View File

@ -1,6 +1,6 @@
import _ from 'lodash/fp';
import { fromRow } from '../transform';
import { Rec, fromRow } from '../transform';
import type { QueryBuilder } from '../../query-builder';
import type { Database } from '../../..';
import type { Meta, RelationalAttribute, Relation } from '../../../metadata/types';
@ -15,8 +15,11 @@ type Context = {
type Input<T extends RelationalAttribute = RelationalAttribute> = {
attribute: T;
attributeName: string;
results: Record<string, unknown>[];
populateValue: Record<string, unknown>;
results: Row[];
populateValue: {
on?: Record<string, Record<string, unknown>>;
} & Record<string, unknown>;
isCount: boolean;
};
@ -24,7 +27,7 @@ type InputWithTarget<T extends RelationalAttribute = RelationalAttribute> = Inpu
targetMeta: Meta;
};
type MorphIdMap = Record<string, Record<ID, Record<string, unknown>[]>>;
type MorphIdMap = Record<string, Record<ID, Row[]>>;
type Row = Record<string, unknown>;
@ -41,7 +44,12 @@ const XtoOne = async (
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db, qb } = ctx;
const fromTargetRow = (rowOrRows: Row | Row[]) => fromRow(targetMeta, rowOrRows);
const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => {
if (!rowOrRows) {
return rowOrRows;
}
return fromRow(targetMeta, rowOrRows);
};
if ('joinColumn' in attribute && attribute.joinColumn) {
const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;
@ -63,12 +71,12 @@ const XtoOne = async (
.init(populateValue)
.addSelect(`${qb.alias}.${referencedColumnName}`)
.where({ [referencedColumnName]: referencedValues })
.execute<Record<string, unknown>[]>({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const map = _.groupBy<ID>(referencedColumnName)(rows);
const map = _.groupBy<Row[]>(referencedColumnName)(rows);
results.forEach((result) => {
result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName]]));
result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName] as string]));
});
return;
@ -144,12 +152,12 @@ const XtoOne = async (
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const map = _.groupBy(joinColumnName, rows);
const map = _.groupBy<Row>(joinColumnName)(rows);
results.forEach((result) => {
result[attributeName] = fromTargetRow(_.first(map[result[referencedColumnName]]));
result[attributeName] = fromTargetRow(_.first(map[result[referencedColumnName] as string]));
});
}
};
@ -158,7 +166,13 @@ const oneToMany = async (input: InputWithTarget<Relation.OneToMany>, ctx: Contex
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db, qb } = ctx;
const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows);
const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => {
if (!rowOrRows) {
return rowOrRows;
}
return fromRow(targetMeta, rowOrRows);
};
if ('joinColumn' in attribute && attribute.joinColumn) {
const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;
@ -179,12 +193,12 @@ const oneToMany = async (input: InputWithTarget<Relation.OneToMany>, ctx: Contex
.init(populateValue)
.addSelect(`${qb.alias}.${referencedColumnName}`)
.where({ [referencedColumnName]: referencedValues })
.execute({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const map = _.groupBy(referencedColumnName, rows);
const map = _.groupBy<Row>(referencedColumnName)(rows);
results.forEach((result) => {
result[attributeName] = fromTargetRow(map[result[joinColumnName]] || []);
result[attributeName] = fromTargetRow(map[result[joinColumnName] as string] || []);
});
return;
@ -259,12 +273,12 @@ const oneToMany = async (input: InputWithTarget<Relation.OneToMany>, ctx: Contex
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const map = _.groupBy(joinColumnName, rows);
const map = _.groupBy<Row>(joinColumnName)(rows);
results.forEach((r) => {
r[attributeName] = fromTargetRow(map[r[referencedColumnName]] || []);
r[attributeName] = fromTargetRow(map[r[referencedColumnName] as string] || []);
});
}
};
@ -273,7 +287,13 @@ const manyToMany = async (input: InputWithTarget<Relation.ManyToMany>, ctx: Cont
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db } = ctx;
const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows);
const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => {
if (!rowOrRows) {
return rowOrRows;
}
return fromRow(targetMeta, rowOrRows);
};
const { joinTable } = attribute;
@ -342,9 +362,9 @@ const manyToMany = async (input: InputWithTarget<Relation.ManyToMany>, ctx: Cont
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute<Record<string, unknown>[]>({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const map = _.groupBy(joinColumnName)(rows);
const map = _.groupBy<Row>(joinColumnName)(rows);
results.forEach((result) => {
result[attributeName] = fromTargetRow(map[result[referencedColumnName] as string] || []);
@ -358,8 +378,13 @@ const morphX = async (
const { attribute, attributeName, results, populateValue, targetMeta } = input;
const { db, uid } = ctx;
const fromTargetRow = (rowOrRows: Record<string, unknown> | Record<string, unknown>[]) =>
fromRow(targetMeta, rowOrRows);
const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => {
if (!rowOrRows) {
return rowOrRows;
}
return fromRow(targetMeta, rowOrRows);
};
const { target, morphBy } = attribute;
@ -385,12 +410,12 @@ const morphX = async (
.init(populateValue)
// .addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
.where({ [idColumn.name]: referencedValues, [typeColumn.name]: uid })
.execute({ mapResults: false });
.execute<Row>({ mapResults: false });
const map = _.groupBy(idColumn.name, rows);
const map = _.groupBy<Row>(idColumn.name)(rows);
results.forEach((result) => {
const matchingRows = map[result[idColumn.referencedColumn]];
const matchingRows = map[result[idColumn.referencedColumn] as string];
const matchingValue =
attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
@ -440,12 +465,12 @@ const morphX = async (
[`${alias}.${idColumn.name}`]: referencedValues,
[`${alias}.${typeColumn.name}`]: uid,
})
.execute({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const map = _.groupBy(idColumn.name, rows);
const map = _.groupBy<Row>(idColumn.name)(rows);
results.forEach((result) => {
const matchingRows = map[result[idColumn.referencedColumn]];
const matchingRows = map[result[idColumn.referencedColumn] as string];
const matchingValue =
attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
@ -480,11 +505,11 @@ const morphToMany = async (input: Input<Relation.MorphToMany>, ctx: Context) =>
// If the populateValue contains an "on" property,
// only populate the types defined in it
...('on' in populateValue
? { [morphColumn.typeColumn.name]: Object.keys(populateValue.on) }
? { [morphColumn.typeColumn.name]: Object.keys(populateValue.on ?? {}) }
: {}),
})
.orderBy([joinColumn.name, 'order'])
.execute<Array<Record<string, unknown>>>({ mapResults: false });
.execute<Row[]>({ mapResults: false });
const joinMap = _.groupBy(joinColumn.name, joinRows);
@ -524,19 +549,24 @@ const morphToMany = async (input: Input<Relation.MorphToMany>, ctx: Context) =>
.init(on?.[type] ?? typePopulate)
.addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
.where({ [idColumn.referencedColumn]: ids })
.execute<Record<string, unknown>[]>({ mapResults: false });
.execute<Row[]>({ mapResults: false });
map[type] = _.groupBy<Record<string, unknown>>(idColumn.referencedColumn)(rows);
map[type] = _.groupBy<Row>(idColumn.referencedColumn)(rows);
}
results.forEach((result) => {
const joinResults = joinMap[result[joinColumn.referencedColumn] as string] || [];
const matchingRows = joinResults.flatMap((joinResult) => {
const id = joinResult[idColumn.name];
const type = joinResult[typeColumn.name];
const id = joinResult[idColumn.name] as ID;
const type = joinResult[typeColumn.name] as string;
const fromTargetRow = (rowOrRows) => fromRow(db.metadata.get(type), rowOrRows);
const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => {
if (!rowOrRows) {
return rowOrRows;
}
return fromRow(db.metadata.get(type), rowOrRows);
};
return (map[type][id] || []).map((row) => {
return {
@ -595,9 +625,9 @@ const morphToOne = async (input: Input<Relation.MorphToOne>, ctx: Context) => {
.init(on?.[type] ?? typePopulate)
.addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
.where({ [idColumn.referencedColumn]: ids })
.execute<Record<string, unknown>[]>({ mapResults: false });
.execute<Row[]>({ mapResults: false });
map[type] = _.groupBy<Record<string, unknown>>(idColumn.referencedColumn)(rows);
map[type] = _.groupBy<Row>(idColumn.referencedColumn)(rows);
}
results.forEach((result) => {
@ -611,9 +641,7 @@ const morphToOne = async (input: Input<Relation.MorphToOne>, ctx: Context) => {
const matchingRows = map[type][id];
const fromTargetRow = (
rowOrRows: Record<string, unknown> | Record<string, unknown>[] | undefined
) => {
const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => {
if (!rowOrRows) {
return rowOrRows;
}
@ -645,11 +673,7 @@ const pickPopulateParams = (populate: Record<string, unknown>) => {
return _.pick(fieldsToPick, populate);
};
const applyPopulate = async (
results: Record<string, unknown>[],
populate: Record<string, any>,
ctx: Context
) => {
const applyPopulate = async (results: Row[], populate: Record<string, any>, ctx: Context) => {
const { db, uid, qb } = ctx;
const meta = db.metadata.get(uid);

View File

@ -77,11 +77,11 @@ export interface QueryBuilder {
shouldUseDistinct(): boolean;
processSelect(): void;
getKnexQuery(): Knex.QueryBuilder;
execute<T>({ mapResults }?: { mapResults?: boolean }): Promise<T>;
stream({ mapResults }?: { mapResults?: boolean }): helpers.ReadableQuery;
execute<T>(options?: { mapResults?: boolean }): Promise<T>;
stream(options?: { mapResults?: boolean }): helpers.ReadableQuery;
}
const createQueryBuilder = <TResult>(
const createQueryBuilder = (
uid: string,
db: Database,
initialState: Partial<State> = {}