node_modules ignore

This commit is contained in:
2025-05-08 23:43:47 +02:00
parent e19d52f172
commit 4574544c9f
65041 changed files with 10593536 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
import type { Database } from '..';
import type { Repository } from './types';
export declare const createRepository: (uid: string, db: Database) => Repository;
//# sourceMappingURL=entity-repository.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"entity-repository.d.ts","sourceRoot":"","sources":["../../src/entity-manager/entity-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,SAAS,CAAC;AAkClD,eAAO,MAAM,gBAAgB,QAAS,MAAM,MAAM,QAAQ,KAAG,UA8H5D,CAAC"}

View File

@@ -0,0 +1,139 @@
'use strict';
var _ = require('lodash/fp');
const withDefaultPagination = (params)=>{
const { page = 1, pageSize = 10, ...rest } = params;
return {
page: Number(page),
pageSize: Number(pageSize),
...rest
};
};
const withOffsetLimit = (params)=>{
const { page, pageSize, ...rest } = withDefaultPagination(params);
const offset = Math.max(page - 1, 0) * pageSize;
const limit = pageSize;
const query = {
...rest,
limit,
offset
};
return [
query,
{
page,
pageSize
}
];
};
const createRepository = (uid, db)=>{
return {
findOne (params = {}) {
return db.entityManager.findOne(uid, params);
},
findMany (params = {}) {
return db.entityManager.findMany(uid, params);
},
findWithCount (params = {}) {
return Promise.all([
db.entityManager.findMany(uid, params),
db.entityManager.count(uid, params)
]);
},
async findPage (params) {
const [query, { page, pageSize }] = withOffsetLimit(params);
const [results, total] = await Promise.all([
db.entityManager.findMany(uid, query),
db.entityManager.count(uid, query)
]);
return {
results,
pagination: {
page,
pageSize,
pageCount: Math.ceil(total / pageSize),
total
}
};
},
create (params) {
return db.entityManager.create(uid, params);
},
createMany (params) {
return db.entityManager.createMany(uid, params);
},
update (params) {
return db.entityManager.update(uid, params);
},
updateMany (params) {
return db.entityManager.updateMany(uid, params);
},
delete (params) {
return db.entityManager.delete(uid, params);
},
deleteMany (params = {}) {
return db.entityManager.deleteMany(uid, params);
},
count (params) {
return db.entityManager.count(uid, params);
},
attachRelations (id, data) {
return db.entityManager.attachRelations(uid, id, data);
},
async updateRelations (id, data) {
const trx = await db.transaction();
try {
await db.entityManager.updateRelations(uid, id, data, {
transaction: trx.get()
});
return await trx.commit();
} catch (e) {
await trx.rollback();
throw e;
}
},
deleteRelations (id) {
return db.entityManager.deleteRelations(uid, id);
},
populate (entity, populate) {
return db.entityManager.populate(uid, entity, populate);
},
load (entity, fields, params) {
return db.entityManager.load(uid, entity, fields, params);
},
async loadPages (entity, field, params) {
if (!_.isString(field)) {
throw new Error(`Invalid load. Expected ${field} to be a string`);
}
const { attributes } = db.metadata.get(uid);
const attribute = attributes[field];
if (!attribute || attribute.type !== 'relation' || !attribute.relation || ![
'oneToMany',
'manyToMany'
].includes(attribute.relation)) {
throw new Error(`Invalid load. Expected ${field} to be an anyToMany relational attribute`);
}
const [query, { page, pageSize }] = withOffsetLimit(params);
const [results, { count: total }] = await Promise.all([
db.entityManager.load(uid, entity, field, query),
db.entityManager.load(uid, entity, field, {
...query,
count: true
})
]);
return {
results,
pagination: {
page,
pageSize,
pageCount: Math.ceil(total / pageSize),
total
}
};
}
};
};
exports.createRepository = createRepository;
//# sourceMappingURL=entity-repository.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,137 @@
import { isString } from 'lodash/fp';
const withDefaultPagination = (params)=>{
const { page = 1, pageSize = 10, ...rest } = params;
return {
page: Number(page),
pageSize: Number(pageSize),
...rest
};
};
const withOffsetLimit = (params)=>{
const { page, pageSize, ...rest } = withDefaultPagination(params);
const offset = Math.max(page - 1, 0) * pageSize;
const limit = pageSize;
const query = {
...rest,
limit,
offset
};
return [
query,
{
page,
pageSize
}
];
};
const createRepository = (uid, db)=>{
return {
findOne (params = {}) {
return db.entityManager.findOne(uid, params);
},
findMany (params = {}) {
return db.entityManager.findMany(uid, params);
},
findWithCount (params = {}) {
return Promise.all([
db.entityManager.findMany(uid, params),
db.entityManager.count(uid, params)
]);
},
async findPage (params) {
const [query, { page, pageSize }] = withOffsetLimit(params);
const [results, total] = await Promise.all([
db.entityManager.findMany(uid, query),
db.entityManager.count(uid, query)
]);
return {
results,
pagination: {
page,
pageSize,
pageCount: Math.ceil(total / pageSize),
total
}
};
},
create (params) {
return db.entityManager.create(uid, params);
},
createMany (params) {
return db.entityManager.createMany(uid, params);
},
update (params) {
return db.entityManager.update(uid, params);
},
updateMany (params) {
return db.entityManager.updateMany(uid, params);
},
delete (params) {
return db.entityManager.delete(uid, params);
},
deleteMany (params = {}) {
return db.entityManager.deleteMany(uid, params);
},
count (params) {
return db.entityManager.count(uid, params);
},
attachRelations (id, data) {
return db.entityManager.attachRelations(uid, id, data);
},
async updateRelations (id, data) {
const trx = await db.transaction();
try {
await db.entityManager.updateRelations(uid, id, data, {
transaction: trx.get()
});
return await trx.commit();
} catch (e) {
await trx.rollback();
throw e;
}
},
deleteRelations (id) {
return db.entityManager.deleteRelations(uid, id);
},
populate (entity, populate) {
return db.entityManager.populate(uid, entity, populate);
},
load (entity, fields, params) {
return db.entityManager.load(uid, entity, fields, params);
},
async loadPages (entity, field, params) {
if (!isString(field)) {
throw new Error(`Invalid load. Expected ${field} to be a string`);
}
const { attributes } = db.metadata.get(uid);
const attribute = attributes[field];
if (!attribute || attribute.type !== 'relation' || !attribute.relation || ![
'oneToMany',
'manyToMany'
].includes(attribute.relation)) {
throw new Error(`Invalid load. Expected ${field} to be an anyToMany relational attribute`);
}
const [query, { page, pageSize }] = withOffsetLimit(params);
const [results, { count: total }] = await Promise.all([
db.entityManager.load(uid, entity, field, query),
db.entityManager.load(uid, entity, field, {
...query,
count: true
})
]);
return {
results,
pagination: {
page,
pageSize,
pageCount: Math.ceil(total / pageSize),
total
}
};
}
};
};
export { createRepository };
//# sourceMappingURL=entity-repository.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
import type { Database } from '..';
import { EntityManager } from './types';
export * from './types';
export declare const createEntityManager: (db: Database) => EntityManager;
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entity-manager/index.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAGnC,OAAO,EAAE,aAAa,EAAsB,MAAM,SAAS,CAAC;AAE5D,cAAc,SAAS,CAAC;AA2LxB,eAAO,MAAM,mBAAmB,OAAQ,QAAQ,KAAG,aAmzClD,CAAC"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,25 @@
/// <reference types="lodash" />
import type { Knex } from 'knex';
import type { Database } from '..';
import type { MorphJoinTable } from '../types';
type Rows = Record<string, unknown>[];
export declare const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate: (rows: Rows, { uid, attributeName, joinTable, db, transaction: trx, }: {
uid: string;
attributeName: string;
joinTable: MorphJoinTable;
db: Database;
transaction?: Knex.Transaction;
}) => Promise<void>;
/**
* Encoding utilities for polymorphic relations.
*
* In some scenarios is useful to encode both the id & __type of the relation
* to have a unique identifier for the relation. (e.g. relations reordering)
*/
export declare const encodePolymorphicId: (id: number | string, __type: string) => string;
export declare const encodePolymorphicRelation: import("lodash").CurriedFunction2<{
idColumn: any;
typeColumn: any;
}, any, any>;
export {};
//# sourceMappingURL=morph-relations.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"morph-relations.d.ts","sourceRoot":"","sources":["../../src/entity-manager/morph-relations.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,UAAU,CAAC;AAEzD,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;AA8BtC,eAAO,MAAM,oDAAoD,yEAQ5D;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,cAAc,CAAC;IAC1B,EAAE,EAAE,QAAQ,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,WAAW,CAAC;CAChC,kBAoCF,CAAC;AAEF;;;;;GAKG;AAEH,eAAO,MAAM,mBAAmB,OAAQ,MAAM,GAAG,MAAM,UAAU,MAAM,WAEtE,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;YAkBpC,CAAC"}

View File

@@ -0,0 +1,73 @@
'use strict';
var _ = require('lodash/fp');
var queryBuilder = require('../query/query-builder.js');
/* eslint-disable @typescript-eslint/naming-convention */ // allow __type
const getMorphToManyRowsLinkedToMorphOne = (rows, { uid, attributeName, typeColumn, db })=>rows.filter((row)=>{
const relatedType = row[typeColumn.name];
const field = row.field;
const targetAttribute = db.metadata.get(relatedType).attributes[field];
// ensure targeted field is the right one + check if it is a morphOne
return targetAttribute?.target === uid && targetAttribute?.morphBy === attributeName && targetAttribute?.relation === 'morphOne';
});
const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, { uid, attributeName, joinTable, db, transaction: trx })=>{
const { morphColumn } = joinTable;
const { idColumn, typeColumn } = morphColumn;
const morphOneRows = getMorphToManyRowsLinkedToMorphOne(rows, {
uid,
attributeName,
typeColumn,
db
});
const groupByType = _.groupBy(typeColumn.name);
const groupByField = _.groupBy('field');
const typeAndFieldIdsGrouped = _.pipe(groupByType, _.mapValues(groupByField))(morphOneRows);
const orWhere = [];
for (const [type, v] of Object.entries(typeAndFieldIdsGrouped)){
for (const [field, arr] of Object.entries(v)){
orWhere.push({
[typeColumn.name]: type,
field,
[idColumn.name]: {
$in: _.map(idColumn.name, arr)
}
});
}
}
if (!_.isEmpty(orWhere)) {
await queryBuilder(joinTable.name, db).delete().where({
$or: orWhere
}).transacting(trx).execute();
}
};
/**
* Encoding utilities for polymorphic relations.
*
* In some scenarios is useful to encode both the id & __type of the relation
* to have a unique identifier for the relation. (e.g. relations reordering)
*/ const encodePolymorphicId = (id, __type)=>{
return `${id}:::${__type}`;
};
const encodePolymorphicRelation = _.curry(({ idColumn, typeColumn }, relation)=>{
// Encode the id of the relation and the positional argument if it exist
const newRelation = {
...relation,
[idColumn]: encodePolymorphicId(relation[idColumn], relation[typeColumn])
};
if (relation.position) {
const { before, after } = relation.position;
const __type = relation.position.__type || relation.__type;
newRelation.position = {
...relation.position
};
if (before) newRelation.position.before = encodePolymorphicId(before, __type);
if (after) newRelation.position.after = encodePolymorphicId(after, __type);
}
return newRelation;
});
exports.deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = deleteRelatedMorphOneRelationsAfterMorphToManyUpdate;
exports.encodePolymorphicId = encodePolymorphicId;
exports.encodePolymorphicRelation = encodePolymorphicRelation;
//# sourceMappingURL=morph-relations.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,69 @@
import { curry, groupBy, pipe, mapValues, map, isEmpty } from 'lodash/fp';
import createQueryBuilder from '../query/query-builder.mjs';
/* eslint-disable @typescript-eslint/naming-convention */ // allow __type
const getMorphToManyRowsLinkedToMorphOne = (rows, { uid, attributeName, typeColumn, db })=>rows.filter((row)=>{
const relatedType = row[typeColumn.name];
const field = row.field;
const targetAttribute = db.metadata.get(relatedType).attributes[field];
// ensure targeted field is the right one + check if it is a morphOne
return targetAttribute?.target === uid && targetAttribute?.morphBy === attributeName && targetAttribute?.relation === 'morphOne';
});
const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, { uid, attributeName, joinTable, db, transaction: trx })=>{
const { morphColumn } = joinTable;
const { idColumn, typeColumn } = morphColumn;
const morphOneRows = getMorphToManyRowsLinkedToMorphOne(rows, {
uid,
attributeName,
typeColumn,
db
});
const groupByType = groupBy(typeColumn.name);
const groupByField = groupBy('field');
const typeAndFieldIdsGrouped = pipe(groupByType, mapValues(groupByField))(morphOneRows);
const orWhere = [];
for (const [type, v] of Object.entries(typeAndFieldIdsGrouped)){
for (const [field, arr] of Object.entries(v)){
orWhere.push({
[typeColumn.name]: type,
field,
[idColumn.name]: {
$in: map(idColumn.name, arr)
}
});
}
}
if (!isEmpty(orWhere)) {
await createQueryBuilder(joinTable.name, db).delete().where({
$or: orWhere
}).transacting(trx).execute();
}
};
/**
* Encoding utilities for polymorphic relations.
*
* In some scenarios is useful to encode both the id & __type of the relation
* to have a unique identifier for the relation. (e.g. relations reordering)
*/ const encodePolymorphicId = (id, __type)=>{
return `${id}:::${__type}`;
};
const encodePolymorphicRelation = curry(({ idColumn, typeColumn }, relation)=>{
// Encode the id of the relation and the positional argument if it exist
const newRelation = {
...relation,
[idColumn]: encodePolymorphicId(relation[idColumn], relation[typeColumn])
};
if (relation.position) {
const { before, after } = relation.position;
const __type = relation.position.__type || relation.__type;
newRelation.position = {
...relation.position
};
if (before) newRelation.position.before = encodePolymorphicId(before, __type);
if (after) newRelation.position.after = encodePolymorphicId(after, __type);
}
return newRelation;
});
export { deleteRelatedMorphOneRelationsAfterMorphToManyUpdate, encodePolymorphicId, encodePolymorphicRelation };
//# sourceMappingURL=morph-relations.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,58 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import type { Knex } from 'knex';
import type { Database } from '..';
import type { ID, Relation } from '../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.
*/
declare const deletePreviousOneToAnyRelations: ({ id, attribute, relIdsToadd, db, transaction: trx, }: {
id: ID;
attribute: Relation.Bidirectional;
relIdsToadd: ID[];
db: Database;
transaction?: Knex.Transaction;
}) => Promise<void>;
/**
* If a relation currently exists for this xToOne relations, this function removes it and update the inverse order if needed.
*/
declare const deletePreviousAnyToOneRelations: ({ id, attribute, relIdToadd, db, transaction: trx, }: {
id: ID;
attribute: Relation.Bidirectional;
relIdToadd: ID;
db: Database;
transaction?: Knex.Transaction;
}) => Promise<void>;
/**
* Delete all or some relations of entity field
*/
declare const deleteRelations: ({ id, attribute, db, relIdsToNotDelete, relIdsToDelete, transaction: trx, }: {
id: ID;
attribute: Relation.Bidirectional;
db: Database;
relIdsToNotDelete?: ID[];
relIdsToDelete?: ID[] | 'all';
transaction?: Knex.Transaction;
}) => Promise<void>;
/**
* Clean the order columns by ensuring the order value are continuous (ex: 1, 2, 3 and not 1, 5, 10)
*/
declare const cleanOrderColumns: ({ id, attribute, db, inverseRelIds, transaction: trx, }: {
id?: ID;
attribute: Relation.Bidirectional;
db: Database;
inverseRelIds?: ID[];
transaction?: Knex.Transaction;
}) => Promise<[void, void] | undefined>;
export { deletePreviousOneToAnyRelations, deletePreviousAnyToOneRelations, deleteRelations, cleanOrderColumns, };
//# sourceMappingURL=regular-relations.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"regular-relations.d.ts","sourceRoot":"","sources":["../../src/entity-manager/regular-relations.ts"],"names":[],"mappings":";;;;;AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAYjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAS,MAAM,UAAU,CAAC;AAEpD,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,IAAI,CAAC;QACb,UAAU,kBAAkB;YAC1B,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;SAC3C;KACF;CACF;AAiCD;;GAEG;AACH,QAAA,MAAM,+BAA+B,0DAMlC;IACD,EAAE,EAAE,EAAE,CAAC;IACP,SAAS,EAAE,SAAS,aAAa,CAAC;IAClC,WAAW,EAAE,EAAE,EAAE,CAAC;IAClB,EAAE,EAAE,QAAQ,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,WAAW,CAAC;CAChC,kBAsBA,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,+BAA+B,yDAMlC;IACD,EAAE,EAAE,EAAE,CAAC;IACP,SAAS,EAAE,SAAS,aAAa,CAAC;IAClC,UAAU,EAAE,EAAE,CAAC;IACf,EAAE,EAAE,QAAQ,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,WAAW,CAAC;CAChC,kBAkDA,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,eAAe,gFAOlB;IACD,EAAE,EAAE,EAAE,CAAC;IACP,SAAS,EAAE,SAAS,aAAa,CAAC;IAClC,EAAE,EAAE,QAAQ,CAAC;IACb,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IAC9B,WAAW,CAAC,EAAE,KAAK,WAAW,CAAC;CAChC,kBAsDA,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,iBAAiB,4DAMpB;IACD,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,SAAS,EAAE,SAAS,aAAa,CAAC;IAClC,EAAE,EAAE,QAAQ,CAAC;IACb,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,WAAW,CAAC;CAChC,sCAiIA,CAAC;AAEF,OAAO,EACL,+BAA+B,EAC/B,+BAA+B,EAC/B,eAAe,EACf,iBAAiB,GAClB,CAAC"}

View File

@@ -0,0 +1,247 @@
'use strict';
var _ = require('lodash/fp');
var relations = require('../metadata/relations.js');
require('../utils/identifiers/index.js');
var queryBuilder = require('../query/query-builder.js');
var knex = require('../utils/knex.js');
// TODO: This is a short term solution, to not steal relations from the same document.
const getDocumentSiblingIdsQuery = (tableName, id)=>{
// Find if the model is a content type or something else (e.g. component)
// to only get the documentId if it's a content type
const models = Array.from(strapi.db.metadata.values());
const isContentType = models.find((model)=>{
return model.tableName === tableName && model.attributes.documentId;
});
if (!isContentType) {
return [
id
];
}
// NOTE: SubQueries are wrapped in a function to not reuse the same connection,
// which causes infinite self references
return function(query) {
query.select('id').from(tableName)// Get all child ids of the document id
.whereIn('document_id', (documentIDSubQuery)=>{
documentIDSubQuery.from(tableName)// get document id related to the current id
.select('document_id').where('id', id);
});
};
};
/**
* If some relations currently exist for this oneToX relation, on the one side, this function removes them and update the inverse order if needed.
*/ const deletePreviousOneToAnyRelations = async ({ id, attribute, relIdsToadd, db, transaction: trx })=>{
if (!(relations.isBidirectional(attribute) && relations.isOneToAny(attribute))) {
throw new Error('deletePreviousOneToAnyRelations can only be called for bidirectional oneToAny relations');
}
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
const con = db.getConnection();
await con.delete().from(joinTable.name)// Exclude the ids of the current document
.whereNotIn(joinColumn.name, getDocumentSiblingIdsQuery(joinColumn.referencedTable, id))// Include all the ids that are being connected
.whereIn(inverseJoinColumn.name, relIdsToadd).where(joinTable.on || {}).transacting(trx);
await cleanOrderColumns({
attribute,
db,
inverseRelIds: relIdsToadd,
transaction: trx
});
};
/**
* If a relation currently exists for this xToOne relations, this function removes it and update the inverse order if needed.
*/ const deletePreviousAnyToOneRelations = async ({ id, attribute, relIdToadd, db, transaction: trx })=>{
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
const con = db.getConnection();
if (!relations.isAnyToOne(attribute)) {
throw new Error('deletePreviousAnyToOneRelations can only be called for anyToOne relations');
}
// handling manyToOne
if (relations.isManyToAny(attribute)) {
// if the database integrity was not broken relsToDelete is supposed to be of length 1
const relsToDelete = await con.select(inverseJoinColumn.name).from(joinTable.name).where(joinColumn.name, id).whereNotIn(inverseJoinColumn.name, getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)).where(joinTable.on || {}).transacting(trx);
const relIdsToDelete = _.map(inverseJoinColumn.name, relsToDelete);
await queryBuilder(joinTable.name, db).delete().where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: {
$in: relIdsToDelete
}
}).where(joinTable.on || {}).transacting(trx).execute();
await cleanOrderColumns({
attribute,
db,
inverseRelIds: relIdsToDelete,
transaction: trx
});
// handling oneToOne
} else {
await con.delete().from(joinTable.name).where(joinColumn.name, id)// Exclude the ids of the current document
.whereNotIn(inverseJoinColumn.name, getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)).where(joinTable.on || {}).transacting(trx);
}
};
/**
* Delete all or some relations of entity field
*/ const deleteRelations = async ({ id, attribute, db, relIdsToNotDelete = [], relIdsToDelete = [], transaction: trx })=>{
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
const all = relIdsToDelete === 'all';
if (relations.hasOrderColumn(attribute) || relations.hasInverseOrderColumn(attribute)) {
let lastId = 0;
let done = false;
const batchSize = 100;
while(!done){
const batchToDelete = await queryBuilder(joinTable.name, db).select(inverseJoinColumn.name).where({
[joinColumn.name]: id,
id: {
$gt: lastId
},
[inverseJoinColumn.name]: {
$notIn: relIdsToNotDelete
},
...all ? {} : {
[inverseJoinColumn.name]: {
$in: relIdsToDelete
}
}
}).where(joinTable.on || {}).orderBy('id').limit(batchSize).transacting(trx).execute();
done = batchToDelete.length < batchSize;
lastId = batchToDelete[batchToDelete.length - 1]?.id || 0;
const batchIds = _.map(inverseJoinColumn.name, batchToDelete);
await queryBuilder(joinTable.name, db).delete().where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: {
$in: batchIds
}
}).where(joinTable.on || {}).transacting(trx).execute();
await cleanOrderColumns({
attribute,
db,
id,
inverseRelIds: batchIds,
transaction: trx
});
}
} else {
await queryBuilder(joinTable.name, db).delete().where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: {
$notIn: relIdsToNotDelete
},
...all ? {} : {
[inverseJoinColumn.name]: {
$in: relIdsToDelete
}
}
}).where(joinTable.on || {}).transacting(trx).execute();
}
};
/**
* Clean the order columns by ensuring the order value are continuous (ex: 1, 2, 3 and not 1, 5, 10)
*/ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds = [], transaction: trx })=>{
if (!(relations.hasOrderColumn(attribute) && id) && !(relations.hasInverseOrderColumn(attribute) && !_.isEmpty(inverseRelIds))) {
return;
}
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
/**
UPDATE :joinTable: as a,
(
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
FROM :joinTable:
WHERE :joinColumn: = :id
) AS b
SET :orderColumn: = b.src_order
WHERE b.id = a.id;
*/ const updateOrderColumn = async ()=>{
if (!relations.hasOrderColumn(attribute) || !id) {
return;
}
const selectRowsToOrder = (joinTableName)=>db.connection(joinTableName).select('id').rowNumber('src_order', orderColumnName, joinColumn.name).where(joinColumn.name, id).toSQL();
switch(strapi.db.dialect.client){
case 'mysql':
{
// Here it's MariaDB and MySQL 8
const select = selectRowsToOrder(joinTable.name);
await db.getConnection().raw(`UPDATE ?? as a, ( ${select.sql} ) AS b
SET ?? = b.src_order
WHERE b.id = a.id`, [
joinTable.name,
...select.bindings,
orderColumnName
]).transacting(trx);
break;
}
default:
{
const joinTableName = knex.addSchema(db, joinTable.name);
const select = selectRowsToOrder(joinTableName);
// raw query as knex doesn't allow updating from a subquery
await db.connection.raw(`UPDATE ?? as a
SET ?? = b.src_order
FROM ( ${select.sql} ) AS b
WHERE b.id = a.id`, [
joinTableName,
orderColumnName,
...select.bindings
]).transacting(trx);
}
}
};
/**
UPDATE :joinTable: as a,
(
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
FROM :joinTable:
WHERE :inverseJoinColumn: IN (:inverseRelIds)
) AS b
SET :inverseOrderColumn: = b.inv_order
WHERE b.id = a.id;
*/ const updateInverseOrderColumn = async ()=>{
if (!relations.hasInverseOrderColumn(attribute) || _.isEmpty(inverseRelIds)) return;
const selectRowsToOrder = (joinTableName)=>db.connection(joinTableName).select('id').rowNumber('inv_order', inverseOrderColumnName, inverseJoinColumn.name).where(inverseJoinColumn.name, 'in', inverseRelIds).toSQL();
switch(strapi.db.dialect.client){
case 'mysql':
{
// Here it's MariaDB and MySQL 8
const select = selectRowsToOrder(joinTable.name);
await db.getConnection().raw(`UPDATE ?? as a, ( ${select.sql} ) AS b
SET ?? = b.inv_order
WHERE b.id = a.id`, [
joinTable.name,
...select.bindings,
inverseOrderColumnName
]).transacting(trx);
break;
}
default:
{
const joinTableName = knex.addSchema(db, joinTable.name);
const select = selectRowsToOrder(joinTableName);
// raw query as knex doesn't allow updating from a subquery
await db.connection.raw(`UPDATE ?? as a
SET ?? = b.inv_order
FROM ( ${select.sql} ) AS b
WHERE b.id = a.id`, [
joinTableName,
inverseOrderColumnName,
...select.bindings
]).transacting(trx);
}
}
};
return Promise.all([
updateOrderColumn(),
updateInverseOrderColumn()
]);
};
exports.cleanOrderColumns = cleanOrderColumns;
exports.deletePreviousAnyToOneRelations = deletePreviousAnyToOneRelations;
exports.deletePreviousOneToAnyRelations = deletePreviousOneToAnyRelations;
exports.deleteRelations = deleteRelations;
//# sourceMappingURL=regular-relations.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,242 @@
import { map, isEmpty } from 'lodash/fp';
import { isBidirectional, isOneToAny, isAnyToOne, isManyToAny, hasOrderColumn, hasInverseOrderColumn } from '../metadata/relations.mjs';
import '../utils/identifiers/index.mjs';
import createQueryBuilder from '../query/query-builder.mjs';
import { addSchema } from '../utils/knex.mjs';
// TODO: This is a short term solution, to not steal relations from the same document.
const getDocumentSiblingIdsQuery = (tableName, id)=>{
// Find if the model is a content type or something else (e.g. component)
// to only get the documentId if it's a content type
const models = Array.from(strapi.db.metadata.values());
const isContentType = models.find((model)=>{
return model.tableName === tableName && model.attributes.documentId;
});
if (!isContentType) {
return [
id
];
}
// NOTE: SubQueries are wrapped in a function to not reuse the same connection,
// which causes infinite self references
return function(query) {
query.select('id').from(tableName)// Get all child ids of the document id
.whereIn('document_id', (documentIDSubQuery)=>{
documentIDSubQuery.from(tableName)// get document id related to the current id
.select('document_id').where('id', id);
});
};
};
/**
* If some relations currently exist for this oneToX relation, on the one side, this function removes them and update the inverse order if needed.
*/ const deletePreviousOneToAnyRelations = async ({ id, attribute, relIdsToadd, db, transaction: trx })=>{
if (!(isBidirectional(attribute) && isOneToAny(attribute))) {
throw new Error('deletePreviousOneToAnyRelations can only be called for bidirectional oneToAny relations');
}
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
const con = db.getConnection();
await con.delete().from(joinTable.name)// Exclude the ids of the current document
.whereNotIn(joinColumn.name, getDocumentSiblingIdsQuery(joinColumn.referencedTable, id))// Include all the ids that are being connected
.whereIn(inverseJoinColumn.name, relIdsToadd).where(joinTable.on || {}).transacting(trx);
await cleanOrderColumns({
attribute,
db,
inverseRelIds: relIdsToadd,
transaction: trx
});
};
/**
* If a relation currently exists for this xToOne relations, this function removes it and update the inverse order if needed.
*/ const deletePreviousAnyToOneRelations = async ({ id, attribute, relIdToadd, db, transaction: trx })=>{
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
const con = db.getConnection();
if (!isAnyToOne(attribute)) {
throw new Error('deletePreviousAnyToOneRelations can only be called for anyToOne relations');
}
// handling manyToOne
if (isManyToAny(attribute)) {
// if the database integrity was not broken relsToDelete is supposed to be of length 1
const relsToDelete = await con.select(inverseJoinColumn.name).from(joinTable.name).where(joinColumn.name, id).whereNotIn(inverseJoinColumn.name, getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)).where(joinTable.on || {}).transacting(trx);
const relIdsToDelete = map(inverseJoinColumn.name, relsToDelete);
await createQueryBuilder(joinTable.name, db).delete().where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: {
$in: relIdsToDelete
}
}).where(joinTable.on || {}).transacting(trx).execute();
await cleanOrderColumns({
attribute,
db,
inverseRelIds: relIdsToDelete,
transaction: trx
});
// handling oneToOne
} else {
await con.delete().from(joinTable.name).where(joinColumn.name, id)// Exclude the ids of the current document
.whereNotIn(inverseJoinColumn.name, getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)).where(joinTable.on || {}).transacting(trx);
}
};
/**
* Delete all or some relations of entity field
*/ const deleteRelations = async ({ id, attribute, db, relIdsToNotDelete = [], relIdsToDelete = [], transaction: trx })=>{
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn } = joinTable;
const all = relIdsToDelete === 'all';
if (hasOrderColumn(attribute) || hasInverseOrderColumn(attribute)) {
let lastId = 0;
let done = false;
const batchSize = 100;
while(!done){
const batchToDelete = await createQueryBuilder(joinTable.name, db).select(inverseJoinColumn.name).where({
[joinColumn.name]: id,
id: {
$gt: lastId
},
[inverseJoinColumn.name]: {
$notIn: relIdsToNotDelete
},
...all ? {} : {
[inverseJoinColumn.name]: {
$in: relIdsToDelete
}
}
}).where(joinTable.on || {}).orderBy('id').limit(batchSize).transacting(trx).execute();
done = batchToDelete.length < batchSize;
lastId = batchToDelete[batchToDelete.length - 1]?.id || 0;
const batchIds = map(inverseJoinColumn.name, batchToDelete);
await createQueryBuilder(joinTable.name, db).delete().where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: {
$in: batchIds
}
}).where(joinTable.on || {}).transacting(trx).execute();
await cleanOrderColumns({
attribute,
db,
id,
inverseRelIds: batchIds,
transaction: trx
});
}
} else {
await createQueryBuilder(joinTable.name, db).delete().where({
[joinColumn.name]: id,
[inverseJoinColumn.name]: {
$notIn: relIdsToNotDelete
},
...all ? {} : {
[inverseJoinColumn.name]: {
$in: relIdsToDelete
}
}
}).where(joinTable.on || {}).transacting(trx).execute();
}
};
/**
* Clean the order columns by ensuring the order value are continuous (ex: 1, 2, 3 and not 1, 5, 10)
*/ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds = [], transaction: trx })=>{
if (!(hasOrderColumn(attribute) && id) && !(hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds))) {
return;
}
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
/**
UPDATE :joinTable: as a,
(
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
FROM :joinTable:
WHERE :joinColumn: = :id
) AS b
SET :orderColumn: = b.src_order
WHERE b.id = a.id;
*/ const updateOrderColumn = async ()=>{
if (!hasOrderColumn(attribute) || !id) {
return;
}
const selectRowsToOrder = (joinTableName)=>db.connection(joinTableName).select('id').rowNumber('src_order', orderColumnName, joinColumn.name).where(joinColumn.name, id).toSQL();
switch(strapi.db.dialect.client){
case 'mysql':
{
// Here it's MariaDB and MySQL 8
const select = selectRowsToOrder(joinTable.name);
await db.getConnection().raw(`UPDATE ?? as a, ( ${select.sql} ) AS b
SET ?? = b.src_order
WHERE b.id = a.id`, [
joinTable.name,
...select.bindings,
orderColumnName
]).transacting(trx);
break;
}
default:
{
const joinTableName = addSchema(db, joinTable.name);
const select = selectRowsToOrder(joinTableName);
// raw query as knex doesn't allow updating from a subquery
await db.connection.raw(`UPDATE ?? as a
SET ?? = b.src_order
FROM ( ${select.sql} ) AS b
WHERE b.id = a.id`, [
joinTableName,
orderColumnName,
...select.bindings
]).transacting(trx);
}
}
};
/**
UPDATE :joinTable: as a,
(
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
FROM :joinTable:
WHERE :inverseJoinColumn: IN (:inverseRelIds)
) AS b
SET :inverseOrderColumn: = b.inv_order
WHERE b.id = a.id;
*/ const updateInverseOrderColumn = async ()=>{
if (!hasInverseOrderColumn(attribute) || isEmpty(inverseRelIds)) return;
const selectRowsToOrder = (joinTableName)=>db.connection(joinTableName).select('id').rowNumber('inv_order', inverseOrderColumnName, inverseJoinColumn.name).where(inverseJoinColumn.name, 'in', inverseRelIds).toSQL();
switch(strapi.db.dialect.client){
case 'mysql':
{
// Here it's MariaDB and MySQL 8
const select = selectRowsToOrder(joinTable.name);
await db.getConnection().raw(`UPDATE ?? as a, ( ${select.sql} ) AS b
SET ?? = b.inv_order
WHERE b.id = a.id`, [
joinTable.name,
...select.bindings,
inverseOrderColumnName
]).transacting(trx);
break;
}
default:
{
const joinTableName = addSchema(db, joinTable.name);
const select = selectRowsToOrder(joinTableName);
// raw query as knex doesn't allow updating from a subquery
await db.connection.raw(`UPDATE ?? as a
SET ?? = b.inv_order
FROM ( ${select.sql} ) AS b
WHERE b.id = a.id`, [
joinTableName,
inverseOrderColumnName,
...select.bindings
]).transacting(trx);
}
}
};
return Promise.all([
updateOrderColumn(),
updateInverseOrderColumn()
]);
};
export { cleanOrderColumns, deletePreviousAnyToOneRelations, deletePreviousOneToAnyRelations, deleteRelations };
//# sourceMappingURL=regular-relations.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,74 @@
import type { ID } from '../types';
interface Link {
id: ID;
position?: {
before?: ID;
after?: ID;
start?: true;
end?: true;
};
order?: number;
__component?: string;
}
interface OrderedLink extends Link {
init?: boolean;
order: number;
}
/**
* When connecting relations, the order you connect them matters.
*
* Example, if you connect the following relations:
* { id: 5, position: { before: 1 } }
* { id: 1, position: { before: 2 } }
* { id: 2, position: { end: true } }
*
* Going through the connect array, id 5 has to be connected before id 1,
* so the order of id5 = id1 - 1. But the order value of id 1 is unknown.
* The only way to know the order of id 1 is to connect it first.
*
* This function makes sure the relations are connected in the right order:
* { id: 2, position: { end: true } }
* { id: 1, position: { before: 2 } }
* { id: 5, position: { before: 1 } }
*
*/
declare const sortConnectArray: (connectArr: Link[], initialArr?: Link[], strictSort?: boolean) => Link[];
/**
* Responsible for calculating the relations order when connecting them.
*
* The connect method takes an array of relations with positional attributes:
* - before: the id of the relation to connect before
* - after: the id of the relation to connect after
* - end: it should be at the end
* - start: it should be at the start
*
* Example:
* - Having a connect array like:
* [ { id: 4, before: 2 }, { id: 4, before: 3}, {id: 5, before: 4} ]
* - With the initial relations:
* [ { id: 2, order: 4 }, { id: 3, order: 10 } ]
* - Step by step, going through the connect array, the array of relations would be:
* [ { id: 4, order: 3.5 }, { id: 2, order: 4 }, { id: 3, order: 10 } ]
* [ { id: 2, order: 4 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
* [ { id: 2, order: 4 }, { id: 5, order: 3.5 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
* - The final step would be to recalculate fractional order values.
* [ { id: 2, order: 4 }, { id: 5, order: 3.33 }, { id: 4, order: 3.66 }, { id: 3, order: 10 } ]
*
* @param {Array<*>} initArr - array of relations to initialize the class with
* @param {string} idColumn - the column name of the id
* @param {string} orderColumn - the column name of the order
* @param {boolean} strict - if true, will throw an error if a relation is connected adjacent to
* another one that does not exist
* @return {*}
*/
declare const relationsOrderer: <TRelation extends Record<string, ID | null>>(initArr: TRelation[], idColumn: keyof TRelation, orderColumn: keyof TRelation, strict?: boolean) => {
disconnect(relations: Link | Link[]): any;
connect(relations: Link | Link[]): any;
get(): OrderedLink[];
/**
* Get a map between the relation id and its order
*/
getOrderMap(): Record<ID, number>;
};
export { relationsOrderer, sortConnectArray };
//# sourceMappingURL=relations-orderer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"relations-orderer.d.ts","sourceRoot":"","sources":["../../src/entity-manager/relations-orderer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAEnC,UAAU,IAAI;IACZ,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,IAAI,CAAC;QAAC,GAAG,CAAC,EAAE,IAAI,CAAA;KAAE,CAAC;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,WAAY,SAAQ,IAAI;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,gBAAgB,eAAgB,IAAI,EAAE,eAAc,IAAI,EAAE,iCA0G/D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,QAAA,MAAM,gBAAgB,yDACX,SAAS,EAAE,YACV,MAAM,SAAS,eACZ,MAAM,SAAS,WACnB,OAAO;0BAuDQ,IAAI,GAAG,IAAI,EAAE;uBAMhB,IAAI,GAAG,IAAI,EAAE;;IAqBhC;;OAEG;;CAgBN,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC"}

View File

@@ -0,0 +1,221 @@
'use strict';
var _ = require('lodash/fp');
var _$1 = require('lodash');
var invalidRelation = require('../errors/invalid-relation.js');
/**
* When connecting relations, the order you connect them matters.
*
* Example, if you connect the following relations:
* { id: 5, position: { before: 1 } }
* { id: 1, position: { before: 2 } }
* { id: 2, position: { end: true } }
*
* Going through the connect array, id 5 has to be connected before id 1,
* so the order of id5 = id1 - 1. But the order value of id 1 is unknown.
* The only way to know the order of id 1 is to connect it first.
*
* This function makes sure the relations are connected in the right order:
* { id: 2, position: { end: true } }
* { id: 1, position: { before: 2 } }
* { id: 5, position: { before: 1 } }
*
*/ const sortConnectArray = (connectArr, initialArr = [], strictSort = true)=>{
const sortedConnect = [];
// Boolean to know if we have to recalculate the order of the relations
let needsSorting = false;
// Map to validate if relation is already in sortedConnect or DB.
const relationInInitialArray = initialArr.reduce((acc, rel)=>({
...acc,
[rel.id]: true
}), {});
// Map to store the first index where a relation id is connected
const mappedRelations = connectArr.reduce((mapper, relation)=>{
const adjacentRelId = relation.position?.before || relation.position?.after;
if (!adjacentRelId || !relationInInitialArray[adjacentRelId] && !mapper[adjacentRelId]) {
needsSorting = true;
}
/**
* We do not allow duplicate relations to be connected, so we need to check for uniqueness with components
* Note that the id here includes the uid for polymorphic relations
*
* So for normal relations, the same id means the same relation
* For component relations, it means the unique combo of (id, component name)
*/ // Check if there's an existing relation with this id
const existingRelation = mapper[relation.id];
// Check if existing relation has a component or not
const hasNoComponent = existingRelation && !('__component' in existingRelation);
// Check if the existing relation has the same component as the new relation
const hasSameComponent = existingRelation && existingRelation.__component === relation.__component;
// If we have an existing relation that is not unique (no component or same component) we won't accept it
if (existingRelation && (hasNoComponent || hasSameComponent)) {
throw new invalidRelation(`The relation with id ${relation.id} is already connected. ` + 'You cannot connect the same relation twice.');
}
return {
[relation.id]: {
...relation,
computed: false
},
...mapper
};
}, {});
// If we don't need to sort the connect array, we can return it as is
if (!needsSorting) return connectArr;
// Recursively compute in which order the relation should be connected
const computeRelation = (relation, relationsSeenInBranch)=>{
const adjacentRelId = relation.position?.before || relation.position?.after;
const adjacentRelation = mappedRelations[adjacentRelId];
// If the relation has already been seen in the current branch,
// it means there is a circular reference
if (adjacentRelId && relationsSeenInBranch[adjacentRelId]) {
throw new invalidRelation('A circular reference was found in the connect array. ' + 'One relation is trying to connect before/after another one that is trying to connect before/after it');
}
// This relation has already been computed
if (mappedRelations[relation.id]?.computed) {
return;
}
mappedRelations[relation.id].computed = true;
// Relation does not have a before or after attribute or is in the initial array
if (!adjacentRelId || relationInInitialArray[adjacentRelId]) {
sortedConnect.push(relation);
return;
}
// Look if id is referenced elsewhere in the array
if (mappedRelations[adjacentRelId]) {
computeRelation(adjacentRelation, {
...relationsSeenInBranch,
[relation.id]: true
});
sortedConnect.push(relation);
} else if (strictSort) {
// If we reach this point, it means that the adjacent relation is not in the connect array
// and it is not in the database.
throw new invalidRelation(`There was a problem connecting relation with id ${relation.id} at position ${JSON.stringify(relation.position)}. The relation with id ${adjacentRelId} needs to be connected first.`);
} else {
// We are in non-strict mode so we can push the relation.
sortedConnect.push({
id: relation.id,
position: {
end: true
}
});
}
};
// Iterate over connectArr and populate sortedConnect
connectArr.forEach((relation)=>computeRelation(relation, {}));
return sortedConnect;
};
/**
* Responsible for calculating the relations order when connecting them.
*
* The connect method takes an array of relations with positional attributes:
* - before: the id of the relation to connect before
* - after: the id of the relation to connect after
* - end: it should be at the end
* - start: it should be at the start
*
* Example:
* - Having a connect array like:
* [ { id: 4, before: 2 }, { id: 4, before: 3}, {id: 5, before: 4} ]
* - With the initial relations:
* [ { id: 2, order: 4 }, { id: 3, order: 10 } ]
* - Step by step, going through the connect array, the array of relations would be:
* [ { id: 4, order: 3.5 }, { id: 2, order: 4 }, { id: 3, order: 10 } ]
* [ { id: 2, order: 4 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
* [ { id: 2, order: 4 }, { id: 5, order: 3.5 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
* - The final step would be to recalculate fractional order values.
* [ { id: 2, order: 4 }, { id: 5, order: 3.33 }, { id: 4, order: 3.66 }, { id: 3, order: 10 } ]
*
* @param {Array<*>} initArr - array of relations to initialize the class with
* @param {string} idColumn - the column name of the id
* @param {string} orderColumn - the column name of the order
* @param {boolean} strict - if true, will throw an error if a relation is connected adjacent to
* another one that does not exist
* @return {*}
*/ const relationsOrderer = (initArr, idColumn, orderColumn, strict)=>{
const computedRelations = _.castArray(initArr ?? []).map((r)=>({
init: true,
id: r[idColumn],
order: Number(r[orderColumn]) || 1
}));
const maxOrder = _.maxBy('order', computedRelations)?.order || 0;
const findRelation = (id)=>{
const idx = computedRelations.findIndex((r)=>r.id === id);
return {
idx,
relation: computedRelations[idx]
};
};
const removeRelation = (r)=>{
const { idx } = findRelation(r.id);
if (idx >= 0) {
computedRelations.splice(idx, 1);
}
};
const insertRelation = (r)=>{
let idx;
if (r.position?.before) {
const { idx: _idx, relation } = findRelation(r.position.before);
if (relation.init) {
r.order = relation.order - 0.5;
} else {
r.order = relation.order;
}
idx = _idx;
} else if (r.position?.after) {
const { idx: _idx, relation } = findRelation(r.position.after);
if (relation.init) {
r.order = relation.order + 0.5;
} else {
r.order = relation.order;
}
idx = _idx + 1;
} else if (r.position?.start) {
r.order = 0.5;
idx = 0;
} else {
r.order = maxOrder + 0.5;
idx = computedRelations.length;
}
// Insert the relation in the array
computedRelations.splice(idx, 0, r);
};
return {
disconnect (relations) {
_.castArray(relations).forEach((relation)=>{
removeRelation(relation);
});
return this;
},
connect (relations) {
sortConnectArray(_.castArray(relations), computedRelations, strict).forEach((relation)=>{
this.disconnect(relation);
try {
insertRelation(relation);
} catch (err) {
throw new Error(`There was a problem connecting relation with id ${relation.id} at position ${JSON.stringify(relation.position)}. The list of connect relations is not valid`);
}
});
return this;
},
get () {
return computedRelations;
},
/**
* Get a map between the relation id and its order
*/ getOrderMap () {
return _$1(computedRelations).groupBy('order').reduce((acc, relations)=>{
if (relations[0]?.init) return acc;
relations.forEach((relation, idx)=>{
acc[relation.id] = Math.floor(relation.order) + (idx + 1) / (relations.length + 1);
});
return acc;
}, {});
}
};
};
exports.relationsOrderer = relationsOrderer;
exports.sortConnectArray = sortConnectArray;
//# sourceMappingURL=relations-orderer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,218 @@
import { castArray, maxBy } from 'lodash/fp';
import _ from 'lodash';
import InvalidRelationError from '../errors/invalid-relation.mjs';
/**
* When connecting relations, the order you connect them matters.
*
* Example, if you connect the following relations:
* { id: 5, position: { before: 1 } }
* { id: 1, position: { before: 2 } }
* { id: 2, position: { end: true } }
*
* Going through the connect array, id 5 has to be connected before id 1,
* so the order of id5 = id1 - 1. But the order value of id 1 is unknown.
* The only way to know the order of id 1 is to connect it first.
*
* This function makes sure the relations are connected in the right order:
* { id: 2, position: { end: true } }
* { id: 1, position: { before: 2 } }
* { id: 5, position: { before: 1 } }
*
*/ const sortConnectArray = (connectArr, initialArr = [], strictSort = true)=>{
const sortedConnect = [];
// Boolean to know if we have to recalculate the order of the relations
let needsSorting = false;
// Map to validate if relation is already in sortedConnect or DB.
const relationInInitialArray = initialArr.reduce((acc, rel)=>({
...acc,
[rel.id]: true
}), {});
// Map to store the first index where a relation id is connected
const mappedRelations = connectArr.reduce((mapper, relation)=>{
const adjacentRelId = relation.position?.before || relation.position?.after;
if (!adjacentRelId || !relationInInitialArray[adjacentRelId] && !mapper[adjacentRelId]) {
needsSorting = true;
}
/**
* We do not allow duplicate relations to be connected, so we need to check for uniqueness with components
* Note that the id here includes the uid for polymorphic relations
*
* So for normal relations, the same id means the same relation
* For component relations, it means the unique combo of (id, component name)
*/ // Check if there's an existing relation with this id
const existingRelation = mapper[relation.id];
// Check if existing relation has a component or not
const hasNoComponent = existingRelation && !('__component' in existingRelation);
// Check if the existing relation has the same component as the new relation
const hasSameComponent = existingRelation && existingRelation.__component === relation.__component;
// If we have an existing relation that is not unique (no component or same component) we won't accept it
if (existingRelation && (hasNoComponent || hasSameComponent)) {
throw new InvalidRelationError(`The relation with id ${relation.id} is already connected. ` + 'You cannot connect the same relation twice.');
}
return {
[relation.id]: {
...relation,
computed: false
},
...mapper
};
}, {});
// If we don't need to sort the connect array, we can return it as is
if (!needsSorting) return connectArr;
// Recursively compute in which order the relation should be connected
const computeRelation = (relation, relationsSeenInBranch)=>{
const adjacentRelId = relation.position?.before || relation.position?.after;
const adjacentRelation = mappedRelations[adjacentRelId];
// If the relation has already been seen in the current branch,
// it means there is a circular reference
if (adjacentRelId && relationsSeenInBranch[adjacentRelId]) {
throw new InvalidRelationError('A circular reference was found in the connect array. ' + 'One relation is trying to connect before/after another one that is trying to connect before/after it');
}
// This relation has already been computed
if (mappedRelations[relation.id]?.computed) {
return;
}
mappedRelations[relation.id].computed = true;
// Relation does not have a before or after attribute or is in the initial array
if (!adjacentRelId || relationInInitialArray[adjacentRelId]) {
sortedConnect.push(relation);
return;
}
// Look if id is referenced elsewhere in the array
if (mappedRelations[adjacentRelId]) {
computeRelation(adjacentRelation, {
...relationsSeenInBranch,
[relation.id]: true
});
sortedConnect.push(relation);
} else if (strictSort) {
// If we reach this point, it means that the adjacent relation is not in the connect array
// and it is not in the database.
throw new InvalidRelationError(`There was a problem connecting relation with id ${relation.id} at position ${JSON.stringify(relation.position)}. The relation with id ${adjacentRelId} needs to be connected first.`);
} else {
// We are in non-strict mode so we can push the relation.
sortedConnect.push({
id: relation.id,
position: {
end: true
}
});
}
};
// Iterate over connectArr and populate sortedConnect
connectArr.forEach((relation)=>computeRelation(relation, {}));
return sortedConnect;
};
/**
* Responsible for calculating the relations order when connecting them.
*
* The connect method takes an array of relations with positional attributes:
* - before: the id of the relation to connect before
* - after: the id of the relation to connect after
* - end: it should be at the end
* - start: it should be at the start
*
* Example:
* - Having a connect array like:
* [ { id: 4, before: 2 }, { id: 4, before: 3}, {id: 5, before: 4} ]
* - With the initial relations:
* [ { id: 2, order: 4 }, { id: 3, order: 10 } ]
* - Step by step, going through the connect array, the array of relations would be:
* [ { id: 4, order: 3.5 }, { id: 2, order: 4 }, { id: 3, order: 10 } ]
* [ { id: 2, order: 4 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
* [ { id: 2, order: 4 }, { id: 5, order: 3.5 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
* - The final step would be to recalculate fractional order values.
* [ { id: 2, order: 4 }, { id: 5, order: 3.33 }, { id: 4, order: 3.66 }, { id: 3, order: 10 } ]
*
* @param {Array<*>} initArr - array of relations to initialize the class with
* @param {string} idColumn - the column name of the id
* @param {string} orderColumn - the column name of the order
* @param {boolean} strict - if true, will throw an error if a relation is connected adjacent to
* another one that does not exist
* @return {*}
*/ const relationsOrderer = (initArr, idColumn, orderColumn, strict)=>{
const computedRelations = castArray(initArr ?? []).map((r)=>({
init: true,
id: r[idColumn],
order: Number(r[orderColumn]) || 1
}));
const maxOrder = maxBy('order', computedRelations)?.order || 0;
const findRelation = (id)=>{
const idx = computedRelations.findIndex((r)=>r.id === id);
return {
idx,
relation: computedRelations[idx]
};
};
const removeRelation = (r)=>{
const { idx } = findRelation(r.id);
if (idx >= 0) {
computedRelations.splice(idx, 1);
}
};
const insertRelation = (r)=>{
let idx;
if (r.position?.before) {
const { idx: _idx, relation } = findRelation(r.position.before);
if (relation.init) {
r.order = relation.order - 0.5;
} else {
r.order = relation.order;
}
idx = _idx;
} else if (r.position?.after) {
const { idx: _idx, relation } = findRelation(r.position.after);
if (relation.init) {
r.order = relation.order + 0.5;
} else {
r.order = relation.order;
}
idx = _idx + 1;
} else if (r.position?.start) {
r.order = 0.5;
idx = 0;
} else {
r.order = maxOrder + 0.5;
idx = computedRelations.length;
}
// Insert the relation in the array
computedRelations.splice(idx, 0, r);
};
return {
disconnect (relations) {
castArray(relations).forEach((relation)=>{
removeRelation(relation);
});
return this;
},
connect (relations) {
sortConnectArray(castArray(relations), computedRelations, strict).forEach((relation)=>{
this.disconnect(relation);
try {
insertRelation(relation);
} catch (err) {
throw new Error(`There was a problem connecting relation with id ${relation.id} at position ${JSON.stringify(relation.position)}. The list of connect relations is not valid`);
}
});
return this;
},
get () {
return computedRelations;
},
/**
* Get a map between the relation id and its order
*/ getOrderMap () {
return _(computedRelations).groupBy('order').reduce((acc, relations)=>{
if (relations[0]?.init) return acc;
relations.forEach((relation, idx)=>{
acc[relation.id] = Math.floor(relation.order) + (idx + 1) / (relations.length + 1);
});
return acc;
}, {});
}
};
};
export { relationsOrderer, sortConnectArray };
//# sourceMappingURL=relations-orderer.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,79 @@
import type { Knex } from 'knex';
import type { CountResult, ID } from '../types';
import { QueryBuilder } from '../query/query-builder';
export type Data = Record<string, unknown>;
export type Params = {
where?: any;
filters?: any;
select?: any;
populate?: any;
orderBy?: any;
_q?: string;
data?: any;
page?: number;
pageSize?: number;
limit?: number;
offset?: number;
count?: boolean;
};
export type FindOneParams = Pick<Params, 'where' | 'select' | 'populate' | '_q' | 'orderBy'>;
export interface Repository {
findOne(params?: FindOneParams): Promise<any>;
findMany(params?: Params): Promise<any[]>;
findWithCount(params?: Params): Promise<[any[], number]>;
findPage(params: Params): Promise<{
results: any[];
pagination: {
page: number;
pageSize: number;
pageCount: number;
total: number;
};
}>;
create(params: Params): Promise<any>;
createMany(params: Params): Promise<CountResult & {
ids: ID[];
}>;
update(params: Params): Promise<any>;
updateMany(params: Params): Promise<CountResult>;
delete(params: Params): Promise<any>;
deleteMany(params?: Params): Promise<CountResult>;
count(params?: Params): Promise<number>;
attachRelations(id: ID, data: Data): Promise<any>;
updateRelations(id: ID, data: Data): Promise<any>;
deleteRelations(id: ID): Promise<any>;
populate(entity: Entity, populate: Params['populate']): Promise<any>;
load(entity: any, field: string | string[], populate?: Params['populate']): Promise<any>;
loadPages<TField extends string>(entity: any, field: TField | TField[], populate?: Params['populate']): Promise<any>;
}
export type Entity = {
id: ID;
[key: string]: any;
};
export interface EntityManager {
findOne(uid: string, params: Params): Promise<any>;
findMany(uid: string, params: Params): Promise<any[]>;
count(uid: string, params?: Params): Promise<number>;
create(uid: string, params: Params): Promise<any>;
createMany(uid: string, params: Params): Promise<CountResult & {
ids: ID[];
}>;
update(uid: string, params: Params): Promise<any>;
updateMany(uid: string, params: Params): Promise<CountResult>;
delete(uid: string, params: Params): Promise<any>;
deleteMany(uid: string, params: Params): Promise<CountResult>;
populate(uid: string, entity: Entity, populate: Params['populate']): Promise<Entity>;
load(uid: string, entity: Entity, field: string | string[], populate?: Params['populate']): Promise<any>;
attachRelations(uid: string, id: ID, data: any, options?: {
transaction?: Knex.Transaction;
}): Promise<any>;
updateRelations(uid: string, id: ID, data: any, options?: {
transaction?: Knex.Transaction;
}): Promise<any>;
deleteRelations(uid: string, id: ID, options?: {
transaction?: Knex.Transaction;
}): Promise<void>;
createQueryBuilder(uid: string): QueryBuilder;
getRepository(uid: string): Repository;
}
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entity-manager/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAE7F,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1C,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAChC,OAAO,EAAE,GAAG,EAAE,CAAC;QACf,UAAU,EAAE;YACV,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,MAAM,CAAC;YACjB,SAAS,EAAE,MAAM,CAAC;YAClB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC,CAAC;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG;QAAE,GAAG,EAAE,EAAE,EAAE,CAAA;KAAE,CAAC,CAAC;IACjE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,eAAe,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzF,SAAS,CAAC,MAAM,SAAS,MAAM,EAC7B,MAAM,EAAE,GAAG,EACX,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,GAC5B,OAAO,CAAC,GAAG,CAAC,CAAC;CACjB;AAED,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,EAAE,CAAC;IACP,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAGF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACtD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG;QAAE,GAAG,EAAE,EAAE,EAAE,CAAA;KAAE,CAAC,CAAC;IAC9E,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrF,IAAI,CACF,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,GAC5B,OAAO,CAAC,GAAG,CAAC,CAAC;IAChB,eAAe,CACb,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,IAAI,CAAC,WAAW,CAAA;KAAE,GAC3C,OAAO,CAAC,GAAG,CAAC,CAAC;IAChB,eAAe,CACb,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,IAAI,CAAC,WAAW,CAAA;KAAE,GAC3C,OAAO,CAAC,GAAG,CAAC,CAAC;IAChB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,IAAI,CAAC,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClG,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC;IAC9C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;CACxC"}