@smartive/graphql-magic 2.1.1 → 3.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 (40) hide show
  1. package/.eslintrc +1 -1
  2. package/.github/workflows/release.yml +1 -1
  3. package/.github/workflows/testing.yml +3 -2
  4. package/.nvmrc +1 -1
  5. package/CHANGELOG.md +2 -2
  6. package/dist/cjs/index.cjs +8 -6
  7. package/dist/esm/migrations/generate.js.map +1 -1
  8. package/dist/esm/permissions/check.js.map +1 -1
  9. package/dist/esm/permissions/generate.js.map +1 -1
  10. package/dist/esm/resolvers/filters.js.map +1 -1
  11. package/dist/esm/resolvers/mutations.js +7 -6
  12. package/dist/esm/resolvers/mutations.js.map +1 -1
  13. package/dist/esm/resolvers/node.js.map +1 -1
  14. package/dist/esm/resolvers/resolver.js.map +1 -1
  15. package/dist/esm/resolvers/utils.js.map +1 -1
  16. package/docker-compose.yml +17 -0
  17. package/package.json +16 -9
  18. package/src/migrations/generate.ts +9 -9
  19. package/src/permissions/check.ts +1 -1
  20. package/src/permissions/generate.ts +7 -7
  21. package/src/resolvers/filters.ts +3 -3
  22. package/src/resolvers/mutations.ts +17 -14
  23. package/src/resolvers/node.ts +1 -1
  24. package/src/resolvers/resolver.ts +2 -2
  25. package/src/resolvers/utils.ts +2 -2
  26. package/tests/api/__snapshots__/delete.spec.ts.snap +18 -0
  27. package/tests/api/__snapshots__/query.spec.ts.snap +22 -0
  28. package/tests/api/delete.spec.ts +28 -0
  29. package/tests/api/query.spec.ts +28 -0
  30. package/tests/unit/__snapshots__/generate.spec.ts.snap +20 -0
  31. package/tests/unit/__snapshots__/resolve.spec.ts.snap +3 -0
  32. package/tests/unit/generate.spec.ts +2 -2
  33. package/tests/unit/resolve.spec.ts +2 -2
  34. package/tests/utils/database/knex.ts +19 -0
  35. package/tests/utils/database/schema.ts +62 -0
  36. package/tests/utils/database/seed.ts +54 -0
  37. package/tests/utils/generate-migration.ts +35 -0
  38. package/tests/{unit/utils.ts → utils/models.ts} +26 -5
  39. package/tests/utils/server.ts +108 -0
  40. package/tsconfig.json +1 -1
@@ -71,7 +71,7 @@ export const generatePermissions = (models: Models, config: PermissionsConfig) =
71
71
  rolePermissions[type] = {};
72
72
  for (const action of ACTIONS) {
73
73
  if (action === 'READ' || action in block) {
74
- rolePermissions[type]![action] = true;
74
+ rolePermissions[type][action] = true;
75
75
  }
76
76
  }
77
77
  }
@@ -95,7 +95,7 @@ export const generatePermissions = (models: Models, config: PermissionsConfig) =
95
95
  };
96
96
 
97
97
  const addPermissions = (models: Models, permissions: RolePermissions, links: PermissionLink[], block: PermissionsBlock) => {
98
- const { type } = links[links.length - 1]!;
98
+ const { type } = links[links.length - 1];
99
99
  const model = summonByName(models, type);
100
100
 
101
101
  for (const action of ACTIONS) {
@@ -103,11 +103,11 @@ const addPermissions = (models: Models, permissions: RolePermissions, links: Per
103
103
  if (!permissions[type]) {
104
104
  permissions[type] = {};
105
105
  }
106
- if (!permissions[type]![action]) {
107
- permissions[type]![action] = [];
106
+ if (!permissions[type][action]) {
107
+ permissions[type][action] = [];
108
108
  }
109
- if (permissions[type]![action] !== true) {
110
- (permissions[type]![action] as PermissionStack).push(links);
109
+ if (permissions[type][action] !== true) {
110
+ (permissions[type][action] as PermissionStack).push(links);
111
111
  }
112
112
  }
113
113
  }
@@ -123,7 +123,7 @@ const addPermissions = (models: Models, permissions: RolePermissions, links: Per
123
123
  reverse: true,
124
124
  };
125
125
  } else {
126
- const field = model.reverseRelationsByName[relation]!;
126
+ const field = model.reverseRelationsByName[relation];
127
127
 
128
128
  if (!field) {
129
129
  throw new Error(`Relation ${relation} in model ${model.name} does not exist.`);
@@ -72,12 +72,12 @@ const applyWhere = (node: WhereNode, where: Where, ops: Ops<Knex.QueryBuilder>,
72
72
  const specialFilter = key.match(/^(\w+)_(\w+)$/);
73
73
  if (specialFilter) {
74
74
  const [, actualKey, filter] = specialFilter;
75
- if (!SPECIAL_FILTERS[filter!]) {
75
+ if (!SPECIAL_FILTERS[filter]) {
76
76
  // Should not happen
77
77
  throw new Error(`Invalid filter ${key}.`);
78
78
  }
79
79
  ops.push((query) =>
80
- query.whereRaw(SPECIAL_FILTERS[filter!]!, [`${node.shortTableAlias}.${actualKey}`, value as string])
80
+ query.whereRaw(SPECIAL_FILTERS[filter], [`${node.shortTableAlias}.${actualKey}`, value as string])
81
81
  );
82
82
  continue;
83
83
  }
@@ -154,7 +154,7 @@ const applyOrderBy = (node: FieldResolverNode, orderBy: OrderBy, query: Knex.Que
154
154
  throw new UserInputError(`You need to specify exactly 1 value to order by for each orderBy entry.`);
155
155
  }
156
156
  const key = keys[0];
157
- const value = vals[key!];
157
+ const value = vals[key];
158
158
 
159
159
  // Simple field
160
160
  // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
@@ -12,7 +12,7 @@ export const mutationResolver = async (_parent: any, args: any, partialCtx: Cont
12
12
  return await partialCtx.knex.transaction(async (knex) => {
13
13
  const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
14
14
  const ctx = { ...partialCtx, knex, info, aliases: new AliasGenerator() };
15
- const model = summonByName(ctx.models, modelName!);
15
+ const model = summonByName(ctx.models, modelName);
16
16
  switch (mutation) {
17
17
  case 'create':
18
18
  return await create(model, args, ctx);
@@ -110,17 +110,20 @@ const del = async (model: Model, { where, dryRun }: { where: any; dryRun: boolea
110
110
  return;
111
111
  }
112
112
 
113
- if (dryRun) {
114
- if (!(currentModel.name in toDelete)) {
115
- toDelete[currentModel.name] = {};
116
- }
117
- toDelete[currentModel.name]![entity.id] = entity[currentModel.displayField || 'id'] || entity.id;
118
- } else {
113
+ if (!(currentModel.name in toDelete)) {
114
+ toDelete[currentModel.name] = {};
115
+ }
116
+ if (entity.id in toDelete[currentModel.name]) {
117
+ return;
118
+ }
119
+ toDelete[currentModel.name][entity.id] = entity[currentModel.displayField || 'id'] || entity.id;
120
+
121
+ if (!dryRun) {
119
122
  const normalizedInput = { deleted: true, deletedAt: ctx.now, deletedById: ctx.user.id };
120
123
  const data = { prev: entity, input: {}, normalizedInput, next: { ...entity, ...normalizedInput } };
121
124
  if (ctx.mutationHook) {
122
125
  beforeHooks.push(async () => {
123
- await ctx.mutationHook!(currentModel, 'delete', 'before', data, ctx);
126
+ await ctx.mutationHook(currentModel, 'delete', 'before', data, ctx);
124
127
  });
125
128
  }
126
129
  mutations.push(async () => {
@@ -129,7 +132,7 @@ const del = async (model: Model, { where, dryRun }: { where: any; dryRun: boolea
129
132
  });
130
133
  if (ctx.mutationHook) {
131
134
  afterHooks.push(async () => {
132
- await ctx.mutationHook!(currentModel, 'delete', 'after', data, ctx);
135
+ await ctx.mutationHook(currentModel, 'delete', 'after', data, ctx);
133
136
  });
134
137
  }
135
138
  }
@@ -148,13 +151,13 @@ const del = async (model: Model, { where, dryRun }: { where: any; dryRun: boolea
148
151
  if (!toUnlink[descendantModel.name]) {
149
152
  toUnlink[descendantModel.name] = {};
150
153
  }
151
- if (!toUnlink[descendantModel.name]![descendant.id]) {
152
- toUnlink[descendantModel.name]![descendant.id] = {
154
+ if (!toUnlink[descendantModel.name][descendant.id]) {
155
+ toUnlink[descendantModel.name][descendant.id] = {
153
156
  display: descendant[descendantModel.displayField || 'id'] || entity.id,
154
157
  fields: [],
155
158
  };
156
159
  }
157
- toUnlink[descendantModel.name]![descendant.id]!.fields.push(name);
160
+ toUnlink[descendantModel.name][descendant.id].fields.push(name);
158
161
  } else {
159
162
  mutations.push(async () => {
160
163
  await ctx
@@ -225,7 +228,7 @@ const restore = async (model: Model, { where }: { where: any }, ctx: FullContext
225
228
  const data = { prev: relatedEntity, input: {}, normalizedInput, next: { ...relatedEntity, ...normalizedInput } };
226
229
  if (ctx.mutationHook) {
227
230
  beforeHooks.push(async () => {
228
- await ctx.mutationHook!(model, 'restore', 'before', data, ctx);
231
+ await ctx.mutationHook(model, 'restore', 'before', data, ctx);
229
232
  });
230
233
  }
231
234
  mutations.push(async () => {
@@ -234,7 +237,7 @@ const restore = async (model: Model, { where }: { where: any }, ctx: FullContext
234
237
  });
235
238
  if (ctx.mutationHook) {
236
239
  afterHooks.push(async () => {
237
- await ctx.mutationHook!(model, 'restore', 'after', data, ctx);
240
+ await ctx.mutationHook(model, 'restore', 'after', data, ctx);
238
241
  });
239
242
  }
240
243
 
@@ -132,7 +132,7 @@ export const getFragmentSpreads = (node: ResolverNode) =>
132
132
  node.selectionSet.filter(isFragmentSpreadNode).map((subNode) =>
133
133
  getResolverNode({
134
134
  ctx: node.ctx,
135
- node: node.ctx.info.fragments[subNode.name.value]!,
135
+ node: node.ctx.info.fragments[subNode.name.value],
136
136
  tableAlias: node.tableAlias,
137
137
  baseTypeDefinition: node.baseTypeDefinition,
138
138
  typeName: node.model.name,
@@ -165,7 +165,7 @@ const applySubQueries = async (
165
165
  if (!entriesById[entry[ID_ALIAS]]) {
166
166
  entriesById[entry[ID_ALIAS]] = [];
167
167
  }
168
- entriesById[entry[ID_ALIAS]!]!.push(entry);
168
+ entriesById[entry[ID_ALIAS]].push(entry);
169
169
  }
170
170
  const ids = Object.keys(entriesById);
171
171
 
@@ -189,7 +189,7 @@ const applySubQueries = async (
189
189
  const children = hydrate(subNode, rawChildren);
190
190
 
191
191
  for (const child of children) {
192
- for (const entry of entriesById[child[foreignKey] as string]!) {
192
+ for (const entry of entriesById[child[foreignKey] as string]) {
193
193
  if (isList) {
194
194
  (entry[fieldName] as Entry[]).push(cloneDeep(child));
195
195
  } else {
@@ -72,13 +72,13 @@ export function hydrate<T extends Entry>(
72
72
  outer: for (const [column, value] of Object.entries(entry)) {
73
73
  let current = res;
74
74
  const shortParts = column.split('__');
75
- const fieldName = shortParts.pop()!;
75
+ const fieldName = shortParts.pop();
76
76
  const columnWithoutField = shortParts.join('__');
77
77
  const longColumn = node.ctx.aliases.getLong(columnWithoutField);
78
78
  const longColumnWithoutRoot = longColumn.replace(new RegExp(`^${tableAlias}(__)?`), '');
79
79
  const allParts = [tableAlias, ...(longColumnWithoutRoot ? longColumnWithoutRoot.split('__') : []), fieldName];
80
80
  for (let i = 0; i < allParts.length - 1; i++) {
81
- const part = allParts[i]!;
81
+ const part = allParts[i];
82
82
 
83
83
  if (!current[part]) {
84
84
  const idField = [node.ctx.aliases.getShort(allParts.slice(0, i + 1).join('__')), ID_ALIAS].join('__');
@@ -0,0 +1,18 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`delete works with self-referential entities 1`] = `
4
+ {
5
+ "deleteAnotherObject": "226a20e8-5c18-4423-99ca-eb0df6ff4fdd",
6
+ }
7
+ `;
8
+
9
+ exports[`delete works with self-referential entities 2`] = `
10
+ {
11
+ "anotherObjects": [
12
+ {
13
+ "deleted": true,
14
+ "id": "226a20e8-5c18-4423-99ca-eb0df6ff4fdd",
15
+ },
16
+ ],
17
+ }
18
+ `;
@@ -0,0 +1,22 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`query can be executed 1`] = `
4
+ {
5
+ "manyObjects": [
6
+ {
7
+ "another": {
8
+ "id": "226a20e8-5c18-4423-99ca-eb0df6ff4fdd",
9
+ "manyObjects": [
10
+ {
11
+ "field": null,
12
+ "id": "604ab55d-ec3e-4857-9f27-219158f80e64",
13
+ },
14
+ ],
15
+ },
16
+ "field": null,
17
+ "id": "604ab55d-ec3e-4857-9f27-219158f80e64",
18
+ "xyz": 1,
19
+ },
20
+ ],
21
+ }
22
+ `;
@@ -0,0 +1,28 @@
1
+ import { gql } from '../../src';
2
+ import { ANOTHER_ID } from '../utils/database/seed';
3
+ import { withServer } from '../utils/server';
4
+
5
+ describe('delete', () => {
6
+ it('works with self-referential entities', async () => {
7
+ await withServer(async (request) => {
8
+ expect(
9
+ await request(gql`
10
+ mutation DeleteAnotherObject {
11
+ deleteAnotherObject(where: { id: "${ANOTHER_ID}" })
12
+ }
13
+ `)
14
+ ).toMatchSnapshot();
15
+
16
+ expect(
17
+ await request(gql`
18
+ query GetAnotherObject {
19
+ anotherObjects(where: { id: "${ANOTHER_ID}", deleted: true }) {
20
+ id
21
+ deleted
22
+ }
23
+ }
24
+ `)
25
+ ).toMatchSnapshot();
26
+ });
27
+ });
28
+ });
@@ -0,0 +1,28 @@
1
+ import { gql } from '../../src';
2
+ import { ANOTHER_ID, SOME_ID } from '../utils/database/seed';
3
+ import { withServer } from '../utils/server';
4
+
5
+ describe('query', () => {
6
+ it('can be executed', async () => {
7
+ await withServer(async (request) => {
8
+ expect(
9
+ await request(gql`
10
+ query SomeQuery {
11
+ manyObjects(where: { another: { id: "${ANOTHER_ID}" } }, orderBy: [{ xyz: DESC }]) {
12
+ id
13
+ field
14
+ xyz
15
+ another {
16
+ id
17
+ manyObjects(where: { id: "${SOME_ID}" }) {
18
+ id
19
+ field
20
+ }
21
+ }
22
+ }
23
+ }
24
+ `)
25
+ ).toMatchSnapshot();
26
+ });
27
+ });
28
+ });
@@ -3,11 +3,21 @@
3
3
  exports[`generate generates a schema 1`] = `
4
4
  "type AnotherObject {
5
5
  id: ID!
6
+ myself: AnotherObject
7
+ deleted: Boolean!
8
+ deletedAt: DateTime
9
+ deletedBy: User
10
+ self(where: AnotherObjectWhere, orderBy: [AnotherObjectOrderBy!], limit: Int, offset: Int): AnotherObject
6
11
  manyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
7
12
  }
8
13
 
14
+ input AnotherObjectOrderBy {
15
+ deletedAt: Order
16
+ }
17
+
9
18
  input AnotherObjectWhere {
10
19
  id: [ID!]
20
+ deleted: [Boolean!] = false
11
21
  }
12
22
 
13
23
  input AnotherObjectWhereUnique {
@@ -21,6 +31,8 @@ input CreateSomeObject {
21
31
  scalar DateTime
22
32
 
23
33
  type Mutation {
34
+ deleteAnotherObject(where: AnotherObjectWhereUnique!, dryRun: Boolean): ID!
35
+ restoreAnotherObject(where: AnotherObjectWhereUnique!): ID!
24
36
  createSomeObject(data: CreateSomeObject!): SomeObject!
25
37
  updateSomeObject(where: SomeObjectWhereUnique!, data: UpdateSomeObject!): SomeObject!
26
38
  deleteSomeObject(where: SomeObjectWhereUnique!, dryRun: Boolean): ID!
@@ -35,9 +47,15 @@ enum Order {
35
47
  type Query {
36
48
  me: User
37
49
  someObject(where: SomeObjectWhereUnique!): SomeObject!
50
+ anotherObjects(where: AnotherObjectWhere, orderBy: [AnotherObjectOrderBy!], limit: Int, offset: Int): [AnotherObject!]!
38
51
  manyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
39
52
  }
40
53
 
54
+ enum Role {
55
+ ADMIN
56
+ USER
57
+ }
58
+
41
59
  enum SomeEnum {
42
60
  A
43
61
  B
@@ -89,6 +107,8 @@ scalar Upload
89
107
  type User {
90
108
  id: ID!
91
109
  username: String
110
+ role: Role
111
+ deletedAnotherObjects(where: AnotherObjectWhere, orderBy: [AnotherObjectOrderBy!], limit: Int, offset: Int): [AnotherObject!]!
92
112
  createdManyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
93
113
  updatedManyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
94
114
  deletedManyObjects(where: SomeObjectWhere, search: String, orderBy: [SomeObjectOrderBy!], limit: Int, offset: Int): [SomeObject!]!
@@ -4,11 +4,14 @@ exports[`resolvers are generated correctly 1`] = `
4
4
  {
5
5
  "Mutation": {
6
6
  "createSomeObject": [Function],
7
+ "deleteAnotherObject": [Function],
7
8
  "deleteSomeObject": [Function],
9
+ "restoreAnotherObject": [Function],
8
10
  "restoreSomeObject": [Function],
9
11
  "updateSomeObject": [Function],
10
12
  },
11
13
  "Query": {
14
+ "anotherObjects": [Function],
12
15
  "manyObjects": [Function],
13
16
  "me": [Function],
14
17
  "someObject": [Function],
@@ -1,5 +1,5 @@
1
- import { printSchemaFromModels } from '../../src/generate';
2
- import { rawModels } from './utils';
1
+ import { printSchemaFromModels } from '../../src';
2
+ import { rawModels } from '../utils/models';
3
3
 
4
4
  describe('generate', () => {
5
5
  it('generates a schema', () => {
@@ -1,12 +1,12 @@
1
1
  import { makeExecutableSchema } from '@graphql-tools/schema';
2
2
  import { execute, parse, Source } from 'graphql';
3
3
  import knex from 'knex';
4
+ import { DateTime } from 'luxon';
4
5
  import { gql } from '../../src/client/gql';
5
6
  import { Context } from '../../src/context';
6
7
  import { generate } from '../../src/generate';
7
8
  import { getResolvers } from '../../src/resolvers';
8
- import { models, permissions, rawModels } from './utils';
9
- import { DateTime } from 'luxon';
9
+ import { models, permissions, rawModels } from '../utils/models';
10
10
 
11
11
  const test = async (operationName: string, query: string, variableValues: object, responses: unknown[]) => {
12
12
  const knexInstance = knex({
@@ -0,0 +1,19 @@
1
+ import knex from 'knex';
2
+
3
+ export const getKnex = (database = 'postgres') =>
4
+ knex({
5
+ client: 'postgresql',
6
+ connection: {
7
+ host: 'localhost',
8
+ database,
9
+ user: 'postgres',
10
+ password: 'password',
11
+ },
12
+ migrations: {
13
+ tableName: 'knex_migrations',
14
+ },
15
+ pool: {
16
+ min: 0,
17
+ max: 30,
18
+ },
19
+ });
@@ -0,0 +1,62 @@
1
+ import { Knex } from 'knex';
2
+
3
+ export const setupSchema = async (knex: Knex) => {
4
+ await knex.raw(`CREATE TYPE "someEnum" AS ENUM ('A','B','C')`);
5
+
6
+ await knex.raw(`CREATE TYPE "role" AS ENUM ('ADMIN','USER')`);
7
+
8
+ await knex.schema.createTable('User', (table) => {
9
+ table.uuid('id').notNullable().primary();
10
+ table.string('username', undefined).nullable();
11
+ table
12
+ .enum('role', null as any, {
13
+ useNative: true,
14
+ existingType: true,
15
+ enumName: 'role',
16
+ })
17
+ .nullable();
18
+ });
19
+
20
+ await knex.schema.createTable('AnotherObject', (table) => {
21
+ table.uuid('id').notNullable().primary();
22
+ table.uuid('myselfId').notNullable();
23
+ table.foreign('myselfId').references('id').inTable('AnotherObject');
24
+ table.boolean('deleted').notNullable().defaultTo(false);
25
+ table.timestamp('deletedAt').nullable();
26
+ table.uuid('deletedById').nullable();
27
+ table.foreign('deletedById').references('id').inTable('User');
28
+ });
29
+
30
+ await knex.schema.createTable('AnotherObjectRevision', (table) => {
31
+ table.uuid('id').notNullable().primary();
32
+ table.boolean('deleted').notNullable();
33
+ });
34
+
35
+ await knex.schema.createTable('SomeObject', (table) => {
36
+ table.uuid('id').notNullable().primary();
37
+ table.string('field', undefined).nullable();
38
+ table.uuid('anotherId').notNullable();
39
+ table.foreign('anotherId').references('id').inTable('AnotherObject');
40
+ table.decimal('list', 1, 1).notNullable();
41
+ table.integer('xyz').notNullable();
42
+ table.timestamp('createdAt').notNullable();
43
+ table.uuid('createdById').notNullable();
44
+ table.foreign('createdById').references('id').inTable('User');
45
+ table.timestamp('updatedAt').notNullable();
46
+ table.uuid('updatedById').notNullable();
47
+ table.foreign('updatedById').references('id').inTable('User');
48
+ table.boolean('deleted').notNullable().defaultTo(false);
49
+ table.timestamp('deletedAt').nullable();
50
+ table.uuid('deletedById').nullable();
51
+ table.foreign('deletedById').references('id').inTable('User');
52
+ });
53
+
54
+ await knex.schema.createTable('SomeObjectRevision', (table) => {
55
+ table.uuid('id').notNullable().primary();
56
+ table.uuid('someObjectId').notNullable();
57
+ table.uuid('createdById').notNullable();
58
+ table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now(0));
59
+ table.boolean('deleted').notNullable();
60
+ table.integer('xyz').notNullable();
61
+ });
62
+ };
@@ -0,0 +1,54 @@
1
+ import { Knex } from 'knex';
2
+ import { DateTime } from 'luxon';
3
+ import { summonByName } from '../../../src';
4
+ import { models } from '../models';
5
+
6
+ export const ADMIN_ID = '04e45b48-04cf-4b38-bb25-b9af5ae0b2c4';
7
+
8
+ export const SOME_ID = '604ab55d-ec3e-4857-9f27-219158f80e64';
9
+ export const ANOTHER_ID = '226a20e8-5c18-4423-99ca-eb0df6ff4fdd';
10
+
11
+ export const seed = {
12
+ User: [
13
+ {
14
+ id: ADMIN_ID,
15
+ username: 'admin',
16
+ role: 'ADMIN',
17
+ },
18
+ ],
19
+ AnotherObject: [
20
+ {
21
+ id: ANOTHER_ID,
22
+ myselfId: ANOTHER_ID,
23
+ },
24
+ ],
25
+ SomeObject: [
26
+ {
27
+ id: SOME_ID,
28
+ anotherId: ANOTHER_ID,
29
+ list: 0,
30
+ xyz: 1,
31
+ },
32
+ ],
33
+ };
34
+
35
+ export const setupSeed = async (knex: Knex) => {
36
+ const now = DateTime.now();
37
+ for (const [table, entities] of Object.entries(seed)) {
38
+ const model = summonByName(models, table);
39
+ await knex.batchInsert(
40
+ table,
41
+ entities.map((entity, i) => ({
42
+ ...entity,
43
+ ...(model.creatable && {
44
+ createdAt: now.plus({ second: i }),
45
+ createdById: ADMIN_ID,
46
+ }),
47
+ ...(model.updatable && {
48
+ updatedAt: now.plus({ second: i }),
49
+ updatedById: ADMIN_ID,
50
+ }),
51
+ }))
52
+ );
53
+ }
54
+ };
@@ -0,0 +1,35 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { simpleGit } from 'simple-git';
3
+ import { MigrationGenerator } from '../../src/migrations/generate';
4
+ import { getKnex } from './database/knex';
5
+ import { rawModels } from './models';
6
+
7
+ const git = simpleGit();
8
+
9
+ const getDate = () => {
10
+ const date = new Date();
11
+ const year = date.getFullYear();
12
+ const month = String(date.getMonth() + 1).padStart(2, '0');
13
+ const day = String(date.getDate()).padStart(2, '0');
14
+ const hours = String(date.getHours()).padStart(2, '0');
15
+ const minutes = String(date.getMinutes()).padStart(2, '0');
16
+ const seconds = String(date.getSeconds()).padStart(2, '0');
17
+
18
+ return `${year}${month}${day}${hours}${minutes}${seconds}`;
19
+ };
20
+
21
+ const writeMigration = async () => {
22
+ const name = process.argv[2] || (await git.branch()).current.split('/').pop();
23
+
24
+ const knex = getKnex();
25
+
26
+ try {
27
+ const migrations = await new MigrationGenerator(knex, rawModels).generate();
28
+
29
+ writeFileSync(`tmp/${getDate()}_${name}.ts`, migrations);
30
+ } finally {
31
+ await knex.destroy();
32
+ }
33
+ };
34
+
35
+ void writeMigration();
@@ -8,6 +8,11 @@ export const rawModels: RawModels = [
8
8
  type: 'enum',
9
9
  values: ['A', 'B', 'C'],
10
10
  },
11
+ {
12
+ name: 'Role',
13
+ type: 'enum',
14
+ values: ['ADMIN', 'USER'],
15
+ },
11
16
 
12
17
  {
13
18
  name: 'SomeRawObject',
@@ -23,6 +28,25 @@ export const rawModels: RawModels = [
23
28
  name: 'username',
24
29
  type: 'String',
25
30
  },
31
+ {
32
+ name: 'role',
33
+ type: 'Role',
34
+ },
35
+ ],
36
+ },
37
+ {
38
+ type: 'object',
39
+ name: 'AnotherObject',
40
+ listQueriable: true,
41
+ deletable: true,
42
+ fields: [
43
+ {
44
+ type: 'AnotherObject',
45
+ name: 'myself',
46
+ toOne: true,
47
+ reverse: 'self',
48
+ relation: true
49
+ }
26
50
  ],
27
51
  },
28
52
  {
@@ -51,6 +75,8 @@ export const rawModels: RawModels = [
51
75
  {
52
76
  name: 'list',
53
77
  type: 'Float',
78
+ scale: 1,
79
+ precision: 1,
54
80
  nonNull: true,
55
81
  list: true,
56
82
  args: [{ name: 'magic', type: 'Boolean' }],
@@ -66,11 +92,6 @@ export const rawModels: RawModels = [
66
92
  },
67
93
  ],
68
94
  },
69
- {
70
- type: 'object',
71
- name: 'AnotherObject',
72
- fields: [],
73
- },
74
95
  ];
75
96
 
76
97
  export const models = getModels(rawModels);