New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@strapi/database

Package Overview
Dependencies
Maintainers
9
Versions
1594
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@strapi/database - npm Package Compare versions

Comparing version 4.0.0-next.2 to 4.0.0-next.3

lib/lifecycles.js

380

examples/index.js
'use strict';
const util = require('util');
const _ = require('lodash');

@@ -74,381 +73,2 @@ const { Database } = require('../lib/index');

// (async function() {
// for (const key in connections) {
// await main(connections[key]);
// }
// })().catch(err => {
// console.error(err);
// process.exit();
// });
main(connections.sqlite);
// const entityService = uid => {
// // knows more about abstraction then the query layer
// // will be moved in the core services not the db
// // D&P should wrap some d&p logic
// // i18N should wrapp some i18n logic etc etc
// const repo = orm.query(uid);
// return {
// findById(id) {
// return repo.findOne({ where: { id } });
// },
// async update(id, data) {
// await repo.update({ where: { id }, data });
// return repo.findOne({ where: { id } });
// },
// };
// };
/*
db.migration.create();
db.migration.up();
db.migration.down();
db.seed.run();
db.seed.create();
db.exporter.dump()
db.importer.restore()
db.schema.addField
db.schema.removeField
db.schema.addCollection
db.schema.removeCollection
*/
// const r = await orm.entityManager
// .createQueryBuilder('article')
// .select('*')
// .join({
// rootColumn: 'id',
// alias: 'ac',
// referencedTable: 'categories_articles_articles_links',
// referencedColumn: 'article_id',
// })
// .join({
// rootTable: 'ac',
// rootColumn: 'category_id',
// alias: 'c',
// referencedTable: 'categories',
// referencedColumn: 'id',
// })
// .where({
// $and: [],
// })
// .populate({
// category: true,
// })
// .execute();
// console.log(r);
// const catA = await entityService('category').findById(348);
// console.log(catA);
/*
orm.contentType('category').loadRelation()
=> query('category').load('xxx')
*/
// await orm.query('category').delete();
// const article = await orm.query('article').create({
// select: ['id', 'title'],
// populate: {
// category: {
// select: ['price'],
// where: {
// price: {
// $gte: 12,
// },
// },
// },
// },
// data: {
// title: 'my category',
// category_id: 1, // TODO: handle category: 1 too
// },
// });
// console.log(JSON.stringify(article, null, 4));
const tests = async orm => {
// await orm.query('category').createMany({
// // select: {},
// // populate: {},
// data: Array(5)
// .fill({})
// .map((v, idx) => ({
// title: `Category ${_.padStart(idx, 3, '0')}`,
// articles: [idx + 1, idx + 2],
// })),
// });
await orm.query('article').createMany({
// select: {},
// populate: {},
data: Array(5)
.fill({})
.map((v, idx) => ({
title: `Article ${_.padStart(idx, 3, '0')}`,
// category_id: idx + 1,
category: idx + 1,
})),
});
const cat = await orm.query('category').create({
data: {
articles: [1, 2, 3, 4, 5],
},
populate: ['articles'],
});
console.log(cat);
const tag = await orm.query('tag').create({
data: {
articles: [1, 2, 3, 4, 5],
},
populate: ['articles'],
});
console.log(tag);
const someArticles = await orm.query('article').findMany({
where: {
id: [1, 2, 3, 4, 5],
},
populate: ['tags', 'category'],
});
console.log(someArticles);
await orm.query('category').updateMany({
where: {
// articles: {
// title: {
// $contains: 'Category',
// },
// },
},
data: {
title: 'Category 007',
},
});
let r = await orm.query('category').findMany({
where: {
$and: [
{
title: {
$ne: 'salut',
},
},
],
title: 'test',
price: {
$gt: 12,
},
articles: {
title: {
$startsWith: 'Test',
// $mode: 'insensitive',
},
},
compo: {
key: 'x',
},
},
});
// escape stuff
// const raw = (strings, )
const params = {
select: ['id', 'title'],
where: {
$not: {
$or: [
{
articles: {
category: {
title: 'Category 003',
},
},
},
{
title: {
$in: ['Category 001', 'Category 002'],
},
},
{
title: {
$not: 'Category 001',
},
},
],
},
},
orderBy: [{ articles: { title: 'asc' } }],
limit: 10,
offset: 0,
};
r = await orm.query('category').findMany(params);
console.log(r);
r = await orm.query('category').findMany({
where: {
$or: [
{
compo: {
value: {
$gte: 3,
},
},
},
{
articles: {
title: {
$contains: 'test',
},
},
},
],
},
});
console.log(r);
r = await orm.query('user').findMany({
where: {
address: {
name: 'A',
},
},
});
console.log(r);
const [results, count] = await orm.query('category').findWithCount(params);
console.log({ results, count });
await orm.query('category', {
populate: ['articles'],
});
const address = await orm.query('address').findMany({
populate: {
user: {
// select: ['id', 'address_id'],
},
},
});
console.log(address);
const user = await orm.query('user').findMany({
populate: {
address: {
// select: ['id', 'name'],
},
},
});
console.log(user);
const article = await orm.query('article').findMany({
populate: {
category: true,
},
});
console.log(article);
await orm.query('category').findMany({
populate: {
compo: true,
articles: {
select: ['title'],
populate: {
category: {
select: ['title'],
},
},
},
},
limit: 5,
});
await orm.query('article').findMany({
populate: {
tags: true,
},
limit: 5,
});
await orm.query('tag').findMany({
populate: {
articles: true,
},
limit: 5,
});
// const articleCategory = orm.query('article').load(article, 'category', {
// select: ['id', 'title'],
// limit: 5,
// offset: 2,
// orderBy: 'title',
// where: {},
// });
// const article = await orm.query('article').populate(article, {
// category: true,
// tags: true,
// });
await orm.query('article').findMany({
where: {
$not: {
$or: [
{
category: {
title: 'Article 003',
},
},
{
title: {
$in: ['Article 001', 'Article 002'],
},
},
],
title: {
$not: 'Article 007',
},
},
},
orderBy: [{ category: { name: 'asc' } }],
offset: 0,
limit: 10,
populate: {
category: {
orderBy: 'title',
populate: {
categories: {
populate: {
tags: true,
},
},
},
},
seo: true,
},
});
};

110

lib/entity-manager.js

@@ -14,2 +14,3 @@ 'use strict';

// TODO: move to query layer
// TODO: handle programmatic defaults
const toRow = (metadata, data = {}) => {

@@ -90,21 +91,33 @@ const { attributes } = metadata;

return {
findOne(uid, params) {
const qb = this.createQueryBuilder(uid)
async findOne(uid, params) {
await db.lifecycles.run('beforeFindOne', uid, { params });
const result = await this.createQueryBuilder(uid)
.init(params)
.first();
.first()
.execute();
return qb.execute();
await db.lifecycles.run('afterFindOne', uid, { params, result });
return result;
},
// should we name it findOne because people are used to it ?
findMany(uid, params) {
const qb = this.createQueryBuilder(uid).init(params);
async findMany(uid, params) {
await db.lifecycles.run('beforeFindMany', uid, { params });
return qb.execute();
const result = await this.createQueryBuilder(uid)
.init(params)
.execute();
await db.lifecycles.run('afterFindMany', uid, { params, result });
return result;
},
async count(uid, params = {}) {
const qb = this.createQueryBuilder(uid).where(params.where);
await db.lifecycles.run('beforeCount', uid, { params });
const res = await qb
const res = await this.createQueryBuilder(uid)
.where(params.where)
.count()

@@ -114,9 +127,13 @@ .first()

return Number(res.count);
const result = Number(res.count);
await db.lifecycles.run('afterCount', uid, { params, result });
return result;
},
async create(uid, params = {}) {
// create entry in DB
await db.lifecycles.run('beforeCreate', uid, { params });
const metadata = db.metadata.get(uid);
const { data } = params;

@@ -128,4 +145,2 @@

// transform value to storage value
// apply programatic defaults if any -> I think this should be handled outside of this layer as we might have some applicative rules in the entity service
const dataToInsert = toRow(metadata, data);

@@ -137,20 +152,27 @@

// create relation associations or move this to the entity service & call attach on the repo instead
await this.attachRelations(uid, id, data);
// TODO: in case there is not select or populate specified return the inserted data ?
// TODO: do not trigger the findOne lifecycles ?
const result = await this.findOne(uid, {
where: { id },
select: params.select,
populate: params.populate,
});
return this.findOne(uid, { where: { id }, select: params.select, populate: params.populate });
await db.lifecycles.run('afterCreate', uid, { params, result });
return result;
},
// TODO: where do we handle relation processing for many queries ?
async createMany(uid, params = {}) {
await db.lifecycles.run('beforeCreateMany', uid, { params });
const metadata = db.metadata.get(uid);
const { data } = params;
if (!_.isArray(data)) {
throw new Error('CreateMany expecets data to be an array');
throw new Error('CreateMany expects data to be an array');
}
const metadata = db.metadata.get(uid);
const dataToInsert = data.map(datum => toRow(metadata, datum));

@@ -166,8 +188,14 @@

return { count: data.length };
const result = { count: data.length };
await db.lifecycles.run('afterCreateMany', uid, { params, result });
return result;
},
async update(uid, params = {}) {
await db.lifecycles.run('beforeUpdate', uid, { params });
const metadata = db.metadata.get(uid);
const { where, data } = params;
const metadata = db.metadata.get(uid);

@@ -189,3 +217,2 @@ if (!_.isPlainObject(data)) {

if (!entity) {
// TODO: or throw ?
return null;

@@ -207,10 +234,20 @@ }

return this.findOne(uid, { where: { id }, select: params.select, populate: params.populate });
// TODO: do not trigger the findOne lifecycles ?
const result = await this.findOne(uid, {
where: { id },
select: params.select,
populate: params.populate,
});
await db.lifecycles.run('afterUpdate', uid, { params, result });
return result;
},
// TODO: where do we handle relation processing for many queries ?
async updateMany(uid, params = {}) {
await db.lifecycles.run('beforeUpdateMany', uid, { params });
const metadata = db.metadata.get(uid);
const { where, data } = params;
const metadata = db.metadata.get(uid);
const dataToUpdate = toRow(metadata, data);

@@ -227,6 +264,12 @@

return { count: updatedRows };
const result = { count: updatedRows };
await db.lifecycles.run('afterUpdateMany', uid, { params, result });
return result;
},
async delete(uid, params = {}) {
await db.lifecycles.run('beforeDelete', uid, { params });
const { where, select, populate } = params;

@@ -238,2 +281,3 @@

// TODO: avoid trigger the findOne lifecycles in the case ?
const entity = await this.findOne(uid, {

@@ -258,2 +302,4 @@ select: select && ['id'].concat(select),

await db.lifecycles.run('afterDelete', uid, { params, result: entity });
return entity;

@@ -264,2 +310,4 @@ },

async deleteMany(uid, params = {}) {
await db.lifecycles.run('beforeDeleteMany', uid, { params });
const { where } = params;

@@ -272,3 +320,7 @@

return { count: deletedRows };
const result = { count: deletedRows };
await db.lifecycles.run('afterDelete', uid, { params, result });
return result;
},

@@ -736,2 +788,3 @@

// TODO: support multiple relations at once with the populate syntax
// TODO: add lifecycle events
async populate(uid, entity, populate) {

@@ -748,2 +801,3 @@ const entry = await this.findOne(uid, {

// TODO: support multiple relations at once with the populate syntax
// TODO: add lifecycle events
async load(uid, entity, field, params) {

@@ -750,0 +804,0 @@ const { attributes } = db.metadata.get(uid);

@@ -9,2 +9,3 @@ 'use strict';

const { createEntityManager } = require('./entity-manager');
const { createLifecyclesManager } = require('./lifecycles');

@@ -24,5 +25,7 @@ // TODO: move back into strapi

// TODO: migrations -> allow running them through cli before startup
this.schema = createSchemaProvider(this);
// TODO: migrations -> allow running them through cli before startup
this.lifecycles = createLifecyclesManager(this);
this.entityManager = createEntityManager(this);

@@ -33,3 +36,12 @@ }

await this.dialect.initialize();
this.connection = knex(this.config.connection);
// register module lifeycles subscriber
this.lifecycles.subscribe(async event => {
const { model } = event;
if (event.action in model.lifecycles) {
await model.lifecycles[event.action](event);
}
});
}

@@ -46,2 +58,3 @@

async destroy() {
await this.lifecycles.clear();
await this.connection.destroy();

@@ -48,0 +61,0 @@ }

@@ -71,2 +71,3 @@ /**

},
lifecycles: model.lifecycles || {},
});

@@ -73,0 +74,0 @@ }

@@ -519,2 +519,6 @@ 'use strict';

if (_.isEmpty(results)) {
return results;
}
for (const key in populate) {

@@ -583,30 +587,12 @@ // NOTE: Omit limit & offset to avoid needing a query per result to avoid making too many queries

const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
if (isCount) {
const rows = await qb
.init(populateValue)
.join({
alias: alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on,
})
.select([`${alias}.${joinColumnName}`, qb.raw('count(*) AS count')])
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.groupBy(`${alias}.${joinColumnName}`)
.execute({ mapResults: false });
const referencedValues = _.uniq(
results.map(r => r[referencedColumnName]).filter(value => !_.isNil(value))
);
const map = rows.reduce((map, row) => {
map[row[joinColumnName]] = { count: row.count };
return map;
}, {});
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = map[result[referencedColumnName]] || { count: 0 };
result[key] = null;
});
continue;

@@ -625,6 +611,4 @@ }

})
.addSelect(`${alias}.${joinColumnName}`)
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });

@@ -687,4 +671,16 @@

const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const referencedValues = _.uniq(
results.map(r => r[referencedColumnName]).filter(value => !_.isNil(value))
);
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = { count: 0 };
});
continue;
}
const rows = await qb

@@ -700,7 +696,5 @@ .init(populateValue)

})
.select([`${alias}.${joinColumnName}`, qb.raw('count(*) AS count')])
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.groupBy(`${alias}.${joinColumnName}`)
.select([joinColAlias, qb.raw('count(*) AS count')])
.where({ [joinColAlias]: referencedValues })
.groupBy(joinColAlias)
.execute({ mapResults: false });

@@ -720,2 +714,9 @@

if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = [];
});
continue;
}
const rows = await qb

@@ -731,6 +732,4 @@ .init(populateValue)

})
.addSelect(`${alias}.${joinColumnName}`)
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });

@@ -755,4 +754,15 @@

const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const referencedValues = _.uniq(
results.map(r => r[referencedColumnName]).filter(value => !_.isNil(value))
);
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = { count: 0 };
});
continue;
}
const rows = await qb

@@ -768,7 +778,5 @@ .init(populateValue)

})
.select([`${alias}.${joinColumnName}`, qb.raw('count(*) AS count')])
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.groupBy(`${alias}.${joinColumnName}`)
.select([joinColAlias, qb.raw('count(*) AS count')])
.where({ [joinColAlias]: referencedValues })
.groupBy(joinColAlias)
.execute({ mapResults: false });

@@ -788,2 +796,9 @@

if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = [];
});
continue;
}
const rows = await qb

@@ -799,6 +814,4 @@ .init(populateValue)

})
.addSelect(`${alias}.${joinColumnName}`)
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });

@@ -805,0 +818,0 @@

@@ -33,2 +33,9 @@ 'use strict';

select(args) {
state.type = 'select';
state.select = _.castArray(args).map(col => this.aliasColumn(col));
return this;
},
insert(data) {

@@ -69,9 +76,2 @@ state.type = 'insert';

select(args) {
state.type = 'select';
state.select = _.castArray(args).map(col => this.aliasColumn(col));
return this;
},
addSelect(args) {

@@ -78,0 +78,0 @@ state.select.push(..._.castArray(args).map(col => this.aliasColumn(col)));

@@ -75,4 +75,10 @@ 'use strict';

// TODO: drop foreign keys targeting delete tables
// drop all delete table foreign keys then delete the tables
for (const table of schemaDiff.tables.removed) {
debug(`Removing table foreign keys: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
await dropTableForeignKeys(schemaBuilder, table);
}
for (const table of schemaDiff.tables.removed) {

@@ -82,3 +88,2 @@ debug(`Removing table: ${table.name}`);

const schemaBuilder = this.getSchemaBuilder(table, trx);
// TODO: add cascading if possible
await dropTable(schemaBuilder, table);

@@ -293,2 +298,4 @@ }

throw new Error(`Table already exists ${table.name}`);
// TODO: alter the table instead
}

@@ -306,2 +313,9 @@

/**
* Drops a table from a database
* @param {Knex.SchemaBuilder} schemaBuilder
* @param {Table} table
*/
const dropTable = (schemaBuilder, table) => schemaBuilder.dropTableIfExists(table.name);
/**
* Creates a table foreign keys constraints

@@ -319,6 +333,11 @@ * @param {SchemaBuilder} schemaBuilder

/**
* Drops a table from a database
* @param {Knex.SchemaBuilder} schemaBuilder
* Drops a table foreign keys constraints
* @param {SchemaBuilder} schemaBuilder
* @param {Table} table
*/
const dropTable = (schemaBuilder, table) => schemaBuilder.dropTableIfExists(table.name);
const dropTableForeignKeys = async (schemaBuilder, table) => {
// foreign keys
await schemaBuilder.table(table.name, tableBuilder => {
(table.foreignKeys || []).forEach(foreignKey => dropForeignKey(tableBuilder, foreignKey));
});
};
{
"name": "@strapi/database",
"version": "4.0.0-next.2",
"version": "4.0.0-next.3",
"description": "Strapi's database layer",

@@ -40,3 +40,3 @@ "homepage": "https://strapi.io",

},
"gitHead": "50849e68c7d3632eb5899c6346e33d71a211a3bd"
"gitHead": "22eb6f24d86dae4bd6dff8293916ec40e2f44d33"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc