@smartive/graphql-magic 1.0.1

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 (124) hide show
  1. package/.eslintrc +21 -0
  2. package/.github/workflows/release.yml +24 -0
  3. package/.github/workflows/testing.yml +37 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +34 -0
  6. package/.prettierrc.json +1 -0
  7. package/.releaserc +27 -0
  8. package/CHANGELOG.md +6 -0
  9. package/README.md +15 -0
  10. package/dist/cjs/index.cjs +2646 -0
  11. package/dist/esm/client/gql.d.ts +1 -0
  12. package/dist/esm/client/gql.js +5 -0
  13. package/dist/esm/client/gql.js.map +1 -0
  14. package/dist/esm/client/index.d.ts +2 -0
  15. package/dist/esm/client/index.js +4 -0
  16. package/dist/esm/client/index.js.map +1 -0
  17. package/dist/esm/client/queries.d.ts +24 -0
  18. package/dist/esm/client/queries.js +152 -0
  19. package/dist/esm/client/queries.js.map +1 -0
  20. package/dist/esm/context.d.ts +30 -0
  21. package/dist/esm/context.js +2 -0
  22. package/dist/esm/context.js.map +1 -0
  23. package/dist/esm/errors.d.ts +17 -0
  24. package/dist/esm/errors.js +27 -0
  25. package/dist/esm/errors.js.map +1 -0
  26. package/dist/esm/generate/generate.d.ts +7 -0
  27. package/dist/esm/generate/generate.js +211 -0
  28. package/dist/esm/generate/generate.js.map +1 -0
  29. package/dist/esm/generate/index.d.ts +3 -0
  30. package/dist/esm/generate/index.js +5 -0
  31. package/dist/esm/generate/index.js.map +1 -0
  32. package/dist/esm/generate/mutations.d.ts +2 -0
  33. package/dist/esm/generate/mutations.js +18 -0
  34. package/dist/esm/generate/mutations.js.map +1 -0
  35. package/dist/esm/generate/utils.d.ts +22 -0
  36. package/dist/esm/generate/utils.js +150 -0
  37. package/dist/esm/generate/utils.js.map +1 -0
  38. package/dist/esm/index.d.ts +10 -0
  39. package/dist/esm/index.js +12 -0
  40. package/dist/esm/index.js.map +1 -0
  41. package/dist/esm/migrations/generate.d.ts +28 -0
  42. package/dist/esm/migrations/generate.js +516 -0
  43. package/dist/esm/migrations/generate.js.map +1 -0
  44. package/dist/esm/migrations/index.d.ts +1 -0
  45. package/dist/esm/migrations/index.js +3 -0
  46. package/dist/esm/migrations/index.js.map +1 -0
  47. package/dist/esm/models.d.ts +170 -0
  48. package/dist/esm/models.js +27 -0
  49. package/dist/esm/models.js.map +1 -0
  50. package/dist/esm/permissions/check.d.ts +15 -0
  51. package/dist/esm/permissions/check.js +162 -0
  52. package/dist/esm/permissions/check.js.map +1 -0
  53. package/dist/esm/permissions/generate.d.ts +45 -0
  54. package/dist/esm/permissions/generate.js +77 -0
  55. package/dist/esm/permissions/generate.js.map +1 -0
  56. package/dist/esm/permissions/index.d.ts +2 -0
  57. package/dist/esm/permissions/index.js +4 -0
  58. package/dist/esm/permissions/index.js.map +1 -0
  59. package/dist/esm/resolvers/arguments.d.ts +26 -0
  60. package/dist/esm/resolvers/arguments.js +88 -0
  61. package/dist/esm/resolvers/arguments.js.map +1 -0
  62. package/dist/esm/resolvers/filters.d.ts +5 -0
  63. package/dist/esm/resolvers/filters.js +126 -0
  64. package/dist/esm/resolvers/filters.js.map +1 -0
  65. package/dist/esm/resolvers/index.d.ts +7 -0
  66. package/dist/esm/resolvers/index.js +9 -0
  67. package/dist/esm/resolvers/index.js.map +1 -0
  68. package/dist/esm/resolvers/mutations.d.ts +3 -0
  69. package/dist/esm/resolvers/mutations.js +255 -0
  70. package/dist/esm/resolvers/mutations.js.map +1 -0
  71. package/dist/esm/resolvers/node.d.ts +44 -0
  72. package/dist/esm/resolvers/node.js +102 -0
  73. package/dist/esm/resolvers/node.js.map +1 -0
  74. package/dist/esm/resolvers/resolver.d.ts +5 -0
  75. package/dist/esm/resolvers/resolver.js +143 -0
  76. package/dist/esm/resolvers/resolver.js.map +1 -0
  77. package/dist/esm/resolvers/resolvers.d.ts +9 -0
  78. package/dist/esm/resolvers/resolvers.js +39 -0
  79. package/dist/esm/resolvers/resolvers.js.map +1 -0
  80. package/dist/esm/resolvers/utils.d.ts +43 -0
  81. package/dist/esm/resolvers/utils.js +125 -0
  82. package/dist/esm/resolvers/utils.js.map +1 -0
  83. package/dist/esm/utils.d.ts +25 -0
  84. package/dist/esm/utils.js +159 -0
  85. package/dist/esm/utils.js.map +1 -0
  86. package/dist/esm/values.d.ts +15 -0
  87. package/dist/esm/values.js +7 -0
  88. package/dist/esm/values.js.map +1 -0
  89. package/jest.config.ts +12 -0
  90. package/package.json +66 -0
  91. package/renovate.json +32 -0
  92. package/src/client/gql.ts +7 -0
  93. package/src/client/index.ts +4 -0
  94. package/src/client/queries.ts +251 -0
  95. package/src/context.ts +27 -0
  96. package/src/errors.ts +32 -0
  97. package/src/generate/generate.ts +273 -0
  98. package/src/generate/index.ts +5 -0
  99. package/src/generate/mutations.ts +35 -0
  100. package/src/generate/utils.ts +223 -0
  101. package/src/index.ts +12 -0
  102. package/src/migrations/generate.ts +633 -0
  103. package/src/migrations/index.ts +3 -0
  104. package/src/models.ts +228 -0
  105. package/src/permissions/check.ts +239 -0
  106. package/src/permissions/generate.ts +143 -0
  107. package/src/permissions/index.ts +4 -0
  108. package/src/resolvers/arguments.ts +129 -0
  109. package/src/resolvers/filters.ts +163 -0
  110. package/src/resolvers/index.ts +9 -0
  111. package/src/resolvers/mutations.ts +313 -0
  112. package/src/resolvers/node.ts +193 -0
  113. package/src/resolvers/resolver.ts +223 -0
  114. package/src/resolvers/resolvers.ts +40 -0
  115. package/src/resolvers/utils.ts +188 -0
  116. package/src/utils.ts +186 -0
  117. package/src/values.ts +19 -0
  118. package/tests/unit/__snapshots__/generate.spec.ts.snap +105 -0
  119. package/tests/unit/__snapshots__/resolve.spec.ts.snap +60 -0
  120. package/tests/unit/generate.spec.ts +8 -0
  121. package/tests/unit/resolve.spec.ts +128 -0
  122. package/tests/unit/utils.ts +82 -0
  123. package/tsconfig.jest.json +13 -0
  124. package/tsconfig.json +13 -0
@@ -0,0 +1,633 @@
1
+ import CodeBlockWriter from 'code-block-writer';
2
+ import { Knex } from 'knex';
3
+ import { SchemaInspector } from 'knex-schema-inspector';
4
+ import { Column } from 'knex-schema-inspector/dist/types/column';
5
+ import { SchemaInspector as SchemaInspectorType } from 'knex-schema-inspector/dist/types/schema-inspector';
6
+ import lowerFirst from 'lodash/lowerFirst';
7
+ import { EnumModel, isEnumModel, Model, ModelField, Models, RawModels } from '../models';
8
+ import { get, getModels, summonByName, typeToField } from '../utils';
9
+ import { Value } from '../values';
10
+
11
+ type Callbacks = (() => void)[];
12
+
13
+ export class MigrationGenerator {
14
+ private writer = new (CodeBlockWriter['default'] || CodeBlockWriter)({
15
+ useSingleQuote: true,
16
+ indentNumberOfSpaces: 2,
17
+ });
18
+ private schema: SchemaInspectorType;
19
+ private columns: Record<string, Column[]> = {};
20
+ private uuidUsed?: boolean;
21
+ private nowUsed?: boolean;
22
+ private models: Models;
23
+
24
+ constructor(knex: Knex, private rawModels: RawModels) {
25
+ this.schema = SchemaInspector(knex);
26
+ this.models = getModels(rawModels);
27
+ }
28
+
29
+ public async generate() {
30
+ const { writer, schema, rawModels, models } = this;
31
+ const enums = (await schema.knex('pg_type').where({ typtype: 'e' }).select('typname')).map(({ typname }) => typname);
32
+ const tables = await schema.tables();
33
+ for (const table of tables) {
34
+ this.columns[table] = await schema.columnInfo(table);
35
+ }
36
+
37
+ const up: Callbacks = [];
38
+ const down: Callbacks = [];
39
+
40
+ this.createEnums(
41
+ rawModels.filter(isEnumModel).filter((enm) => !enums.includes(lowerFirst(enm.name))),
42
+ up,
43
+ down
44
+ );
45
+
46
+ for (const model of models) {
47
+ if (model.deleted) {
48
+ up.push(() => {
49
+ this.dropTable(model.name);
50
+ });
51
+
52
+ down.push(() => {
53
+ this.createTable(model.name, () => {
54
+ for (const field of model.fields) {
55
+ this.column(field);
56
+ }
57
+ });
58
+ });
59
+
60
+ // TODO: also add revision table if it's deletable
61
+ if (model.updatable) {
62
+ up.push(() => {
63
+ this.dropTable(`${model.name}Revision`);
64
+ });
65
+
66
+ down.push(() => {
67
+ this.createRevisionTable(model);
68
+ });
69
+ }
70
+ }
71
+
72
+ if (model.oldName) {
73
+ // Rename table
74
+ up.push(() => {
75
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
76
+ this.renameTable(model.oldName!, model.name);
77
+ });
78
+ down.push(() => {
79
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
80
+ this.renameTable(model.name, model.oldName!);
81
+ });
82
+ tables[tables.indexOf(model.oldName)] = model.name;
83
+ this.columns[model.name] = this.columns[model.oldName]!;
84
+ delete this.columns[model.oldName];
85
+
86
+ if (model.updatable) {
87
+ up.push(() => {
88
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
89
+ this.renameTable(`${model.oldName!}Revision`, `${model.name}Revision`);
90
+ this.alterTable(`${model.name}Revision`, () => {
91
+ this.renameColumn(`${typeToField(get(model, 'oldName'))}Id`, `${typeToField(model.name)}Id`);
92
+ });
93
+ });
94
+ down.push(() => {
95
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
96
+ this.renameTable(`${model.name}Revision`, `${model.oldName}Revision`);
97
+ this.alterTable(`${model.oldName}Revision`, () => {
98
+ this.renameColumn(`${typeToField(model.name)}Id`, `${typeToField(get(model, 'oldName'))}Id`);
99
+ });
100
+ });
101
+ tables[tables.indexOf(`${model.oldName}Revision`)] = `${model.name}Revision`;
102
+ this.columns[`${model.name}Revision`] = this.columns[`${model.oldName}Revision`]!;
103
+ delete this.columns[`${model.oldName}Revision`];
104
+ }
105
+ }
106
+
107
+ if (!tables.includes(model.name)) {
108
+ // Create missing table
109
+ up.push(() => {
110
+ this.createTable(model.name, () => {
111
+ for (const field of model.fields) {
112
+ this.column(field);
113
+ }
114
+ });
115
+ });
116
+
117
+ down.push(() => {
118
+ this.dropTable(model.name);
119
+ });
120
+ } else {
121
+ // Rename fields
122
+ this.renameFields(
123
+ model,
124
+ model.fields.filter(({ oldName }) => oldName),
125
+ up,
126
+ down
127
+ );
128
+
129
+ // Add missing fields
130
+ this.createFields(
131
+ model,
132
+ model.fields.filter(
133
+ ({ name, relation, raw, foreignKey }) =>
134
+ !raw && !this.columns[model.name]!.some((col) => col.name === (foreignKey || (relation ? `${name}Id` : name)))
135
+ ),
136
+ up,
137
+ down
138
+ );
139
+
140
+ // Update fields
141
+ const existingFields = model.fields.filter(({ name, relation, nonNull }) => {
142
+ const col = this.columns[model.name]!.find((col) => col.name === (relation ? `${name}Id` : name));
143
+ if (!col) {
144
+ return false;
145
+ }
146
+ return !model.nonStrict && !nonNull && !col.is_nullable;
147
+ });
148
+ this.updateFields(model, existingFields, up, down);
149
+ }
150
+
151
+ if (model.updatable) {
152
+ if (!tables.includes(`${model.name}Revision`)) {
153
+ up.push(() => {
154
+ this.createRevisionTable(model);
155
+ });
156
+
157
+ if (tables.includes(model.name)) {
158
+ up.push(() => {
159
+ // Populate empty revisions tables
160
+ writer
161
+ .block(() => {
162
+ writer.writeLine(`const data = await knex('${model.name}');`);
163
+
164
+ writer.write(`if (data.length)`).block(() => {
165
+ writer
166
+ .write(`await knex.batchInsert('${model.name}Revision', data.map((row) => (`)
167
+ .inlineBlock(() => {
168
+ writer.writeLine(`id: uuid(),`);
169
+ writer.writeLine(`${typeToField(model.name)}Id: row.id,`);
170
+ this.nowUsed = true;
171
+ writer.writeLine(`createdAt: row.updatedAt || row.createdAt || now,`);
172
+ writer.writeLine(`createdById: row.updatedById || row.createdById,`);
173
+ if (model.deletable) {
174
+ writer.writeLine(`deleted: row.deleted,`);
175
+ }
176
+
177
+ for (const { name, relation } of model.fields.filter(({ updatable }) => updatable)) {
178
+ const col = relation ? `${name}Id` : name;
179
+
180
+ writer.writeLine(`${col}: row.${col},`);
181
+ }
182
+ })
183
+ .write(')));')
184
+ .newLine();
185
+ });
186
+ })
187
+ .blankLine();
188
+ });
189
+ }
190
+
191
+ down.push(() => {
192
+ this.dropTable(`${model.name}Revision`);
193
+ });
194
+ } else {
195
+ const revisionTable = `${model.name}Revision`;
196
+ const missingRevisionFields = model.fields.filter(
197
+ ({ name, relation, raw, foreignKey, updatable }) =>
198
+ !raw &&
199
+ updatable &&
200
+ !this.columns[revisionTable]!.some((col) => col.name === (foreignKey || (relation ? `${name}Id` : name)))
201
+ );
202
+
203
+ this.createRevisionFields(model, missingRevisionFields, up, down);
204
+
205
+ const revisionFieldsToRemove = model.fields.filter(
206
+ ({ name, updatable, foreignKey, relation, raw, generated }) =>
207
+ !generated &&
208
+ !raw &&
209
+ !updatable &&
210
+ foreignKey !== 'id' &&
211
+ this.columns[revisionTable]!.some((col) => col.name === (foreignKey || (relation ? `${name}Id` : name)))
212
+ );
213
+ this.createRevisionFields(model, revisionFieldsToRemove, down, up);
214
+ }
215
+ }
216
+ }
217
+
218
+ for (const model of getModels(rawModels)) {
219
+ if (tables.includes(model.name)) {
220
+ this.createFields(
221
+ model,
222
+ model.fields.filter(({ name, deleted }) => deleted && this.columns[model.name]!.some((col) => col.name === name)),
223
+ down,
224
+ up
225
+ );
226
+
227
+ if (model.updatable) {
228
+ this.createRevisionFields(
229
+ model,
230
+ model.fields.filter(({ deleted, updatable }) => updatable && deleted),
231
+ down,
232
+ up
233
+ );
234
+ }
235
+ }
236
+ }
237
+
238
+ this.createEnums(
239
+ rawModels.filter(isEnumModel).filter((enm) => enm.deleted),
240
+ down,
241
+ up
242
+ );
243
+
244
+ writer.writeLine(`import { Knex } from 'knex';`);
245
+ if (this.uuidUsed) {
246
+ writer.writeLine(`import { v4 as uuid } from 'uuid';`);
247
+ }
248
+ if (this.nowUsed) {
249
+ writer.writeLine(`import { date } from '../src/utils/dates';`);
250
+ }
251
+ writer.blankLine();
252
+
253
+ if (this.nowUsed) {
254
+ writer.writeLine(`const now = date();`).blankLine();
255
+ }
256
+
257
+ this.migration('up', up);
258
+ this.migration('down', down.reverse());
259
+
260
+ return writer.toString();
261
+ }
262
+
263
+ private renameFields(model: Model, fields: ModelField[], up: Callbacks, down: Callbacks) {
264
+ if (!fields.length) {
265
+ return;
266
+ }
267
+
268
+ up.push(() => {
269
+ for (const field of fields) {
270
+ this.alterTable(model.name, () => {
271
+ this.renameColumn(
272
+ field.relation ? `${field.oldName}Id` : get(field, 'oldName'),
273
+ field.relation ? `${field.name}Id` : field.name
274
+ );
275
+ });
276
+ }
277
+ });
278
+
279
+ down.push(() => {
280
+ for (const field of fields) {
281
+ this.alterTable(model.name, () => {
282
+ this.renameColumn(
283
+ field.relation ? `${field.name}Id` : field.name,
284
+ field.relation ? `${field.oldName}Id` : get(field, 'oldName')
285
+ );
286
+ });
287
+ }
288
+ });
289
+
290
+ for (const field of fields) {
291
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
292
+ summonByName(this.columns[model.name]!, field.relation ? `${field.oldName!}Id` : field.oldName!).name = field.relation
293
+ ? `${field.name}Id`
294
+ : field.name;
295
+ }
296
+ }
297
+
298
+ private createFields(model: Model, fields: ModelField[], up: Callbacks, down: Callbacks) {
299
+ if (!fields.length) {
300
+ return;
301
+ }
302
+
303
+ up.push(() => {
304
+ const alter: Callbacks = [];
305
+ const updates: Callbacks = [];
306
+ const postAlter: Callbacks = [];
307
+ for (const field of fields) {
308
+ alter.push(() => this.column(field, { setNonNull: field.default !== undefined }));
309
+
310
+ // If the field is not nullable but has no default, write placeholder code
311
+ if (field.nonNull && field.default === undefined) {
312
+ updates.push(() => this.writer.write(`${field.name}: 'TODO',`).newLine());
313
+ postAlter.push(() => this.column(field, { alter: true, foreign: false }));
314
+ }
315
+ }
316
+ if (alter.length) {
317
+ this.alterTable(model.name, () => {
318
+ alter.map((cb) => cb());
319
+ });
320
+ }
321
+ if (updates.length) {
322
+ this.writer
323
+ .write(`await knex('${model.name}').update(`)
324
+ .inlineBlock(() => {
325
+ updates.map((cb) => cb());
326
+ })
327
+ .write(');')
328
+ .newLine()
329
+ .blankLine();
330
+ }
331
+ if (postAlter.length) {
332
+ this.alterTable(model.name, () => {
333
+ postAlter.map((cb) => cb());
334
+ });
335
+ }
336
+ });
337
+
338
+ down.push(() => {
339
+ this.alterTable(model.name, () => {
340
+ for (const { relation, name } of fields) {
341
+ this.dropColumn(relation ? `${name}Id` : name);
342
+ }
343
+ });
344
+ });
345
+ }
346
+
347
+ private updateFields(model: Model, fields: ModelField[], up: Callbacks, down: Callbacks) {
348
+ if (!fields.length) {
349
+ return;
350
+ }
351
+
352
+ up.push(() => {
353
+ this.alterTable(model.name, () => {
354
+ for (const field of fields) {
355
+ this.column(field, { alter: true });
356
+ }
357
+ });
358
+ });
359
+
360
+ down.push(() => {
361
+ this.alterTable(model.name, () => {
362
+ for (const field of fields) {
363
+ this.column(
364
+ field,
365
+ { alter: true },
366
+ summonByName(this.columns[model.name]!, field.relation ? `${field.name}Id` : field.name)
367
+ );
368
+ }
369
+ });
370
+ });
371
+
372
+ if (model.updatable) {
373
+ const updatableFields = fields.filter(({ updatable }) => updatable);
374
+ if (!updatableFields.length) {
375
+ return;
376
+ }
377
+
378
+ up.push(() => {
379
+ this.alterTable(`${model.name}Revision`, () => {
380
+ for (const field of updatableFields) {
381
+ this.column(field, { alter: true });
382
+ }
383
+ });
384
+ });
385
+
386
+ down.push(() => {
387
+ this.alterTable(`${model.name}Revision`, () => {
388
+ for (const field of updatableFields) {
389
+ this.column(
390
+ field,
391
+ { alter: true },
392
+ summonByName(this.columns[model.name]!, field.relation ? `${field.name}Id` : field.name)
393
+ );
394
+ }
395
+ });
396
+ });
397
+ }
398
+ }
399
+
400
+ private createRevisionTable(model: Model) {
401
+ const writer = this.writer;
402
+
403
+ // Create missing revisions table
404
+ this.createTable(`${model.name}Revision`, () => {
405
+ writer.writeLine(`table.uuid('id').notNullable().primary();`);
406
+ writer.writeLine(`table.uuid('${typeToField(model.name)}Id').notNullable();`);
407
+ writer.write(`table.uuid('createdById')`);
408
+ if (!model.nonStrict) {
409
+ writer.write('.notNullable()');
410
+ }
411
+ writer.write(';').newLine();
412
+ writer.writeLine(`table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now(0));`);
413
+ if (model.deletable) {
414
+ writer.writeLine(`table.boolean('deleted').notNullable();`);
415
+ }
416
+
417
+ for (const field of model.fields.filter((field) => field.updatable)) {
418
+ this.column(field, { setUnique: false, setDefault: false });
419
+ }
420
+ });
421
+ }
422
+
423
+ private createRevisionFields(model: Model, missingRevisionFields: ModelField[], up: Callbacks, down: Callbacks) {
424
+ const revisionTable = `${model.name}Revision`;
425
+ if (missingRevisionFields.length) {
426
+ up.push(() => {
427
+ // Create missing revision columns
428
+ this.alterTable(revisionTable, () => {
429
+ for (const field of missingRevisionFields) {
430
+ this.column(field, { setUnique: false, setNonNull: false, setDefault: false });
431
+ }
432
+ });
433
+
434
+ // Insert data for missing revisions columns
435
+ this.writer
436
+ .write(`await knex('${model.name}Revision').update(`)
437
+ .inlineBlock(() => {
438
+ for (const { name, relation } of missingRevisionFields) {
439
+ const col = relation ? `${name}Id` : name;
440
+ this.writer
441
+ .write(
442
+ `${col}: knex.raw('(select "${col}" from "${model.name}" where "${model.name}".id = "${
443
+ model.name
444
+ }Revision"."${typeToField(model.name)}Id")'),`
445
+ )
446
+ .newLine();
447
+ }
448
+ })
449
+ .write(');')
450
+ .newLine()
451
+ .blankLine();
452
+
453
+ const nonNullableMissingRevisionFields = missingRevisionFields.filter(({ nonNull }) => nonNull);
454
+ if (nonNullableMissingRevisionFields.length) {
455
+ this.alterTable(revisionTable, () => {
456
+ for (const field of nonNullableMissingRevisionFields) {
457
+ this.column(field, { setUnique: false, setDefault: false, alter: true });
458
+ }
459
+ });
460
+ }
461
+ });
462
+
463
+ down.push(() => {
464
+ this.alterTable(revisionTable, () => {
465
+ for (const field of missingRevisionFields) {
466
+ this.dropColumn(field.relation ? `${field.name}Id` : field.name);
467
+ }
468
+ });
469
+ });
470
+ }
471
+ }
472
+
473
+ private createEnums(enums: EnumModel[], up: Callbacks, down: Callbacks) {
474
+ for (const enm of enums) {
475
+ const name = lowerFirst(enm.name);
476
+ up.push(() =>
477
+ this.writer
478
+ .writeLine(
479
+ `await knex.raw(\`CREATE TYPE "${name}" AS ENUM (${enm.values.map((value) => `'${value}'`).join(',')})\`);`
480
+ )
481
+ .newLine()
482
+ );
483
+ down.push(() => this.writer.writeLine(`await knex.raw('DROP TYPE "${name}"')`));
484
+ }
485
+ }
486
+
487
+ private migration(name: 'up' | 'down', cbs: (() => void)[]) {
488
+ return this.writer
489
+ .write(`export const ${name} = async (knex: Knex) => `)
490
+ .inlineBlock(() => {
491
+ cbs.map((cb) => cb());
492
+ })
493
+ .write(';')
494
+ .newLine()
495
+ .blankLine();
496
+ }
497
+
498
+ private createTable(table: string, block: () => void) {
499
+ return this.writer
500
+ .write(`await knex.schema.createTable('${table}', (table) => `)
501
+ .inlineBlock(block)
502
+ .write(');')
503
+ .newLine()
504
+ .blankLine();
505
+ }
506
+
507
+ private alterTable(table: string, block: () => void) {
508
+ return this.writer
509
+ .write(`await knex.schema.alterTable('${table}', (table) => `)
510
+ .inlineBlock(block)
511
+ .write(');')
512
+ .newLine()
513
+ .blankLine();
514
+ }
515
+
516
+ private dropColumn(col: string) {
517
+ return this.writer.writeLine(`table.dropColumn('${col}');`);
518
+ }
519
+
520
+ private dropTable(table: string) {
521
+ return this.writer.writeLine(`await knex.schema.dropTable('${table}');`).blankLine();
522
+ }
523
+
524
+ private renameTable(from: string, to: string) {
525
+ return this.writer.writeLine(`await knex.schema.renameTable('${from}', '${to}');`).blankLine();
526
+ }
527
+
528
+ private renameColumn(from: string, to: string) {
529
+ this.writer.writeLine(`table.renameColumn('${from}', '${to}')`);
530
+ }
531
+
532
+ private value(value: Value) {
533
+ if (typeof value === 'string') {
534
+ return `'${value}'`;
535
+ }
536
+
537
+ return value;
538
+ }
539
+
540
+ private column(
541
+ { name, relation, type, primary, list, ...field }: ModelField,
542
+ { setUnique = true, setNonNull = true, alter = false, foreign = true, setDefault = true } = {},
543
+ toColumn?: Column
544
+ ) {
545
+ const col = (what?: string) => {
546
+ if (what) {
547
+ this.writer.write(what);
548
+ }
549
+ if (setNonNull) {
550
+ if (toColumn) {
551
+ if (toColumn.is_nullable) {
552
+ this.writer.write(`.nullable()`);
553
+ } else {
554
+ this.writer.write('.notNullable()');
555
+ }
556
+ } else {
557
+ if (field.nonNull) {
558
+ this.writer.write(`.notNullable()`);
559
+ } else {
560
+ this.writer.write('.nullable()');
561
+ }
562
+ }
563
+ }
564
+ if (setDefault && field.default !== undefined) {
565
+ this.writer.write(`.defaultTo(${this.value(field.default)})`);
566
+ }
567
+ if (primary) {
568
+ this.writer.write('.primary()');
569
+ } else if (setUnique && field.unique) {
570
+ this.writer.write('.unique()');
571
+ }
572
+ if (alter) {
573
+ this.writer.write('.alter()');
574
+ }
575
+ this.writer.write(';').newLine();
576
+ };
577
+ if (relation) {
578
+ col(`table.uuid('${name}Id')`);
579
+ if (foreign && !alter) {
580
+ this.writer.writeLine(`table.foreign('${name}Id').references('id').inTable('${type}');`);
581
+ }
582
+ } else if (this.rawModels.some((m) => m.name === type && m.type === 'enum')) {
583
+ list
584
+ ? this.writer.write(`table.specificType('${name}', '"${typeToField(type)}"[]');`)
585
+ : this.writer
586
+ .write(`table.enum('${name}', null as any, `)
587
+ .inlineBlock(() => {
588
+ this.writer.writeLine(`useNative: true,`);
589
+ this.writer.writeLine(`existingType: true,`);
590
+ this.writer.writeLine(`enumName: '${typeToField(type)}',`);
591
+ })
592
+ .write(')');
593
+ col();
594
+ } else {
595
+ switch (type) {
596
+ case 'Boolean':
597
+ col(`table.boolean('${name}')`);
598
+ break;
599
+ case 'Int':
600
+ col(`table.integer('${name}')`);
601
+ break;
602
+ case 'Float':
603
+ if (field.double) {
604
+ col(`table.double('${name}')`);
605
+ } else {
606
+ col(`table.decimal('${name}', ${get(field, 'precision')}, ${get(field, 'scale')})`);
607
+ }
608
+ break;
609
+ case 'String':
610
+ if (field.large) {
611
+ col(`table.text('${name}')`);
612
+ } else {
613
+ col(`table.string('${name}', ${field.maxLength})`);
614
+ }
615
+ break;
616
+ case 'DateTime':
617
+ col(`table.timestamp('${name}')`);
618
+ break;
619
+ case 'ID':
620
+ if (field.maxLength) {
621
+ col(`table.string('${name}', ${get(field, 'maxLength')})`);
622
+ } else {
623
+ col(`table.uuid('${name}')`);
624
+ }
625
+ break;
626
+ case 'Upload':
627
+ break;
628
+ default:
629
+ throw new Error(`Unknown field type ${type}`);
630
+ }
631
+ }
632
+ }
633
+ }
@@ -0,0 +1,3 @@
1
+ // created from 'create-ts-index'
2
+
3
+ export * from './generate';