@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
@@ -0,0 +1,232 @@
1
+ 'use strict';
2
+
3
+ const SQL_QUERIES = {
4
+ TABLE_LIST: /* sql */ `
5
+ SELECT *
6
+ FROM information_schema.tables
7
+ WHERE
8
+ table_schema = ?
9
+ AND table_type = 'BASE TABLE'
10
+ AND table_name != 'geometry_columns'
11
+ AND table_name != 'spatial_ref_sys';
12
+ `,
13
+ LIST_COLUMNS: /* sql */ `
14
+ SELECT data_type, column_name, character_maximum_length, column_default, is_nullable
15
+ FROM information_schema.columns
16
+ WHERE table_schema = ? AND table_name = ?;
17
+ `,
18
+ INDEX_LIST: /* sql */ `
19
+ SELECT
20
+ ix.indexrelid,
21
+ i.relname as index_name,
22
+ a.attname as column_name,
23
+ ix.indisunique as is_unique,
24
+ ix.indisprimary as is_primary
25
+ FROM
26
+ pg_class t,
27
+ pg_namespace s,
28
+ pg_class i,
29
+ pg_index ix,
30
+ pg_attribute a
31
+ WHERE
32
+ t.oid = ix.indrelid
33
+ AND i.oid = ix.indexrelid
34
+ AND a.attrelid = t.oid
35
+ AND a.attnum = ANY(ix.indkey)
36
+ AND t.relkind = 'r'
37
+ AND t.relnamespace = s.oid
38
+ AND s.nspname = ?
39
+ AND t.relname = ?;
40
+ `,
41
+ FOREIGN_KEY_LIST: /* sql */ `
42
+ SELECT
43
+ tco."constraint_name" as constraint_name,
44
+ kcu."column_name" as column_name,
45
+ rel_kcu."table_name" as foreign_table,
46
+ rel_kcu."column_name" as fk_column_name,
47
+ rco.update_rule as on_update,
48
+ rco.delete_rule as on_delete
49
+ FROM information_schema.table_constraints tco
50
+ JOIN information_schema.key_column_usage kcu
51
+ ON tco.constraint_schema = kcu.constraint_schema
52
+ AND tco.constraint_name = kcu.constraint_name
53
+ JOIN information_schema.referential_constraints rco
54
+ ON tco.constraint_schema = rco.constraint_schema
55
+ AND tco.constraint_name = rco.constraint_name
56
+ JOIN information_schema.key_column_usage rel_kcu
57
+ ON rco.unique_constraint_schema = rel_kcu.constraint_schema
58
+ AND rco.unique_constraint_name = rel_kcu.constraint_name
59
+ AND kcu.ordinal_position = rel_kcu.ordinal_position
60
+ WHERE
61
+ tco.constraint_type = 'FOREIGN KEY'
62
+ AND tco.constraint_schema = ?
63
+ AND tco.table_name = ?
64
+ ORDER BY kcu.table_schema, kcu.table_name, kcu.ordinal_position, kcu.constraint_name;
65
+ `,
66
+ };
67
+
68
+ const toStrapiType = column => {
69
+ const rootType = column.data_type.toLowerCase().match(/[^(), ]+/)[0];
70
+
71
+ switch (rootType) {
72
+ case 'integer': {
73
+ // find a way to figure out the increments
74
+ return { type: 'integer' };
75
+ }
76
+ case 'text': {
77
+ return { type: 'text', args: ['longtext'] };
78
+ }
79
+ case 'boolean': {
80
+ return { type: 'boolean' };
81
+ }
82
+ case 'character': {
83
+ return { type: 'string', args: [column.character_maximum_length] };
84
+ }
85
+ case 'timestamp': {
86
+ return { type: 'datetime', args: [{ useTz: false, precision: 6 }] };
87
+ }
88
+ case 'date': {
89
+ return { type: 'date' };
90
+ }
91
+ case 'time': {
92
+ return { type: 'time', args: [{ precision: 3 }] };
93
+ }
94
+ case 'numeric': {
95
+ return { type: 'decimal', args: [10, 2] };
96
+ }
97
+ case 'real':
98
+ case 'double': {
99
+ return { type: 'double' };
100
+ }
101
+ case 'bigint': {
102
+ return { type: 'bigInteger' };
103
+ }
104
+ case 'jsonb': {
105
+ return { type: 'jsonb' };
106
+ }
107
+ default: {
108
+ return { type: 'specificType', args: [column.data_type] };
109
+ }
110
+ }
111
+ };
112
+
113
+ class PostgresqlSchemaInspector {
114
+ constructor(db) {
115
+ this.db = db;
116
+ }
117
+
118
+ async getSchema() {
119
+ const schema = { tables: [] };
120
+
121
+ const tables = await this.getTables();
122
+
123
+ schema.tables = await Promise.all(
124
+ tables.map(async tableName => {
125
+ const columns = await this.getColumns(tableName);
126
+ const indexes = await this.getIndexes(tableName);
127
+ const foreignKeys = await this.getForeignKeys(tableName);
128
+
129
+ return {
130
+ name: tableName,
131
+ columns,
132
+ indexes,
133
+ foreignKeys,
134
+ };
135
+ })
136
+ );
137
+
138
+ return schema;
139
+ }
140
+
141
+ getDatabaseSchema() {
142
+ return this.db.connection.getSchemaName() || 'public';
143
+ }
144
+
145
+ async getTables() {
146
+ const { rows } = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST, [
147
+ this.getDatabaseSchema(),
148
+ ]);
149
+
150
+ return rows.map(row => row.table_name);
151
+ }
152
+
153
+ async getColumns(tableName) {
154
+ const { rows } = await this.db.connection.raw(SQL_QUERIES.LIST_COLUMNS, [
155
+ this.getDatabaseSchema(),
156
+ tableName,
157
+ ]);
158
+
159
+ return rows.map(row => {
160
+ const { type, args = [], ...rest } = toStrapiType(row);
161
+
162
+ const defaultTo =
163
+ row.column_default && row.column_default.includes('nextval(') ? null : row.column_default;
164
+
165
+ return {
166
+ type,
167
+ args,
168
+ defaultTo,
169
+ name: row.column_name,
170
+ notNullable: row.is_nullable === 'NO',
171
+ unsigned: false,
172
+ ...rest,
173
+ };
174
+ });
175
+ }
176
+
177
+ async getIndexes(tableName) {
178
+ const { rows } = await this.db.connection.raw(SQL_QUERIES.INDEX_LIST, [
179
+ this.getDatabaseSchema(),
180
+ tableName,
181
+ ]);
182
+
183
+ const ret = {};
184
+
185
+ for (const index of rows) {
186
+ if (index.column_name === 'id') {
187
+ continue;
188
+ }
189
+
190
+ if (!ret[index.indexrelid]) {
191
+ ret[index.indexrelid] = {
192
+ columns: [index.column_name],
193
+ name: index.index_name,
194
+ type: index.is_primary ? 'primary' : index.is_unique ? 'unique' : null,
195
+ };
196
+ } else {
197
+ ret[index.indexrelid].columns.push(index.column_name);
198
+ }
199
+ }
200
+
201
+ return Object.values(ret);
202
+ }
203
+
204
+ async getForeignKeys(tableName) {
205
+ const { rows } = await this.db.connection.raw(SQL_QUERIES.FOREIGN_KEY_LIST, [
206
+ this.getDatabaseSchema(),
207
+ tableName,
208
+ ]);
209
+
210
+ const ret = {};
211
+
212
+ for (const fk of rows) {
213
+ if (!ret[fk.constraint_name]) {
214
+ ret[fk.constraint_name] = {
215
+ name: fk.constraint_name,
216
+ columns: [fk.column_name],
217
+ referencedColumns: [fk.fk_column_name],
218
+ referencedTable: fk.foreign_table,
219
+ onUpdate: fk.on_update.toUpperCase(),
220
+ onDelete: fk.on_delete.toUpperCase(),
221
+ };
222
+ } else {
223
+ ret[fk.constraint_name].columns.push(fk.column_name);
224
+ ret[fk.constraint_name].referencedColumns.push(fk.fk_column_name);
225
+ }
226
+ }
227
+
228
+ return Object.values(ret);
229
+ }
230
+ }
231
+
232
+ module.exports = PostgresqlSchemaInspector;
@@ -0,0 +1,73 @@
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
+ case 'enum': {
38
+ return 'text';
39
+ }
40
+ case 'double':
41
+ case 'decimal': {
42
+ return 'float';
43
+ }
44
+ case 'timestamp': {
45
+ return 'datetime';
46
+ }
47
+ default: {
48
+ return type;
49
+ }
50
+ }
51
+ }
52
+
53
+ async startSchemaUpdate() {
54
+ await this.db.connection.raw(`pragma foreign_keys = off`);
55
+ }
56
+
57
+ async endSchemaUpdate() {
58
+ await this.db.connection.raw(`pragma foreign_keys = on`);
59
+ }
60
+
61
+ transformErrors(error) {
62
+ switch (error.errno) {
63
+ case 19: {
64
+ throw new errors.NotNullConstraint(); // TODO: extract column name
65
+ }
66
+ default: {
67
+ super.transformErrors(error);
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ 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;
@@ -29,8 +29,7 @@ const toAssocs = data => {
29
29
  });
30
30
  };
31
31
 
32
- // TODO: handle programmatic defaults
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 = toRow(metadata, data, { withDefaults: true });
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 not select or populate specified return the inserted data ?
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 => toRow(metadata, datum, { withDefaults: true }));
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 = toRow(metadata, data);
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 = toRow(metadata, data);
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: avoid trigger the findOne lifecycles in the case ?
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 (['oneToOne', 'oneToMany'].includes(attribute.relation)) {
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: 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
 
@@ -95,9 +95,8 @@ const createRepository = (uid, db) => {
95
95
  return db.entityManager.deleteRelations(uid, id);
96
96
  },
97
97
 
98
- // TODO: add relation API
99
- populate(entity, field, params) {
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
- class NotNullConstraint extends Error {
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 ? `on on column ${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
@@ -1,10 +1,9 @@
1
+ import { Attribute } from './schema';
2
+
1
3
  interface Field {
2
4
  config: {};
3
5
  toDB(value: any): any;
4
6
  fromDB(value: any): any;
5
7
  }
6
8
 
7
- interface Attribute {
8
- type: string
9
- }
10
9
  export function createField(attribute: Attribute): Field;