@strapi/database 4.0.0-next.8 → 4.0.2

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 (46) hide show
  1. package/jest.config.js +10 -0
  2. package/lib/dialects/dialect.js +45 -0
  3. package/lib/dialects/index.js +6 -112
  4. package/lib/dialects/mysql/index.js +51 -0
  5. package/lib/dialects/mysql/schema-inspector.js +199 -0
  6. package/lib/dialects/postgresql/index.js +49 -0
  7. package/lib/dialects/postgresql/schema-inspector.js +232 -0
  8. package/lib/dialects/sqlite/index.js +73 -0
  9. package/lib/dialects/sqlite/schema-inspector.js +151 -0
  10. package/lib/entity-manager.js +18 -14
  11. package/lib/entity-repository.js +2 -3
  12. package/lib/errors.js +44 -2
  13. package/lib/fields.d.ts +2 -3
  14. package/lib/fields.js +7 -16
  15. package/lib/index.d.ts +67 -22
  16. package/lib/index.js +44 -27
  17. package/lib/lifecycles/index.d.ts +50 -0
  18. package/lib/{lifecycles.js → lifecycles/index.js} +25 -14
  19. package/lib/lifecycles/subscribers/index.d.ts +9 -0
  20. package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
  21. package/lib/lifecycles/subscribers/timestamps.js +65 -0
  22. package/lib/metadata/index.js +84 -95
  23. package/lib/metadata/relations.js +16 -0
  24. package/lib/migrations/index.d.ts +9 -0
  25. package/lib/migrations/index.js +69 -0
  26. package/lib/migrations/storage.js +51 -0
  27. package/lib/query/helpers/join.js +3 -5
  28. package/lib/query/helpers/order-by.js +21 -11
  29. package/lib/query/helpers/populate.js +35 -10
  30. package/lib/query/helpers/search.js +26 -12
  31. package/lib/query/helpers/transform.js +42 -14
  32. package/lib/query/helpers/where.js +92 -57
  33. package/lib/query/query-builder.js +116 -34
  34. package/lib/schema/__tests__/schema-diff.test.js +14 -1
  35. package/lib/schema/builder.js +315 -284
  36. package/lib/schema/diff.js +374 -0
  37. package/lib/schema/index.d.ts +49 -0
  38. package/lib/schema/index.js +47 -50
  39. package/lib/schema/schema.js +21 -26
  40. package/lib/schema/storage.js +79 -0
  41. package/lib/utils/content-types.js +0 -1
  42. package/package.json +26 -21
  43. package/examples/data.sqlite +0 -0
  44. package/lib/configuration.js +0 -49
  45. package/lib/schema/schema-diff.js +0 -337
  46. package/lib/schema/schema-storage.js +0 -44
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 Error(`Expected a string, got a ${typeof value}`);
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 Error('Invalid time format, expected HH:mm:ss.SSS');
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 Error(`Invalid format, expected an ISO compatible date`);
127
+ throw new InvalidDateError(`Invalid format, expected an ISO compatible date`);
137
128
  } catch (error) {
138
- throw new Error(`Invalid format, expected an ISO compatible date`);
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 Error(`Invalid format, expected a timestamp or an ISO date`);
142
+ throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
152
143
  } catch (error) {
153
- throw new Error(`Invalid format, expected a timestamp or an ISO date`);
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,26 +54,71 @@ 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
- findOne(params: FindParams<AllTypes[T]>): any;
55
- findMany(params: FindParams<AllTypes[T]>): any[];
56
- findWithCount(params: FindParams<AllTypes[T]>): [any[], number];
57
- findPage(params: FindParams<AllTypes[T]>): { results: any[]; pagination: Pagination };
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 }>;
58
102
 
59
- create(params: CreateParams<AllTypes[T]>): any;
60
- createMany(params: CreateManyParams<AllTypes[T]>): { count: number };
103
+ update(params: any): Promise<any>;
104
+ updateMany(params: any): Promise<{ count: number }>;
61
105
 
62
- update(params: any): any;
63
- updateMany(params: any): { count: number };
106
+ delete(params: any): Promise<any>;
107
+ deleteMany(params: any): Promise<{ count: number }>;
64
108
 
65
- delete(params: any): any;
66
- deleteMany(params: any): { count: number };
109
+ count(params: any): Promise<number>;
67
110
 
68
- count(params: any): number;
111
+ attachRelations(id: ID, data: any): Promise<any>;
112
+ updateRelations(id: ID, data: any): Promise<any>;
113
+ deleteRelations(id: ID): Promise<any>;
69
114
 
70
- attachRelations(id: ID, data: any): any;
71
- updateRelations(id: ID, data: any): any;
72
- deleteRelations(id: ID): any;
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: DatabaseSchema;
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 { createLifecyclesManager } = require('./lifecycles');
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
- // TODO: validate meta
19
- // this.metadata.validate();
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
- // TODO: migrations -> allow running them through cli before startup
25
- this.schema = createSchemaProvider(this);
41
+ this.connection = createConnection(this.config.connection);
26
42
 
27
- this.lifecycles = createLifecyclesManager(this);
43
+ this.dialect.initialize();
28
44
 
29
- this.entityManager = createEntityManager(this);
30
- }
31
-
32
- async initialize() {
33
- await this.dialect.initialize();
45
+ this.schema = createSchemaProvider(this);
34
46
 
35
- this.connection = knex(this.config.connection);
47
+ this.migrations = createMigrationsProvider(this);
48
+ this.lifecycles = createLifecyclesProvider(this);
36
49
 
37
- // register module lifeycles subscriber
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
  };
@@ -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;
@@ -1,16 +1,33 @@
1
1
  'use strict';
2
2
 
3
- const createLifecyclesManager = db => {
4
- let subscribers = [];
3
+ const assert = require('assert').strict;
5
4
 
6
- const lifecycleManager = {
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 {
7
21
  subscribe(subscriber) {
8
- // TODO: verify subscriber
22
+ assert(isValidSubscriber(subscriber), 'Invalid subscriber. Expected function or object');
23
+
9
24
  subscribers.push(subscriber);
10
25
 
11
- return () => {
12
- subscribers.splice(subscribers.indexOf(subscriber), 1);
13
- };
26
+ return () => subscribers.splice(subscribers.indexOf(subscriber), 1);
27
+ },
28
+
29
+ clear() {
30
+ subscribers = [];
14
31
  },
15
32
 
16
33
  createEvent(action, uid, properties) {
@@ -41,15 +58,9 @@ const createLifecyclesManager = db => {
41
58
  }
42
59
  }
43
60
  },
44
-
45
- clear() {
46
- subscribers = [];
47
- },
48
61
  };
49
-
50
- return lifecycleManager;
51
62
  };
52
63
 
53
64
  module.exports = {
54
- createLifecyclesManager,
65
+ createLifecyclesProvider,
55
66
  };
@@ -0,0 +1,9 @@
1
+ import { Event, Action } from '../';
2
+
3
+ type SubscriberFn = (event: Event) => Promise<void> | void;
4
+
5
+ type SubscriberMap = {
6
+ [k in Action]: SubscriberFn;
7
+ };
8
+
9
+ export type Subscriber = SubscriberFn | SubscriberMap;
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @typedef {import(".").Subscriber } Subscriber
5
+ */
6
+
7
+ /**
8
+ * For each model try to run it's lifecycles function if any is defined
9
+ * @type {Subscriber}
10
+ */
11
+ const modelsLifecyclesSubscriber = async event => {
12
+ const { model } = event;
13
+
14
+ if (event.action in model.lifecycles) {
15
+ await model.lifecycles[event.action](event);
16
+ }
17
+ };
18
+
19
+ module.exports = modelsLifecyclesSubscriber;
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ /**
6
+ * @typedef {import(".").Subscriber } Subscriber
7
+ * @typedef { import("../").Event } Event
8
+ */
9
+
10
+ // NOTE: we could add onCreate & onUpdate on field level to do this instead
11
+
12
+ /**
13
+ * @type {Subscriber}
14
+ */
15
+ const timestampsLifecyclesSubscriber = {
16
+ /**
17
+ * Init createdAt & updatedAt before create
18
+ * @param {Event} event
19
+ */
20
+ beforeCreate(event) {
21
+ const { data } = event.params;
22
+
23
+ const now = new Date();
24
+ _.defaults(data, { createdAt: now, updatedAt: now });
25
+ },
26
+
27
+ /**
28
+ * Init createdAt & updatedAt before create
29
+ * @param {Event} event
30
+ */
31
+ beforeCreateMany(event) {
32
+ const { data } = event.params;
33
+
34
+ const now = new Date();
35
+ if (_.isArray(data)) {
36
+ data.forEach(data => _.defaults(data, { createdAt: now, updatedAt: now }));
37
+ }
38
+ },
39
+
40
+ /**
41
+ * Update updatedAt before update
42
+ * @param {Event} event
43
+ */
44
+ beforeUpdate(event) {
45
+ const { data } = event.params;
46
+
47
+ const now = new Date();
48
+ _.assign(data, { updatedAt: now });
49
+ },
50
+
51
+ /**
52
+ * Update updatedAt before update
53
+ * @param {Event} event
54
+ */
55
+ beforeUpdateMany(event) {
56
+ const { data } = event.params;
57
+
58
+ const now = new Date();
59
+ if (_.isArray(data)) {
60
+ data.forEach(data => _.assign(data, { updatedAt: now }));
61
+ }
62
+ },
63
+ };
64
+
65
+ module.exports = timestampsLifecyclesSubscriber;