@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.
Files changed (52) hide show
  1. package/LICENSE +22 -0
  2. package/examples/connections.js +36 -0
  3. package/examples/data.sqlite +0 -0
  4. package/examples/docker-compose.yml +29 -0
  5. package/examples/index.js +73 -0
  6. package/examples/models.js +341 -0
  7. package/examples/typings.ts +17 -0
  8. package/lib/dialects/dialect.js +45 -0
  9. package/lib/dialects/index.js +28 -0
  10. package/lib/dialects/mysql/index.js +51 -0
  11. package/lib/dialects/mysql/schema-inspector.js +203 -0
  12. package/lib/dialects/postgresql/index.js +49 -0
  13. package/lib/dialects/postgresql/schema-inspector.js +229 -0
  14. package/lib/dialects/sqlite/index.js +74 -0
  15. package/lib/dialects/sqlite/schema-inspector.js +151 -0
  16. package/lib/entity-manager.js +886 -0
  17. package/lib/entity-repository.js +110 -0
  18. package/lib/errors.js +14 -0
  19. package/lib/fields.d.ts +9 -0
  20. package/lib/fields.js +232 -0
  21. package/lib/index.d.ts +146 -0
  22. package/lib/index.js +60 -0
  23. package/lib/lifecycles/index.d.ts +50 -0
  24. package/lib/lifecycles/index.js +66 -0
  25. package/lib/lifecycles/subscribers/index.d.ts +9 -0
  26. package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
  27. package/lib/lifecycles/subscribers/timestamps.js +65 -0
  28. package/lib/metadata/index.js +219 -0
  29. package/lib/metadata/relations.js +488 -0
  30. package/lib/migrations/index.d.ts +9 -0
  31. package/lib/migrations/index.js +69 -0
  32. package/lib/migrations/storage.js +49 -0
  33. package/lib/query/helpers/index.js +10 -0
  34. package/lib/query/helpers/join.js +95 -0
  35. package/lib/query/helpers/order-by.js +70 -0
  36. package/lib/query/helpers/populate.js +652 -0
  37. package/lib/query/helpers/search.js +84 -0
  38. package/lib/query/helpers/transform.js +84 -0
  39. package/lib/query/helpers/where.js +322 -0
  40. package/lib/query/index.js +7 -0
  41. package/lib/query/query-builder.js +348 -0
  42. package/lib/schema/__tests__/schema-diff.test.js +181 -0
  43. package/lib/schema/builder.js +352 -0
  44. package/lib/schema/diff.js +376 -0
  45. package/lib/schema/index.d.ts +49 -0
  46. package/lib/schema/index.js +95 -0
  47. package/lib/schema/schema.js +209 -0
  48. package/lib/schema/storage.js +75 -0
  49. package/lib/types/index.d.ts +6 -0
  50. package/lib/types/index.js +34 -0
  51. package/lib/utils/content-types.js +41 -0
  52. package/package.json +39 -0
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ const types = require('../types');
4
+
5
+ const createColumn = (name, attribute) => {
6
+ const { type, args = [], ...opts } = getColumnType(attribute);
7
+
8
+ return {
9
+ name,
10
+ type,
11
+ args,
12
+ defaultTo: null,
13
+ notNullable: false,
14
+ unsigned: false,
15
+ ...opts,
16
+ ...(attribute.column || {}),
17
+ };
18
+ };
19
+
20
+ const createTable = meta => {
21
+ const table = {
22
+ name: meta.tableName,
23
+ indexes: meta.indexes || [],
24
+ foreignKeys: meta.foreignKeys || [],
25
+ columns: [],
26
+ };
27
+
28
+ for (const key in meta.attributes) {
29
+ const attribute = meta.attributes[key];
30
+
31
+ if (types.isRelation(attribute.type)) {
32
+ if (attribute.morphColumn && attribute.owner) {
33
+ const { idColumn, typeColumn } = attribute.morphColumn;
34
+
35
+ table.columns.push(
36
+ createColumn(idColumn.name, {
37
+ type: 'integer',
38
+ column: {
39
+ unsigned: true,
40
+ },
41
+ })
42
+ );
43
+
44
+ table.columns.push(createColumn(typeColumn.name, { type: 'string' }));
45
+ } else if (attribute.joinColumn && attribute.owner) {
46
+ // NOTE: we could pass uniquness for oneToOne to avoid creating more than one to one
47
+
48
+ const { name: columnName, referencedColumn, referencedTable } = attribute.joinColumn;
49
+
50
+ const column = createColumn(columnName, {
51
+ type: 'integer',
52
+ column: {
53
+ unsigned: true,
54
+ },
55
+ });
56
+
57
+ table.columns.push(column);
58
+
59
+ table.foreignKeys.push({
60
+ name: `${table.name}_${columnName}_fk`,
61
+ columns: [columnName],
62
+ referencedTable,
63
+ referencedColumns: [referencedColumn],
64
+ // NOTE: could allow configuration
65
+ onDelete: 'SET NULL',
66
+ });
67
+
68
+ table.indexes.push({
69
+ name: `${table.name}_${columnName}_fk`,
70
+ columns: [columnName],
71
+ });
72
+ }
73
+ } else if (types.isScalar(attribute.type)) {
74
+ const column = createColumn(attribute.columnName || key, attribute);
75
+
76
+ if (column.unique) {
77
+ table.indexes.push({
78
+ type: 'unique',
79
+ name: `${table.name}_${column.name}_unique`,
80
+ columns: [column.name],
81
+ });
82
+ }
83
+
84
+ if (column.primary) {
85
+ table.indexes.push({
86
+ type: 'primary',
87
+ name: `${table.name}_${column.name}_primary`,
88
+ columns: [column.name],
89
+ });
90
+ }
91
+
92
+ table.columns.push(column);
93
+ }
94
+ }
95
+
96
+ return table;
97
+ };
98
+
99
+ const getColumnType = attribute => {
100
+ if (attribute.columnType) {
101
+ return attribute.columnType;
102
+ }
103
+
104
+ switch (attribute.type) {
105
+ case 'increments': {
106
+ return {
107
+ type: 'increments',
108
+ args: [{ primary: true }],
109
+ notNullable: true,
110
+ };
111
+ }
112
+
113
+ // We might want to convert email/password to string types before going into the orm with specific validators & transformers
114
+ case 'password':
115
+ case 'email':
116
+ case 'string': {
117
+ return { type: 'string' };
118
+ }
119
+ case 'uid': {
120
+ return {
121
+ type: 'string',
122
+ unique: true,
123
+ };
124
+ }
125
+ case 'richtext':
126
+ case 'text': {
127
+ return {
128
+ type: 'text',
129
+ args: ['longtext'],
130
+ };
131
+ }
132
+ case 'json': {
133
+ return { type: 'jsonb' };
134
+ }
135
+ case 'enumeration': {
136
+ return {
137
+ type: 'enum',
138
+ args: [
139
+ attribute.enum,
140
+ /*,{ useNative: true, existingType: true, enumName: 'foo_type', schemaName: 'public' }*/
141
+ ],
142
+ };
143
+ }
144
+ case 'integer': {
145
+ return { type: 'integer' };
146
+ }
147
+ case 'biginteger': {
148
+ return { type: 'bigInteger' };
149
+ }
150
+ case 'float': {
151
+ return { type: 'double' };
152
+ }
153
+ case 'decimal': {
154
+ return { type: 'decimal', args: [10, 2] };
155
+ }
156
+ case 'date': {
157
+ return { type: 'date' };
158
+ }
159
+ case 'time': {
160
+ return { type: 'time', args: [{ precision: 3 }] };
161
+ }
162
+ case 'datetime': {
163
+ return {
164
+ type: 'datetime',
165
+ args: [
166
+ {
167
+ useTz: false,
168
+ precision: 6,
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ case 'timestamp': {
174
+ return {
175
+ type: 'timestamp',
176
+ args: [
177
+ {
178
+ useTz: false,
179
+ precision: 6,
180
+ },
181
+ ],
182
+ };
183
+ }
184
+ case 'boolean': {
185
+ return { type: 'boolean' };
186
+ }
187
+ default: {
188
+ throw new Error(`Unknow type ${attribute.type}`);
189
+ }
190
+ }
191
+ };
192
+
193
+ const metadataToSchema = metadata => {
194
+ const schema = {
195
+ tables: [],
196
+ addTable(table) {
197
+ this.tables.push(table);
198
+ return this;
199
+ },
200
+ };
201
+
202
+ metadata.forEach(metadata => {
203
+ schema.addTable(createTable(metadata));
204
+ });
205
+
206
+ return schema;
207
+ };
208
+
209
+ module.exports = { metadataToSchema, createTable };
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ const TABLE_NAME = 'strapi_database_schema';
6
+
7
+ module.exports = db => {
8
+ const hasSchemaTable = () => db.connection.schema.hasTable(TABLE_NAME);
9
+
10
+ const createSchemaTable = () => {
11
+ return db.connection.schema.createTable(TABLE_NAME, t => {
12
+ t.increments('id');
13
+ t.json('schema');
14
+ t.datetime('time', { useTz: false });
15
+ t.string('hash');
16
+ });
17
+ };
18
+
19
+ const checkTableExists = async () => {
20
+ if (!(await hasSchemaTable())) {
21
+ await createSchemaTable();
22
+ }
23
+ };
24
+
25
+ return {
26
+ async read() {
27
+ await checkTableExists();
28
+
29
+ const res = await db.connection
30
+ .select('*')
31
+ .from(TABLE_NAME)
32
+ .orderBy('time', 'DESC')
33
+ .first();
34
+
35
+ if (!res) {
36
+ return null;
37
+ }
38
+
39
+ const parsedSchema = typeof res.schema === 'object' ? res.schema : JSON.parse(res.schema);
40
+
41
+ return {
42
+ ...res,
43
+ schema: parsedSchema,
44
+ };
45
+ },
46
+
47
+ hashSchema(schema) {
48
+ return crypto
49
+ .createHash('md5')
50
+ .update(JSON.stringify(schema))
51
+ .digest('hex');
52
+ },
53
+
54
+ async add(schema) {
55
+ await checkTableExists();
56
+
57
+ // NOTE: we can remove this to add history
58
+ await db.connection(TABLE_NAME).delete();
59
+
60
+ const time = new Date();
61
+
62
+ await db.connection(TABLE_NAME).insert({
63
+ schema: JSON.stringify(schema),
64
+ hash: this.hashSchema(schema),
65
+ time,
66
+ });
67
+ },
68
+
69
+ async clear() {
70
+ await checkTableExists();
71
+
72
+ await db.connection(TABLE_NAME).truncate();
73
+ },
74
+ };
75
+ };
@@ -0,0 +1,6 @@
1
+ export function isScalar(type: string): boolean
2
+ export function isNumber(type: string): boolean
3
+ export function isString(type: string): boolean
4
+ export function isComponent(type: string): boolean
5
+ export function isDynamicZone(type: string): boolean
6
+ export function isRelation(type: string): boolean
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const SCALAR_TYPES = [
4
+ 'increments',
5
+ 'password',
6
+ 'email',
7
+ 'string',
8
+ 'uid',
9
+ 'richtext',
10
+ 'text',
11
+ 'json',
12
+ 'enumeration',
13
+ 'integer',
14
+ 'biginteger',
15
+ 'float',
16
+ 'decimal',
17
+ 'date',
18
+ 'time',
19
+ 'datetime',
20
+ 'timestamp',
21
+ 'boolean',
22
+ ];
23
+
24
+ const STRING_TYPES = ['string', 'text', 'uid', 'email', 'enumeration', 'richtext'];
25
+ const NUMBER_TYPES = ['biginteger', 'integer', 'decimal', 'float'];
26
+
27
+ module.exports = {
28
+ isString: type => STRING_TYPES.includes(type),
29
+ isNumber: type => NUMBER_TYPES.includes(type),
30
+ isScalar: type => SCALAR_TYPES.includes(type),
31
+ isComponent: type => type === 'component',
32
+ isDynamicZone: type => type === 'dynamiczone',
33
+ isRelation: type => type === 'relation',
34
+ };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const transformAttribute = attribute => {
4
+ switch (attribute.type) {
5
+ case 'media': {
6
+ // TODO: handle a filter on field
7
+ return {
8
+ type: 'relation',
9
+ relation: attribute.multiple === true ? 'morphMany' : 'morphOne',
10
+ target: 'plugin::upload.file',
11
+ morphBy: 'related',
12
+ };
13
+ }
14
+ default: {
15
+ return attribute;
16
+ }
17
+ }
18
+ };
19
+
20
+ // TODO: model logic outside DB
21
+ const transformContentTypes = contentTypes => {
22
+ return contentTypes.map(contentType => {
23
+ const model = {
24
+ ...contentType,
25
+ // reuse new model def
26
+ singularName: contentType.modelName,
27
+ tableName: contentType.collectionName,
28
+ attributes: {
29
+ ...Object.keys(contentType.attributes || {}).reduce((attrs, attrName) => {
30
+ return Object.assign(attrs, {
31
+ [attrName]: transformAttribute(contentType.attributes[attrName]),
32
+ });
33
+ }, {}),
34
+ },
35
+ };
36
+
37
+ return model;
38
+ });
39
+ };
40
+
41
+ module.exports = { transformContentTypes };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@strapi/database",
3
+ "version": "4.0.0-beta.0",
4
+ "description": "Strapi's database layer",
5
+ "homepage": "https://strapi.io",
6
+ "main": "./lib/index.js",
7
+ "scripts": {
8
+ "test": "echo \"no tests yet\""
9
+ },
10
+ "directories": {
11
+ "lib": "./lib"
12
+ },
13
+ "author": {
14
+ "name": "Strapi team",
15
+ "email": "hi@strapi.io",
16
+ "url": "https://strapi.io"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git://github.com/strapi/strapi.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/strapi/strapi/issues"
24
+ },
25
+ "engines": {
26
+ "node": ">=12.x.x <=16.x.x",
27
+ "npm": ">=6.0.0"
28
+ },
29
+ "license": "SEE LICENSE IN LICENSE",
30
+ "dependencies": {
31
+ "date-fns": "2.22.1",
32
+ "debug": "4.3.1",
33
+ "fs-extra": "10.0.0",
34
+ "knex": "0.95.6",
35
+ "lodash": "4.17.21",
36
+ "umzug": "2.3.0"
37
+ },
38
+ "gitHead": "9807c78cb7ab6373b7abb46da5ecc4980a9ea56c"
39
+ }