@strapi/database 4.0.0-beta.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/LICENSE +22 -0
- package/examples/connections.js +36 -0
- package/examples/data.sqlite +0 -0
- package/examples/docker-compose.yml +29 -0
- package/examples/index.js +73 -0
- package/examples/models.js +341 -0
- package/examples/typings.ts +17 -0
- package/lib/dialects/dialect.js +45 -0
- package/lib/dialects/index.js +28 -0
- package/lib/dialects/mysql/index.js +51 -0
- package/lib/dialects/mysql/schema-inspector.js +203 -0
- package/lib/dialects/postgresql/index.js +49 -0
- package/lib/dialects/postgresql/schema-inspector.js +229 -0
- package/lib/dialects/sqlite/index.js +74 -0
- package/lib/dialects/sqlite/schema-inspector.js +151 -0
- package/lib/entity-manager.js +886 -0
- package/lib/entity-repository.js +110 -0
- package/lib/errors.js +14 -0
- package/lib/fields.d.ts +9 -0
- package/lib/fields.js +232 -0
- package/lib/index.d.ts +146 -0
- package/lib/index.js +60 -0
- package/lib/lifecycles/index.d.ts +50 -0
- package/lib/lifecycles/index.js +66 -0
- 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 +219 -0
- package/lib/metadata/relations.js +488 -0
- package/lib/migrations/index.d.ts +9 -0
- package/lib/migrations/index.js +69 -0
- package/lib/migrations/storage.js +49 -0
- package/lib/query/helpers/index.js +10 -0
- package/lib/query/helpers/join.js +95 -0
- package/lib/query/helpers/order-by.js +70 -0
- package/lib/query/helpers/populate.js +652 -0
- package/lib/query/helpers/search.js +84 -0
- package/lib/query/helpers/transform.js +84 -0
- package/lib/query/helpers/where.js +322 -0
- package/lib/query/index.js +7 -0
- package/lib/query/query-builder.js +348 -0
- package/lib/schema/__tests__/schema-diff.test.js +181 -0
- package/lib/schema/builder.js +352 -0
- package/lib/schema/diff.js +376 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +95 -0
- package/lib/schema/schema.js +209 -0
- package/lib/schema/storage.js +75 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/index.js +34 -0
- package/lib/utils/content-types.js +41 -0
- package/package.json +39 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const withDefaultPagination = params => {
|
|
4
|
+
const { page = 1, pageSize = 10, ...rest } = params;
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
page: Number(page),
|
|
8
|
+
pageSize: Number(pageSize),
|
|
9
|
+
...rest,
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const createRepository = (uid, db) => {
|
|
14
|
+
return {
|
|
15
|
+
findOne(params) {
|
|
16
|
+
return db.entityManager.findOne(uid, params);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
findMany(params) {
|
|
20
|
+
return db.entityManager.findMany(uid, params);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
findWithCount(params) {
|
|
24
|
+
return Promise.all([
|
|
25
|
+
db.entityManager.findMany(uid, params),
|
|
26
|
+
db.entityManager.count(uid, params),
|
|
27
|
+
]);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async findPage(params) {
|
|
31
|
+
const { page, pageSize, ...rest } = withDefaultPagination(params);
|
|
32
|
+
|
|
33
|
+
const offset = Math.max(page - 1, 0) * pageSize;
|
|
34
|
+
const limit = pageSize;
|
|
35
|
+
|
|
36
|
+
const query = {
|
|
37
|
+
...rest,
|
|
38
|
+
limit,
|
|
39
|
+
offset,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const [results, total] = await Promise.all([
|
|
43
|
+
db.entityManager.findMany(uid, query),
|
|
44
|
+
db.entityManager.count(uid, query),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
results,
|
|
49
|
+
pagination: {
|
|
50
|
+
page,
|
|
51
|
+
pageSize,
|
|
52
|
+
pageCount: Math.ceil(total / pageSize),
|
|
53
|
+
total,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
create(params) {
|
|
59
|
+
return db.entityManager.create(uid, params);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
createMany(params) {
|
|
63
|
+
return db.entityManager.createMany(uid, params);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
update(params) {
|
|
67
|
+
return db.entityManager.update(uid, params);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
updateMany(params) {
|
|
71
|
+
return db.entityManager.updateMany(uid, params);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
delete(params) {
|
|
75
|
+
return db.entityManager.delete(uid, params);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
deleteMany(params) {
|
|
79
|
+
return db.entityManager.deleteMany(uid, params);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
count(params) {
|
|
83
|
+
return db.entityManager.count(uid, params);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
attachRelations(id, data) {
|
|
87
|
+
return db.entityManager.attachRelations(uid, id, data);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
updateRelations(id, data) {
|
|
91
|
+
return db.entityManager.updateRelations(uid, id, data);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
deleteRelations(id) {
|
|
95
|
+
return db.entityManager.deleteRelations(uid, id);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
populate(entity, populate) {
|
|
99
|
+
return db.entityManager.populate(uid, entity, populate);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
load(entity, field, params) {
|
|
103
|
+
return db.entityManager.load(uid, entity, field, params);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
createRepository,
|
|
110
|
+
};
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class NotNullConstraint extends Error {
|
|
4
|
+
constructor({ column = '' } = {}) {
|
|
5
|
+
super();
|
|
6
|
+
this.name = 'NotNullConstraint';
|
|
7
|
+
this.message = `Not null constraint violation${column ? `on on column ${column}` : ''}.`;
|
|
8
|
+
this.stack = '';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
NotNullConstraint,
|
|
14
|
+
};
|
package/lib/fields.d.ts
ADDED
package/lib/fields.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash/fp');
|
|
4
|
+
const dateFns = require('date-fns');
|
|
5
|
+
|
|
6
|
+
class Field {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
|
|
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
|
+
toDB(value) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fromDB(value) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class StringField extends Field {
|
|
31
|
+
toDB(value) {
|
|
32
|
+
return _.toString(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fromDB(value) {
|
|
36
|
+
return _.toString(value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class JSONField extends Field {
|
|
41
|
+
toDB(value) {
|
|
42
|
+
return JSON.stringify(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fromDB(value) {
|
|
46
|
+
if (typeof value === 'string') return JSON.parse(value);
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class BooleanField extends Field {
|
|
52
|
+
toDB(value) {
|
|
53
|
+
if (typeof value === 'boolean') return value;
|
|
54
|
+
|
|
55
|
+
if (['true', 't', '1', 1].includes(value)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (['false', 'f', '0', 0].includes(value)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Boolean(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fromDB(value) {
|
|
67
|
+
if (typeof value === 'boolean') {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const strVal = _.toString(value);
|
|
72
|
+
|
|
73
|
+
if (strVal === '1') {
|
|
74
|
+
return true;
|
|
75
|
+
} else if (strVal === '0') {
|
|
76
|
+
return false;
|
|
77
|
+
} else {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class NumberField extends Field {
|
|
84
|
+
toDB(value) {
|
|
85
|
+
const numberValue = _.toNumber(value);
|
|
86
|
+
|
|
87
|
+
if (Number.isNaN(numberValue)) {
|
|
88
|
+
throw new Error(`Expected a valid Number, got ${value}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return numberValue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fromDB(value) {
|
|
95
|
+
return _.toNumber(value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class BigIntegerField extends NumberField {
|
|
100
|
+
toDB(value) {
|
|
101
|
+
return _.toString(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fromDB(value) {
|
|
105
|
+
return _.toString(value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const timeRegex = new RegExp('^(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]{1,3})?$');
|
|
110
|
+
|
|
111
|
+
const parseTime = value => {
|
|
112
|
+
if (dateFns.isDate(value)) return dateFns.format(value, 'HH:mm:ss.SSS');
|
|
113
|
+
|
|
114
|
+
if (typeof value !== 'string') {
|
|
115
|
+
throw new Error(`Expected a string, got a ${typeof value}`);
|
|
116
|
+
}
|
|
117
|
+
const result = value.match(timeRegex);
|
|
118
|
+
|
|
119
|
+
if (result === null) {
|
|
120
|
+
throw new Error('Invalid time format, expected HH:mm:ss.SSS');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const [, hours, minutes, seconds, fraction = '.000'] = result;
|
|
124
|
+
const fractionPart = _.padCharsEnd('0', 3, fraction.slice(1));
|
|
125
|
+
|
|
126
|
+
return `${hours}:${minutes}:${seconds}.${fractionPart}`;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const parseDate = value => {
|
|
130
|
+
if (dateFns.isDate(value)) return dateFns.format(value, 'yyyy-MM-dd');
|
|
131
|
+
try {
|
|
132
|
+
let date = dateFns.parseISO(value);
|
|
133
|
+
|
|
134
|
+
if (dateFns.isValid(date)) return dateFns.format(date, 'yyyy-MM-dd');
|
|
135
|
+
|
|
136
|
+
throw new Error(`Invalid format, expected an ISO compatible date`);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error(`Invalid format, expected an ISO compatible date`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const parseDateTimeOrTimestamp = value => {
|
|
143
|
+
if (dateFns.isDate(value)) return value;
|
|
144
|
+
try {
|
|
145
|
+
const date = dateFns.parseISO(value);
|
|
146
|
+
if (dateFns.isValid(date)) return date;
|
|
147
|
+
|
|
148
|
+
const milliUnixDate = dateFns.parse(value, 'T', new Date());
|
|
149
|
+
if (dateFns.isValid(milliUnixDate)) return milliUnixDate;
|
|
150
|
+
|
|
151
|
+
throw new Error(`Invalid format, expected a timestamp or an ISO date`);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error(`Invalid format, expected a timestamp or an ISO date`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
class DateField extends Field {
|
|
158
|
+
toDB(value) {
|
|
159
|
+
return parseDate(value);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fromDB(value) {
|
|
163
|
+
const cast = new Date(value);
|
|
164
|
+
return dateFns.isValid(cast) ? dateFns.formatISO(cast, { representation: 'date' }) : null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
class DatetimeField extends Field {
|
|
168
|
+
toDB(value) {
|
|
169
|
+
return parseDateTimeOrTimestamp(value);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fromDB(value) {
|
|
173
|
+
const cast = new Date(value);
|
|
174
|
+
return dateFns.isValid(cast) ? cast.toISOString() : null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
class TimeField extends Field {
|
|
179
|
+
toDB(value) {
|
|
180
|
+
return parseTime(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fromDB(value) {
|
|
184
|
+
// make sure that's a string with valid format ?
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
class TimestampField extends Field {
|
|
189
|
+
toDB(value) {
|
|
190
|
+
return parseDateTimeOrTimestamp(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fromDB(value) {
|
|
194
|
+
const cast = new Date(value);
|
|
195
|
+
return dateFns.isValid(cast) ? dateFns.format(cast, 'T') : null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const typeToFieldMap = {
|
|
200
|
+
increments: Field,
|
|
201
|
+
password: StringField,
|
|
202
|
+
email: StringField,
|
|
203
|
+
string: StringField,
|
|
204
|
+
uid: StringField,
|
|
205
|
+
richtext: StringField,
|
|
206
|
+
text: StringField,
|
|
207
|
+
enumeration: StringField,
|
|
208
|
+
json: JSONField,
|
|
209
|
+
biginteger: BigIntegerField,
|
|
210
|
+
integer: NumberField,
|
|
211
|
+
float: NumberField,
|
|
212
|
+
decimal: NumberField,
|
|
213
|
+
date: DateField,
|
|
214
|
+
time: TimeField,
|
|
215
|
+
datetime: DatetimeField,
|
|
216
|
+
timestamp: TimestampField,
|
|
217
|
+
boolean: BooleanField,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const createField = attribute => {
|
|
221
|
+
const { type } = attribute;
|
|
222
|
+
|
|
223
|
+
if (_.has(type, typeToFieldMap)) {
|
|
224
|
+
return new typeToFieldMap[type]({});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
throw new Error(`Undefined field for type ${type}`);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
createField,
|
|
232
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { LifecycleProvider } from './lifecycles';
|
|
2
|
+
import { MigrationProvider } from './migrations';
|
|
3
|
+
import { SchemaProvideer } from './schema';
|
|
4
|
+
|
|
5
|
+
type BooleanWhere<T> = {
|
|
6
|
+
$and?: WhereParams<T>[];
|
|
7
|
+
$or?: WhereParams<T>[];
|
|
8
|
+
$not?: WhereParams<T>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type WhereParams<T> = {
|
|
12
|
+
[K in keyof T]?: T[K];
|
|
13
|
+
} &
|
|
14
|
+
BooleanWhere<T>;
|
|
15
|
+
|
|
16
|
+
type Sortables<T> = {
|
|
17
|
+
// check sortable
|
|
18
|
+
[P in keyof T]: P;
|
|
19
|
+
}[keyof T];
|
|
20
|
+
|
|
21
|
+
type Direction = 'asc' | 'ASC' | 'DESC' | 'desc';
|
|
22
|
+
|
|
23
|
+
interface FindParams<T> {
|
|
24
|
+
select?: (keyof T)[];
|
|
25
|
+
// TODO: add nested operators & relations
|
|
26
|
+
where?: WhereParams<T>;
|
|
27
|
+
limit?: number;
|
|
28
|
+
offset?: number;
|
|
29
|
+
orderBy?: // TODO: add relations
|
|
30
|
+
| Sortables<T>
|
|
31
|
+
| Sortables<T>[]
|
|
32
|
+
| { [K in Sortables<T>]?: Direction }
|
|
33
|
+
| { [K in Sortables<T>]?: Direction }[];
|
|
34
|
+
// TODO: define nested obj
|
|
35
|
+
populate?: (keyof T)[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface CreateParams<T> {
|
|
39
|
+
select?: (keyof T)[];
|
|
40
|
+
populate?: (keyof T)[];
|
|
41
|
+
data: T[keyof T];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface CreateManyParams<T> {
|
|
45
|
+
select?: (keyof T)[];
|
|
46
|
+
populate?: (keyof T)[];
|
|
47
|
+
data: T[keyof T][];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Pagination {
|
|
51
|
+
page: number;
|
|
52
|
+
pageSize: number;
|
|
53
|
+
pageCount: number;
|
|
54
|
+
total: number;
|
|
55
|
+
}
|
|
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
|
+
|
|
94
|
+
interface QueryFromContentType<T extends keyof AllTypes> {
|
|
95
|
+
findOne(params: FindParams<AllTypes[T]>): Promise<any>;
|
|
96
|
+
findMany(params: FindParams<AllTypes[T]>): Promise<any[]>;
|
|
97
|
+
findWithCount(params: FindParams<AllTypes[T]>): Promise<[any[], number]>;
|
|
98
|
+
findPage(params: FindParams<AllTypes[T]>): Promise<{ results: any[]; pagination: Pagination }>;
|
|
99
|
+
|
|
100
|
+
create(params: CreateParams<AllTypes[T]>): Promise<any>;
|
|
101
|
+
createMany(params: CreateManyParams<AllTypes[T]>): Promise<{ count: number }>;
|
|
102
|
+
|
|
103
|
+
update(params: any): Promise<any>;
|
|
104
|
+
updateMany(params: any): Promise<{ count: number }>;
|
|
105
|
+
|
|
106
|
+
delete(params: any): Promise<any>;
|
|
107
|
+
deleteMany(params: any): Promise<{ count: number }>;
|
|
108
|
+
|
|
109
|
+
count(params: any): Promise<number>;
|
|
110
|
+
|
|
111
|
+
attachRelations(id: ID, data: any): Promise<any>;
|
|
112
|
+
updateRelations(id: ID, data: any): Promise<any>;
|
|
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]>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface ModelConfig {
|
|
125
|
+
tableName: string;
|
|
126
|
+
[k: string]: any;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface ConnectionConfig {}
|
|
130
|
+
|
|
131
|
+
interface DatabaseConfig {
|
|
132
|
+
connection: ConnectionConfig;
|
|
133
|
+
models: ModelConfig[];
|
|
134
|
+
}
|
|
135
|
+
export interface Database {
|
|
136
|
+
schema: SchemaProvideer;
|
|
137
|
+
lifecycles: LifecycleProvider;
|
|
138
|
+
migrations: MigrationProvider;
|
|
139
|
+
entityManager: EntityManager;
|
|
140
|
+
|
|
141
|
+
query<T extends keyof AllTypes>(uid: T): QueryFromContentType<T>;
|
|
142
|
+
}
|
|
143
|
+
export class Database implements Database {
|
|
144
|
+
static transformContentTypes(contentTypes: any[]): ModelConfig[];
|
|
145
|
+
static init(config: DatabaseConfig): Promise<Database>;
|
|
146
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const knex = require('knex');
|
|
4
|
+
|
|
5
|
+
const { getDialect } = require('./dialects');
|
|
6
|
+
const createSchemaProvider = require('./schema');
|
|
7
|
+
const createMetadata = require('./metadata');
|
|
8
|
+
const { createEntityManager } = require('./entity-manager');
|
|
9
|
+
const { createMigrationsProvider } = require('./migrations');
|
|
10
|
+
const { createLifecyclesProvider } = require('./lifecycles');
|
|
11
|
+
|
|
12
|
+
// TODO: move back into strapi
|
|
13
|
+
const { transformContentTypes } = require('./utils/content-types');
|
|
14
|
+
|
|
15
|
+
class Database {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.metadata = createMetadata(config.models);
|
|
18
|
+
|
|
19
|
+
this.config = config;
|
|
20
|
+
|
|
21
|
+
this.dialect = getDialect(this);
|
|
22
|
+
this.dialect.configure();
|
|
23
|
+
|
|
24
|
+
this.connection = knex(this.config.connection);
|
|
25
|
+
|
|
26
|
+
this.dialect.initialize();
|
|
27
|
+
|
|
28
|
+
this.schema = createSchemaProvider(this);
|
|
29
|
+
|
|
30
|
+
this.migrations = createMigrationsProvider(this);
|
|
31
|
+
this.lifecycles = createLifecyclesProvider(this);
|
|
32
|
+
|
|
33
|
+
this.entityManager = createEntityManager(this);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
query(uid) {
|
|
37
|
+
if (!this.metadata.has(uid)) {
|
|
38
|
+
throw new Error(`Model ${uid} not found`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return this.entityManager.getRepository(uid);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
queryBuilder(uid) {
|
|
45
|
+
return this.entityManager.createQueryBuilder(uid);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async destroy() {
|
|
49
|
+
await this.lifecycles.clear();
|
|
50
|
+
await this.connection.destroy();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// TODO: move into strapi
|
|
55
|
+
Database.transformContentTypes = transformContentTypes;
|
|
56
|
+
Database.init = async config => new Database(config);
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
Database,
|
|
60
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Database } from '../';
|
|
2
|
+
import { Model } from '../schema';
|
|
3
|
+
import { Subscriber } from './subscribers';
|
|
4
|
+
|
|
5
|
+
export type Action =
|
|
6
|
+
| 'beforeCreate'
|
|
7
|
+
| 'afterCreate'
|
|
8
|
+
| 'beforeFindOne'
|
|
9
|
+
| 'afterFindOne'
|
|
10
|
+
| 'beforeFindMany'
|
|
11
|
+
| 'afterFindMany'
|
|
12
|
+
| 'beforeCount'
|
|
13
|
+
| 'afterCount'
|
|
14
|
+
| 'beforeCreateMany'
|
|
15
|
+
| 'afterCreateMany'
|
|
16
|
+
| 'beforeUpdate'
|
|
17
|
+
| 'afterUpdate'
|
|
18
|
+
| 'beforeUpdateMany'
|
|
19
|
+
| 'afterUpdateMany'
|
|
20
|
+
| 'beforeDelete'
|
|
21
|
+
| 'afterDelete'
|
|
22
|
+
| 'beforeDeleteMany'
|
|
23
|
+
| 'afterDeleteMany';
|
|
24
|
+
|
|
25
|
+
export interface Params {
|
|
26
|
+
select?: any;
|
|
27
|
+
where?: any;
|
|
28
|
+
_q?: any;
|
|
29
|
+
orderBy?: any;
|
|
30
|
+
groupBy?: any;
|
|
31
|
+
offset?: any;
|
|
32
|
+
limit?: any;
|
|
33
|
+
populate?: any;
|
|
34
|
+
data?: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Event {
|
|
38
|
+
action: Action;
|
|
39
|
+
model: Model;
|
|
40
|
+
params: Params;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface LifecycleProvider {
|
|
44
|
+
subscribe(subscriber: Subscriber): () => void;
|
|
45
|
+
clear(): void;
|
|
46
|
+
run(action: Action, uid: string, properties: any): Promise<void>;
|
|
47
|
+
createEvent(action: Action, uid: string, properties: any): Event;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createLifecyclesProvider(db: Database): LifecycleProvider;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert').strict;
|
|
4
|
+
|
|
5
|
+
const timestampsLifecyclesSubscriber = require('./subscribers/timestamps');
|
|
6
|
+
const modelLifecyclesSubscriber = require('./subscribers/models-lifecycles');
|
|
7
|
+
|
|
8
|
+
const isValidSubscriber = subscriber => {
|
|
9
|
+
return (
|
|
10
|
+
typeof subscriber === 'function' || (typeof subscriber === 'object' && subscriber !== null)
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @type {import('.').createLifecyclesProvider}
|
|
16
|
+
*/
|
|
17
|
+
const createLifecyclesProvider = db => {
|
|
18
|
+
let subscribers = [timestampsLifecyclesSubscriber, modelLifecyclesSubscriber];
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
subscribe(subscriber) {
|
|
22
|
+
assert(isValidSubscriber(subscriber), 'Invalid subscriber. Expected function or object');
|
|
23
|
+
|
|
24
|
+
subscribers.push(subscriber);
|
|
25
|
+
|
|
26
|
+
return () => subscribers.splice(subscribers.indexOf(subscriber), 1);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
clear() {
|
|
30
|
+
subscribers = [];
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
createEvent(action, uid, properties) {
|
|
34
|
+
const model = db.metadata.get(uid);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
action,
|
|
38
|
+
model,
|
|
39
|
+
...properties,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async run(action, uid, properties) {
|
|
44
|
+
for (const subscriber of subscribers) {
|
|
45
|
+
if (typeof subscriber === 'function') {
|
|
46
|
+
const event = this.createEvent(action, uid, properties);
|
|
47
|
+
await subscriber(event);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const hasAction = action in subscriber;
|
|
52
|
+
const hasModel = !subscriber.models || subscriber.models.includes(uid);
|
|
53
|
+
|
|
54
|
+
if (hasAction && hasModel) {
|
|
55
|
+
const event = this.createEvent(action, uid, properties);
|
|
56
|
+
|
|
57
|
+
await subscriber[action](event);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
createLifecyclesProvider,
|
|
66
|
+
};
|