@strapi/database 4.0.0-next.9 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dialects/dialect.js +45 -0
- package/lib/dialects/index.js +6 -112
- package/lib/dialects/mysql/index.js +51 -0
- package/lib/dialects/mysql/schema-inspector.js +199 -0
- package/lib/dialects/postgresql/index.js +49 -0
- package/lib/dialects/postgresql/schema-inspector.js +232 -0
- package/lib/dialects/sqlite/index.js +74 -0
- package/lib/dialects/sqlite/schema-inspector.js +151 -0
- package/lib/entity-manager.js +18 -14
- package/lib/entity-repository.js +2 -3
- package/lib/errors.js +44 -2
- package/lib/fields.d.ts +2 -3
- package/lib/fields.js +7 -16
- package/lib/index.d.ts +53 -8
- package/lib/index.js +44 -27
- package/lib/lifecycles/index.d.ts +50 -0
- package/lib/{lifecycles.js → lifecycles/index.js} +25 -14
- package/lib/lifecycles/subscribers/index.d.ts +9 -0
- package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
- package/lib/lifecycles/subscribers/timestamps.js +65 -0
- package/lib/metadata/index.js +84 -95
- package/lib/metadata/relations.js +16 -0
- package/lib/migrations/index.d.ts +9 -0
- package/lib/migrations/index.js +69 -0
- package/lib/migrations/storage.js +51 -0
- package/lib/query/helpers/join.js +3 -5
- package/lib/query/helpers/order-by.js +21 -11
- package/lib/query/helpers/populate.js +35 -10
- package/lib/query/helpers/search.js +26 -12
- package/lib/query/helpers/transform.js +42 -14
- package/lib/query/helpers/where.js +92 -57
- package/lib/query/query-builder.js +116 -34
- package/lib/schema/__tests__/schema-diff.test.js +14 -1
- package/lib/schema/builder.js +315 -284
- package/lib/schema/diff.js +376 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +47 -50
- package/lib/schema/schema.js +21 -18
- package/lib/schema/storage.js +79 -0
- package/lib/utils/content-types.js +0 -1
- package/package.json +27 -21
- package/lib/configuration.js +0 -49
- package/lib/schema/schema-diff.js +0 -337
- package/lib/schema/schema-storage.js +0 -44
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
const errors = require('../../errors');
|
|
7
|
+
const { Dialect } = require('../dialect');
|
|
8
|
+
const SqliteSchmeaInspector = require('./schema-inspector');
|
|
9
|
+
|
|
10
|
+
class SqliteDialect extends Dialect {
|
|
11
|
+
constructor(db) {
|
|
12
|
+
super(db);
|
|
13
|
+
|
|
14
|
+
this.schemaInspector = new SqliteSchmeaInspector(db);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
configure() {
|
|
18
|
+
this.db.config.connection.connection.filename = path.resolve(
|
|
19
|
+
this.db.config.connection.connection.filename
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const dbDir = path.dirname(this.db.config.connection.connection.filename);
|
|
23
|
+
|
|
24
|
+
fse.ensureDirSync(dbDir);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async initialize() {
|
|
28
|
+
await this.db.connection.raw('pragma foreign_keys = on');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
canAlterConstraints() {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getSqlType(type) {
|
|
36
|
+
switch (type) {
|
|
37
|
+
// FIXME: enum must be dealt separately
|
|
38
|
+
case 'enum': {
|
|
39
|
+
return 'text';
|
|
40
|
+
}
|
|
41
|
+
case 'double':
|
|
42
|
+
case 'decimal': {
|
|
43
|
+
return 'float';
|
|
44
|
+
}
|
|
45
|
+
case 'timestamp': {
|
|
46
|
+
return 'datetime';
|
|
47
|
+
}
|
|
48
|
+
default: {
|
|
49
|
+
return type;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async startSchemaUpdate() {
|
|
55
|
+
await this.db.connection.raw(`pragma foreign_keys = off`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async endSchemaUpdate() {
|
|
59
|
+
await this.db.connection.raw(`pragma foreign_keys = on`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
transformErrors(error) {
|
|
63
|
+
switch (error.errno) {
|
|
64
|
+
case 19: {
|
|
65
|
+
throw new errors.NotNullConstraint(); // TODO: extract column name
|
|
66
|
+
}
|
|
67
|
+
default: {
|
|
68
|
+
super.transformErrors(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = SqliteDialect;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SQL_QUERIES = {
|
|
4
|
+
TABLE_LIST: `select name from sqlite_master where type = 'table' and name NOT LIKE 'sqlite%'`,
|
|
5
|
+
TABLE_INFO: `pragma table_info(??)`,
|
|
6
|
+
INDEX_LIST: 'pragma index_list(??)',
|
|
7
|
+
INDEX_INFO: 'pragma index_info(??)',
|
|
8
|
+
FOREIGN_KEY_LIST: 'pragma foreign_key_list(??)',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const toStrapiType = column => {
|
|
12
|
+
const { type } = column;
|
|
13
|
+
|
|
14
|
+
const rootType = type.toLowerCase().match(/[^(), ]+/)[0];
|
|
15
|
+
|
|
16
|
+
switch (rootType) {
|
|
17
|
+
case 'integer': {
|
|
18
|
+
if (column.pk) {
|
|
19
|
+
return { type: 'increments', args: [{ primary: true }] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { type: 'integer' };
|
|
23
|
+
}
|
|
24
|
+
case 'float': {
|
|
25
|
+
return { type: 'float', args: [10, 2] };
|
|
26
|
+
}
|
|
27
|
+
case 'bigint': {
|
|
28
|
+
return { type: 'bigInteger' };
|
|
29
|
+
}
|
|
30
|
+
case 'varchar': {
|
|
31
|
+
const length = type.slice(8, type.length - 1);
|
|
32
|
+
|
|
33
|
+
return { type: 'string', args: [Number(length)] };
|
|
34
|
+
}
|
|
35
|
+
case 'text': {
|
|
36
|
+
return { type: 'text', args: ['longtext'] };
|
|
37
|
+
}
|
|
38
|
+
case 'json': {
|
|
39
|
+
return { type: 'jsonb' };
|
|
40
|
+
}
|
|
41
|
+
case 'boolean': {
|
|
42
|
+
return { type: 'boolean' };
|
|
43
|
+
}
|
|
44
|
+
case 'datetime': {
|
|
45
|
+
return { type: 'datetime', args: [{ useTz: false, precision: 6 }] };
|
|
46
|
+
}
|
|
47
|
+
case 'date': {
|
|
48
|
+
return { type: 'date' };
|
|
49
|
+
}
|
|
50
|
+
case 'time': {
|
|
51
|
+
return { type: 'time', args: [{ precision: 3 }] };
|
|
52
|
+
}
|
|
53
|
+
default: {
|
|
54
|
+
return { type: 'specificType', args: [column.data_type] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
class SqliteSchemaInspector {
|
|
60
|
+
constructor(db) {
|
|
61
|
+
this.db = db;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getSchema() {
|
|
65
|
+
const schema = { tables: [] };
|
|
66
|
+
const tables = await this.getTables();
|
|
67
|
+
|
|
68
|
+
for (const tableName of tables) {
|
|
69
|
+
const columns = await this.getColumns(tableName);
|
|
70
|
+
const indexes = await this.getIndexes(tableName);
|
|
71
|
+
const foreignKeys = await this.getForeignKeys(tableName);
|
|
72
|
+
|
|
73
|
+
schema.tables.push({
|
|
74
|
+
name: tableName,
|
|
75
|
+
columns,
|
|
76
|
+
indexes,
|
|
77
|
+
foreignKeys,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return schema;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getTables() {
|
|
85
|
+
const rows = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);
|
|
86
|
+
|
|
87
|
+
return rows.map(row => row.name);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async getColumns(tableName) {
|
|
91
|
+
const rows = await this.db.connection.raw(SQL_QUERIES.TABLE_INFO, [tableName]);
|
|
92
|
+
|
|
93
|
+
return rows.map(row => {
|
|
94
|
+
const { type, args = [], ...rest } = toStrapiType(row);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
type,
|
|
98
|
+
args,
|
|
99
|
+
name: row.name,
|
|
100
|
+
defaultTo: row.dflt_value,
|
|
101
|
+
notNullable: row.notnull !== null ? Boolean(row.notnull) : null,
|
|
102
|
+
unsigned: false,
|
|
103
|
+
...rest,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async getIndexes(tableName) {
|
|
109
|
+
const indexes = await this.db.connection.raw(SQL_QUERIES.INDEX_LIST, [tableName]);
|
|
110
|
+
|
|
111
|
+
const ret = [];
|
|
112
|
+
|
|
113
|
+
for (const index of indexes.filter(index => !index.name.startsWith('sqlite_'))) {
|
|
114
|
+
const res = await this.db.connection.raw(SQL_QUERIES.INDEX_INFO, [index.name]);
|
|
115
|
+
|
|
116
|
+
ret.push({
|
|
117
|
+
columns: res.map(row => row.name),
|
|
118
|
+
name: index.name,
|
|
119
|
+
type: index.unique ? 'unique' : null,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return ret;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getForeignKeys(tableName) {
|
|
127
|
+
const fks = await this.db.connection.raw(SQL_QUERIES.FOREIGN_KEY_LIST, [tableName]);
|
|
128
|
+
|
|
129
|
+
const ret = {};
|
|
130
|
+
|
|
131
|
+
for (const fk of fks) {
|
|
132
|
+
if (!ret[fk.id]) {
|
|
133
|
+
ret[fk.id] = {
|
|
134
|
+
// TODO: name, // find name
|
|
135
|
+
columns: [fk.from],
|
|
136
|
+
referencedColumns: [fk.to],
|
|
137
|
+
referencedTable: fk.table,
|
|
138
|
+
onUpdate: fk.on_update.toUpperCase(),
|
|
139
|
+
onDelete: fk.on_delete.toUpperCase(),
|
|
140
|
+
};
|
|
141
|
+
} else {
|
|
142
|
+
ret[fk.id].columns.push(fk.from);
|
|
143
|
+
ret[fk.id].referencedColumns.push(fk.to);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Object.values(ret);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = SqliteSchemaInspector;
|
package/lib/entity-manager.js
CHANGED
|
@@ -29,8 +29,7 @@ const toAssocs = data => {
|
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
32
|
+
const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
34
33
|
const { attributes } = metadata;
|
|
35
34
|
|
|
36
35
|
const obj = {};
|
|
@@ -38,7 +37,6 @@ const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
|
38
37
|
for (const attributeName in attributes) {
|
|
39
38
|
const attribute = attributes[attributeName];
|
|
40
39
|
|
|
41
|
-
// TODO: convert to column name
|
|
42
40
|
if (types.isScalar(attribute.type)) {
|
|
43
41
|
const field = createField(attribute);
|
|
44
42
|
|
|
@@ -65,7 +63,6 @@ const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
|
65
63
|
if (types.isRelation(attribute.type)) {
|
|
66
64
|
// oneToOne & manyToOne
|
|
67
65
|
if (attribute.joinColumn && attribute.owner) {
|
|
68
|
-
// TODO: ensure joinColumn name respect convention ?
|
|
69
66
|
const joinColumnName = attribute.joinColumn.name;
|
|
70
67
|
|
|
71
68
|
// allow setting to null
|
|
@@ -145,7 +142,7 @@ const createEntityManager = db => {
|
|
|
145
142
|
await db.lifecycles.run('beforeCount', uid, { params });
|
|
146
143
|
|
|
147
144
|
const res = await this.createQueryBuilder(uid)
|
|
148
|
-
.init(_.pick(['_q', 'where'], params))
|
|
145
|
+
.init(_.pick(['_q', 'where', 'filters'], params))
|
|
149
146
|
.count()
|
|
150
147
|
.first()
|
|
151
148
|
.execute();
|
|
@@ -167,7 +164,7 @@ const createEntityManager = db => {
|
|
|
167
164
|
throw new Error('Create expects a data object');
|
|
168
165
|
}
|
|
169
166
|
|
|
170
|
-
const dataToInsert =
|
|
167
|
+
const dataToInsert = processData(metadata, data, { withDefaults: true });
|
|
171
168
|
|
|
172
169
|
const [id] = await this.createQueryBuilder(uid)
|
|
173
170
|
.insert(dataToInsert)
|
|
@@ -175,7 +172,7 @@ const createEntityManager = db => {
|
|
|
175
172
|
|
|
176
173
|
await this.attachRelations(uid, id, data);
|
|
177
174
|
|
|
178
|
-
// TODO: in case there is
|
|
175
|
+
// TODO: in case there is no select or populate specified return the inserted data ?
|
|
179
176
|
// TODO: do not trigger the findOne lifecycles ?
|
|
180
177
|
const result = await this.findOne(uid, {
|
|
181
178
|
where: { id },
|
|
@@ -199,7 +196,7 @@ const createEntityManager = db => {
|
|
|
199
196
|
throw new Error('CreateMany expects data to be an array');
|
|
200
197
|
}
|
|
201
198
|
|
|
202
|
-
const dataToInsert = data.map(datum =>
|
|
199
|
+
const dataToInsert = data.map(datum => processData(metadata, datum, { withDefaults: true }));
|
|
203
200
|
|
|
204
201
|
if (_.isEmpty(dataToInsert)) {
|
|
205
202
|
throw new Error('Nothing to insert');
|
|
@@ -242,7 +239,7 @@ const createEntityManager = db => {
|
|
|
242
239
|
|
|
243
240
|
const { id } = entity;
|
|
244
241
|
|
|
245
|
-
const dataToUpdate =
|
|
242
|
+
const dataToUpdate = processData(metadata, data);
|
|
246
243
|
|
|
247
244
|
if (!_.isEmpty(dataToUpdate)) {
|
|
248
245
|
await this.createQueryBuilder(uid)
|
|
@@ -272,7 +269,7 @@ const createEntityManager = db => {
|
|
|
272
269
|
const metadata = db.metadata.get(uid);
|
|
273
270
|
const { where, data } = params;
|
|
274
271
|
|
|
275
|
-
const dataToUpdate =
|
|
272
|
+
const dataToUpdate = processData(metadata, data);
|
|
276
273
|
|
|
277
274
|
if (_.isEmpty(dataToUpdate)) {
|
|
278
275
|
throw new Error('Update requires data');
|
|
@@ -299,7 +296,7 @@ const createEntityManager = db => {
|
|
|
299
296
|
throw new Error('Delete requires a where parameter');
|
|
300
297
|
}
|
|
301
298
|
|
|
302
|
-
// TODO:
|
|
299
|
+
// TODO: do not trigger the findOne lifecycles ?
|
|
303
300
|
const entity = await this.findOne(uid, {
|
|
304
301
|
select: select && ['id'].concat(select),
|
|
305
302
|
where,
|
|
@@ -472,7 +469,6 @@ const createEntityManager = db => {
|
|
|
472
469
|
const { joinTable } = attribute;
|
|
473
470
|
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
474
471
|
|
|
475
|
-
// TODO: validate logic of delete
|
|
476
472
|
if (isOneToAny(attribute) && isBidirectional(attribute)) {
|
|
477
473
|
await this.createQueryBuilder(joinTable.name)
|
|
478
474
|
.delete()
|
|
@@ -654,7 +650,10 @@ const createEntityManager = db => {
|
|
|
654
650
|
.where(joinTable.on || {})
|
|
655
651
|
.execute();
|
|
656
652
|
|
|
657
|
-
if (
|
|
653
|
+
if (
|
|
654
|
+
isBidirectional(attribute) &&
|
|
655
|
+
['oneToOne', 'oneToMany'].includes(attribute.relation)
|
|
656
|
+
) {
|
|
658
657
|
await this.createQueryBuilder(joinTable.name)
|
|
659
658
|
.delete()
|
|
660
659
|
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })
|
|
@@ -772,6 +771,7 @@ const createEntityManager = db => {
|
|
|
772
771
|
continue;
|
|
773
772
|
}
|
|
774
773
|
|
|
774
|
+
// do not need to delete links when using foreign keys
|
|
775
775
|
if (db.dialect.usesForeignKeys()) {
|
|
776
776
|
return;
|
|
777
777
|
}
|
|
@@ -812,7 +812,7 @@ const createEntityManager = db => {
|
|
|
812
812
|
const entry = await this.findOne(uid, {
|
|
813
813
|
select: ['id'],
|
|
814
814
|
where: { id: entity.id },
|
|
815
|
-
populate
|
|
815
|
+
populate,
|
|
816
816
|
});
|
|
817
817
|
|
|
818
818
|
return Object.assign({}, entity, entry);
|
|
@@ -837,6 +837,10 @@ const createEntityManager = db => {
|
|
|
837
837
|
},
|
|
838
838
|
});
|
|
839
839
|
|
|
840
|
+
if (!entry) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
|
|
840
844
|
return entry[field];
|
|
841
845
|
},
|
|
842
846
|
|
package/lib/entity-repository.js
CHANGED
|
@@ -95,9 +95,8 @@ const createRepository = (uid, db) => {
|
|
|
95
95
|
return db.entityManager.deleteRelations(uid, id);
|
|
96
96
|
},
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return db.entityManager.populate(uid, entity, field, params);
|
|
98
|
+
populate(entity, populate) {
|
|
99
|
+
return db.entityManager.populate(uid, entity, populate);
|
|
101
100
|
},
|
|
102
101
|
|
|
103
102
|
load(entity, field, params) {
|
package/lib/errors.js
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/* DatabaseError */
|
|
4
|
+
class DatabaseError extends Error {
|
|
5
|
+
constructor(message, details = {}) {
|
|
6
|
+
super();
|
|
7
|
+
this.name = 'DatabaseError';
|
|
8
|
+
this.message = message || 'A database error occured';
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class NotNullConstraint extends DatabaseError {
|
|
4
14
|
constructor({ column = '' } = {}) {
|
|
5
15
|
super();
|
|
6
16
|
this.name = 'NotNullConstraint';
|
|
7
|
-
this.message = `Not null constraint violation${column ? `
|
|
17
|
+
this.message = `Not null constraint violation${column ? ` on column ${column}` : ''}.`;
|
|
18
|
+
this.details = { column };
|
|
8
19
|
this.stack = '';
|
|
9
20
|
}
|
|
10
21
|
}
|
|
11
22
|
|
|
23
|
+
class InvalidTimeError extends DatabaseError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super();
|
|
26
|
+
this.name = 'InvalidTimeFormat';
|
|
27
|
+
this.message = message || 'Invalid time format, expected HH:mm:ss.SSS';
|
|
28
|
+
this.details = {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class InvalidDateError extends DatabaseError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super();
|
|
35
|
+
this.name = 'InvalidTimeFormat';
|
|
36
|
+
this.message = message || 'Invalid date format, expected YYYY-MM-DD';
|
|
37
|
+
this.details = {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class InvalidDateTimeError extends DatabaseError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super();
|
|
44
|
+
this.name = 'InvalidTimeFormat';
|
|
45
|
+
this.message = message || 'Invalid datetime format, expected a timestamp or an ISO date';
|
|
46
|
+
this.details = {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
12
50
|
module.exports = {
|
|
51
|
+
DatabaseError,
|
|
13
52
|
NotNullConstraint,
|
|
53
|
+
InvalidTimeError,
|
|
54
|
+
InvalidDateError,
|
|
55
|
+
InvalidDateTimeError,
|
|
14
56
|
};
|
package/lib/fields.d.ts
CHANGED
package/lib/fields.js
CHANGED
|
@@ -2,22 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash/fp');
|
|
4
4
|
const dateFns = require('date-fns');
|
|
5
|
+
const { InvalidTimeError, InvalidDateError, InvalidDateTimeError } = require('./errors');
|
|
5
6
|
|
|
6
7
|
class Field {
|
|
7
8
|
constructor(config) {
|
|
8
9
|
this.config = config;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
// TODO: impl
|
|
12
|
-
validate() {
|
|
13
|
-
// // use config validators directly
|
|
14
|
-
// if (this.config.validators) {
|
|
15
|
-
// this.config.validators.forEach(validator => {
|
|
16
|
-
// validator(value)
|
|
17
|
-
// })
|
|
18
|
-
// }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
12
|
toDB(value) {
|
|
22
13
|
return value;
|
|
23
14
|
}
|
|
@@ -112,12 +103,12 @@ const parseTime = value => {
|
|
|
112
103
|
if (dateFns.isDate(value)) return dateFns.format(value, 'HH:mm:ss.SSS');
|
|
113
104
|
|
|
114
105
|
if (typeof value !== 'string') {
|
|
115
|
-
throw new
|
|
106
|
+
throw new InvalidTimeError(`Expected a string, got a ${typeof value}`);
|
|
116
107
|
}
|
|
117
108
|
const result = value.match(timeRegex);
|
|
118
109
|
|
|
119
110
|
if (result === null) {
|
|
120
|
-
throw new
|
|
111
|
+
throw new InvalidTimeError('Invalid time format, expected HH:mm:ss.SSS');
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
const [, hours, minutes, seconds, fraction = '.000'] = result;
|
|
@@ -133,9 +124,9 @@ const parseDate = value => {
|
|
|
133
124
|
|
|
134
125
|
if (dateFns.isValid(date)) return dateFns.format(date, 'yyyy-MM-dd');
|
|
135
126
|
|
|
136
|
-
throw new
|
|
127
|
+
throw new InvalidDateError(`Invalid format, expected an ISO compatible date`);
|
|
137
128
|
} catch (error) {
|
|
138
|
-
throw new
|
|
129
|
+
throw new InvalidDateError(`Invalid format, expected an ISO compatible date`);
|
|
139
130
|
}
|
|
140
131
|
};
|
|
141
132
|
|
|
@@ -148,9 +139,9 @@ const parseDateTimeOrTimestamp = value => {
|
|
|
148
139
|
const milliUnixDate = dateFns.parse(value, 'T', new Date());
|
|
149
140
|
if (dateFns.isValid(milliUnixDate)) return milliUnixDate;
|
|
150
141
|
|
|
151
|
-
throw new
|
|
142
|
+
throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
|
|
152
143
|
} catch (error) {
|
|
153
|
-
throw new
|
|
144
|
+
throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
|
|
154
145
|
}
|
|
155
146
|
};
|
|
156
147
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { LifecycleProvider } from './lifecycles';
|
|
2
|
+
import { MigrationProvider } from './migrations';
|
|
3
|
+
import { SchemaProvideer } from './schema';
|
|
4
|
+
|
|
1
5
|
type BooleanWhere<T> = {
|
|
2
6
|
$and?: WhereParams<T>[];
|
|
3
7
|
$or?: WhereParams<T>[];
|
|
@@ -50,6 +54,43 @@ interface Pagination {
|
|
|
50
54
|
total: number;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
interface PopulateParams {}
|
|
58
|
+
interface EntityManager {
|
|
59
|
+
findOne<K extends keyof AllTypes>(uid: K, params: FindParams<AllTypes[K]>): Promise<any>;
|
|
60
|
+
findMany<K extends keyof AllTypes>(uid: K, params: FindParams<AllTypes[K]>): Promise<any[]>;
|
|
61
|
+
|
|
62
|
+
create<K extends keyof AllTypes>(uid: K, params: CreateParams<AllTypes[K]>): Promise<any>;
|
|
63
|
+
createMany<K extends keyof AllTypes>(
|
|
64
|
+
uid: K,
|
|
65
|
+
params: CreateManyParams<AllTypes[K]>
|
|
66
|
+
): Promise<{ count: number }>;
|
|
67
|
+
|
|
68
|
+
update<K extends keyof AllTypes>(uid: K, params: any): Promise<any>;
|
|
69
|
+
updateMany<K extends keyof AllTypes>(uid: K, params: any): Promise<{ count: number }>;
|
|
70
|
+
|
|
71
|
+
delete<K extends keyof AllTypes>(uid: K, params: any): Promise<any>;
|
|
72
|
+
deleteMany<K extends keyof AllTypes>(uid: K, params: any): Promise<{ count: number }>;
|
|
73
|
+
|
|
74
|
+
count<K extends keyof AllTypes>(uid: K, params: any): Promise<number>;
|
|
75
|
+
|
|
76
|
+
attachRelations<K extends keyof AllTypes>(uid: K, id: ID, data: any): Promise<any>;
|
|
77
|
+
updateRelations<K extends keyof AllTypes>(uid: K, id: ID, data: any): Promise<any>;
|
|
78
|
+
deleteRelations<K extends keyof AllTypes>(uid: K, id: ID): Promise<any>;
|
|
79
|
+
|
|
80
|
+
populate<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
81
|
+
uid: K,
|
|
82
|
+
entity: T,
|
|
83
|
+
populate: PopulateParams
|
|
84
|
+
): Promise<T>;
|
|
85
|
+
|
|
86
|
+
load<K extends keyof AllTypes, T extends AllTypes[K], SK extends keyof T>(
|
|
87
|
+
uid: K,
|
|
88
|
+
entity: T,
|
|
89
|
+
field: SK,
|
|
90
|
+
populate: PopulateParams
|
|
91
|
+
): Promise<T[SK]>;
|
|
92
|
+
}
|
|
93
|
+
|
|
53
94
|
interface QueryFromContentType<T extends keyof AllTypes> {
|
|
54
95
|
findOne(params: FindParams<AllTypes[T]>): Promise<any>;
|
|
55
96
|
findMany(params: FindParams<AllTypes[T]>): Promise<any[]>;
|
|
@@ -70,6 +111,14 @@ interface QueryFromContentType<T extends keyof AllTypes> {
|
|
|
70
111
|
attachRelations(id: ID, data: any): Promise<any>;
|
|
71
112
|
updateRelations(id: ID, data: any): Promise<any>;
|
|
72
113
|
deleteRelations(id: ID): Promise<any>;
|
|
114
|
+
|
|
115
|
+
populate<S extends AllTypes[T]>(entity: S, populate: PopulateParams): Promise<S>;
|
|
116
|
+
|
|
117
|
+
load<S extends AllTypes[T], K extends keyof S>(
|
|
118
|
+
entity: S,
|
|
119
|
+
field: K,
|
|
120
|
+
populate: PopulateParams
|
|
121
|
+
): Promise<S[K]>;
|
|
73
122
|
}
|
|
74
123
|
|
|
75
124
|
interface ModelConfig {
|
|
@@ -83,15 +132,11 @@ interface DatabaseConfig {
|
|
|
83
132
|
connection: ConnectionConfig;
|
|
84
133
|
models: ModelConfig[];
|
|
85
134
|
}
|
|
86
|
-
|
|
87
|
-
interface DatabaseSchema {
|
|
88
|
-
sync(): Promise<void>;
|
|
89
|
-
reset(): Promise<void>;
|
|
90
|
-
create(): Promise<void>;
|
|
91
|
-
drop(): Promise<void>;
|
|
92
|
-
}
|
|
93
135
|
export interface Database {
|
|
94
|
-
schema:
|
|
136
|
+
schema: SchemaProvideer;
|
|
137
|
+
lifecycles: LifecycleProvider;
|
|
138
|
+
migrations: MigrationProvider;
|
|
139
|
+
entityManager: EntityManager;
|
|
95
140
|
|
|
96
141
|
query<T extends keyof AllTypes>(uid: T): QueryFromContentType<T>;
|
|
97
142
|
}
|
package/lib/index.js
CHANGED
|
@@ -6,41 +6,48 @@ const { getDialect } = require('./dialects');
|
|
|
6
6
|
const createSchemaProvider = require('./schema');
|
|
7
7
|
const createMetadata = require('./metadata');
|
|
8
8
|
const { createEntityManager } = require('./entity-manager');
|
|
9
|
-
const {
|
|
9
|
+
const { createMigrationsProvider } = require('./migrations');
|
|
10
|
+
const { createLifecyclesProvider } = require('./lifecycles');
|
|
11
|
+
const errors = require('./errors');
|
|
10
12
|
|
|
11
13
|
// TODO: move back into strapi
|
|
12
14
|
const { transformContentTypes } = require('./utils/content-types');
|
|
13
15
|
|
|
16
|
+
const createConnection = config => {
|
|
17
|
+
const knexInstance = knex(config);
|
|
18
|
+
|
|
19
|
+
return Object.assign(knexInstance, {
|
|
20
|
+
getSchemaName() {
|
|
21
|
+
return this.client.connectionSettings.schema;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
14
26
|
class Database {
|
|
15
27
|
constructor(config) {
|
|
16
28
|
this.metadata = createMetadata(config.models);
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
this.config = {
|
|
31
|
+
connection: {},
|
|
32
|
+
settings: {
|
|
33
|
+
forceMigration: true,
|
|
34
|
+
},
|
|
35
|
+
...config,
|
|
36
|
+
};
|
|
20
37
|
|
|
21
|
-
this.config = config;
|
|
22
38
|
this.dialect = getDialect(this);
|
|
39
|
+
this.dialect.configure();
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
this.schema = createSchemaProvider(this);
|
|
41
|
+
this.connection = createConnection(this.config.connection);
|
|
26
42
|
|
|
27
|
-
this.
|
|
43
|
+
this.dialect.initialize();
|
|
28
44
|
|
|
29
|
-
this.
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async initialize() {
|
|
33
|
-
await this.dialect.initialize();
|
|
45
|
+
this.schema = createSchemaProvider(this);
|
|
34
46
|
|
|
35
|
-
this.
|
|
47
|
+
this.migrations = createMigrationsProvider(this);
|
|
48
|
+
this.lifecycles = createLifecyclesProvider(this);
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
this.lifecycles.subscribe(async event => {
|
|
39
|
-
const { model } = event;
|
|
40
|
-
if (event.action in model.lifecycles) {
|
|
41
|
-
await model.lifecycles[event.action](event);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
50
|
+
this.entityManager = createEntityManager(this);
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
query(uid) {
|
|
@@ -51,6 +58,21 @@ class Database {
|
|
|
51
58
|
return this.entityManager.getRepository(uid);
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
getConnection(tableName) {
|
|
62
|
+
const schema = this.connection.getSchemaName();
|
|
63
|
+
const connection = tableName ? this.connection(tableName) : this.connection;
|
|
64
|
+
return schema ? connection.withSchema(schema) : connection;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getSchemaConnection(trx = this.connection) {
|
|
68
|
+
const schema = this.connection.getSchemaName();
|
|
69
|
+
return schema ? trx.schema.withSchema(schema) : trx.schema;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
queryBuilder(uid) {
|
|
73
|
+
return this.entityManager.createQueryBuilder(uid);
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
async destroy() {
|
|
55
77
|
await this.lifecycles.clear();
|
|
56
78
|
await this.connection.destroy();
|
|
@@ -59,14 +81,9 @@ class Database {
|
|
|
59
81
|
|
|
60
82
|
// TODO: move into strapi
|
|
61
83
|
Database.transformContentTypes = transformContentTypes;
|
|
62
|
-
Database.init = async config =>
|
|
63
|
-
const db = new Database(config);
|
|
64
|
-
|
|
65
|
-
await db.initialize();
|
|
66
|
-
|
|
67
|
-
return db;
|
|
68
|
-
};
|
|
84
|
+
Database.init = async config => new Database(config);
|
|
69
85
|
|
|
70
86
|
module.exports = {
|
|
71
87
|
Database,
|
|
88
|
+
errors,
|
|
72
89
|
};
|