@contember/engine-system-api 1.4.0-alpha.1 → 1.4.0-alpha.3

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 (118) hide show
  1. package/dist/src/SystemContainer.d.ts +3 -2
  2. package/dist/src/SystemContainer.d.ts.map +1 -1
  3. package/dist/src/SystemContainer.js +3 -2
  4. package/dist/src/SystemContainer.js.map +1 -1
  5. package/dist/src/bin/runMigrations.js +1 -1
  6. package/dist/src/bin/runMigrations.js.map +1 -1
  7. package/dist/src/index.d.ts +2 -2
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js +1 -2
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/migrations/2023-10-24-140000-schema-nullable-checksum.d.ts +3 -0
  12. package/dist/src/migrations/2023-10-24-140000-schema-nullable-checksum.d.ts.map +1 -0
  13. package/dist/src/migrations/2023-10-24-140000-schema-nullable-checksum.js +12 -0
  14. package/dist/src/migrations/2023-10-24-140000-schema-nullable-checksum.js.map +1 -0
  15. package/dist/src/migrations/runner.d.ts +1 -2
  16. package/dist/src/migrations/runner.d.ts.map +1 -1
  17. package/dist/src/migrations/runner.js +6 -7
  18. package/dist/src/migrations/runner.js.map +1 -1
  19. package/dist/src/migrations/snapshot.d.ts.map +1 -1
  20. package/dist/src/migrations/snapshot.js +7 -2
  21. package/dist/src/migrations/snapshot.js.map +1 -1
  22. package/dist/src/model/authorization/AuthorizationActions.d.ts +5 -0
  23. package/dist/src/model/authorization/AuthorizationActions.d.ts.map +1 -1
  24. package/dist/src/model/authorization/AuthorizationActions.js +1 -0
  25. package/dist/src/model/authorization/AuthorizationActions.js.map +1 -1
  26. package/dist/src/model/authorization/PermissionsFactory.d.ts.map +1 -1
  27. package/dist/src/model/authorization/PermissionsFactory.js +1 -0
  28. package/dist/src/model/authorization/PermissionsFactory.js.map +1 -1
  29. package/dist/src/model/commands/migrations/SaveMigrationCommand.d.ts +4 -4
  30. package/dist/src/model/commands/migrations/SaveMigrationCommand.d.ts.map +1 -1
  31. package/dist/src/model/commands/migrations/SaveMigrationCommand.js +11 -3
  32. package/dist/src/model/commands/migrations/SaveMigrationCommand.js.map +1 -1
  33. package/dist/src/model/dependencies/content/ContentQueryExecutor.d.ts +35 -0
  34. package/dist/src/model/dependencies/content/ContentQueryExecutor.d.ts.map +1 -0
  35. package/dist/src/model/dependencies/content/ContentQueryExecutor.js +3 -0
  36. package/dist/src/model/dependencies/content/ContentQueryExecutor.js.map +1 -0
  37. package/dist/src/model/dependencies/index.d.ts +1 -0
  38. package/dist/src/model/dependencies/index.d.ts.map +1 -1
  39. package/dist/src/model/dependencies/index.js +1 -0
  40. package/dist/src/model/dependencies/index.js.map +1 -1
  41. package/dist/src/model/dtos/ExecutedMigration.d.ts +3 -2
  42. package/dist/src/model/dtos/ExecutedMigration.d.ts.map +1 -1
  43. package/dist/src/model/dtos/ExecutedMigration.js +2 -1
  44. package/dist/src/model/dtos/ExecutedMigration.js.map +1 -1
  45. package/dist/src/model/migrations/MigrationInput.d.ts +18 -0
  46. package/dist/src/model/migrations/MigrationInput.d.ts.map +1 -0
  47. package/dist/src/model/migrations/MigrationInput.js +3 -0
  48. package/dist/src/model/migrations/MigrationInput.js.map +1 -0
  49. package/dist/src/model/migrations/ProjectMigrator.d.ts +28 -6
  50. package/dist/src/model/migrations/ProjectMigrator.d.ts.map +1 -1
  51. package/dist/src/model/migrations/ProjectMigrator.js +80 -22
  52. package/dist/src/model/migrations/ProjectMigrator.js.map +1 -1
  53. package/dist/src/model/migrations/SchemaVersionBuilder.d.ts.map +1 -1
  54. package/dist/src/model/migrations/SchemaVersionBuilder.js +10 -5
  55. package/dist/src/model/migrations/SchemaVersionBuilder.js.map +1 -1
  56. package/dist/src/model/queries/migrations/ExecutedMigrationsQueryHelper.d.ts +2 -1
  57. package/dist/src/model/queries/migrations/ExecutedMigrationsQueryHelper.d.ts.map +1 -1
  58. package/dist/src/model/queries/migrations/ExecutedMigrationsQueryHelper.js +2 -1
  59. package/dist/src/model/queries/migrations/ExecutedMigrationsQueryHelper.js.map +1 -1
  60. package/dist/src/resolvers/SystemResolverContext.d.ts +2 -2
  61. package/dist/src/resolvers/SystemResolverContext.d.ts.map +1 -1
  62. package/dist/src/resolvers/SystemResolverContext.js.map +1 -1
  63. package/dist/src/resolvers/mutation/MigrateMutationResolver.d.ts +1 -0
  64. package/dist/src/resolvers/mutation/MigrateMutationResolver.d.ts.map +1 -1
  65. package/dist/src/resolvers/mutation/MigrateMutationResolver.js +62 -4
  66. package/dist/src/resolvers/mutation/MigrateMutationResolver.js.map +1 -1
  67. package/dist/src/resolvers/query/ExecutedMigrationsQueryResolver.d.ts.map +1 -1
  68. package/dist/src/resolvers/query/ExecutedMigrationsQueryResolver.js +4 -0
  69. package/dist/src/resolvers/query/ExecutedMigrationsQueryResolver.js.map +1 -1
  70. package/dist/src/schema/system.graphql.d.ts.map +1 -1
  71. package/dist/src/schema/system.graphql.js +30 -4
  72. package/dist/src/schema/system.graphql.js.map +1 -1
  73. package/dist/src/schema/types.d.ts +170 -106
  74. package/dist/src/schema/types.d.ts.map +1 -1
  75. package/dist/src/schema/types.js +8 -1
  76. package/dist/src/schema/types.js.map +1 -1
  77. package/dist/src/tsconfig.tsbuildinfo +1 -1
  78. package/dist/src/types.d.ts +1 -0
  79. package/dist/src/types.d.ts.map +1 -1
  80. package/dist/tests/cases/migrations/2020-05-06-150000-composed-primary.test.js +1 -0
  81. package/dist/tests/cases/migrations/2020-05-06-150000-composed-primary.test.js.map +1 -1
  82. package/dist/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.js +1 -0
  83. package/dist/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.js.map +1 -1
  84. package/dist/tests/cases/migrations/2022-10-03-110000-table-on-delete.test.js +1 -0
  85. package/dist/tests/cases/migrations/2022-10-03-110000-table-on-delete.test.js.map +1 -1
  86. package/dist/tests/cases/migrations/2023-09-11-174000-fix-on-delete.test.js +2 -0
  87. package/dist/tests/cases/migrations/2023-09-11-174000-fix-on-delete.test.js.map +1 -1
  88. package/dist/tests/cases/migrations/2023-10-19-173000-fix-unique.test.js +1 -0
  89. package/dist/tests/cases/migrations/2023-10-19-173000-fix-unique.test.js.map +1 -1
  90. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +22 -22
  92. package/src/SystemContainer.ts +3 -1
  93. package/src/bin/runMigrations.ts +1 -2
  94. package/src/index.ts +5 -1
  95. package/src/migrations/2023-10-24-140000-schema-nullable-checksum.ts +11 -0
  96. package/src/migrations/runner.ts +10 -6
  97. package/src/migrations/snapshot.ts +7 -2
  98. package/src/model/authorization/AuthorizationActions.ts +1 -0
  99. package/src/model/authorization/PermissionsFactory.ts +1 -0
  100. package/src/model/commands/migrations/SaveMigrationCommand.ts +20 -7
  101. package/src/model/dependencies/content/ContentQueryExecutor.ts +27 -0
  102. package/src/model/dependencies/index.ts +1 -0
  103. package/src/model/dtos/ExecutedMigration.ts +3 -1
  104. package/src/model/migrations/MigrationInput.ts +25 -0
  105. package/src/model/migrations/ProjectMigrator.ts +113 -47
  106. package/src/model/migrations/SchemaVersionBuilder.ts +10 -5
  107. package/src/model/queries/migrations/ExecutedMigrationsQueryHelper.ts +3 -1
  108. package/src/resolvers/SystemResolverContext.ts +2 -2
  109. package/src/resolvers/mutation/MigrateMutationResolver.ts +61 -5
  110. package/src/resolvers/query/ExecutedMigrationsQueryResolver.ts +6 -1
  111. package/src/schema/system.graphql.ts +30 -4
  112. package/src/schema/types.ts +145 -106
  113. package/src/types.ts +1 -0
  114. package/tests/cases/migrations/2020-05-06-150000-composed-primary.test.ts +1 -0
  115. package/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.ts +1 -0
  116. package/tests/cases/migrations/2022-10-03-110000-table-on-delete.test.ts +1 -0
  117. package/tests/cases/migrations/2023-09-11-174000-fix-on-delete.test.ts +2 -0
  118. package/tests/cases/migrations/2023-10-19-173000-fix-unique.test.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-system-api",
3
- "version": "1.4.0-alpha.1",
3
+ "version": "1.4.0-alpha.3",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
@@ -13,29 +13,29 @@
13
13
  "test": "vitest --dir ./tests/cases"
14
14
  },
15
15
  "dependencies": {
16
- "@contember/authorization": "1.4.0-alpha.1",
17
- "@contember/database": "1.4.0-alpha.1",
18
- "@contember/database-migrations": "1.4.0-alpha.1",
19
- "@contember/dic": "1.4.0-alpha.1",
20
- "@contember/graphql-utils": "1.4.0-alpha.1",
21
- "@contember/logger": "1.4.0-alpha.1",
22
- "@contember/queryable": "1.4.0-alpha.1",
23
- "@contember/schema": "1.4.0-alpha.1",
24
- "@contember/schema-migrations": "1.4.0-alpha.1",
25
- "@contember/schema-utils": "1.4.0-alpha.1",
26
- "@graphql-tools/utils": "^10.0.7",
27
- "graphql-tag": "^2.12.5"
16
+ "@contember/authorization": "1.4.0-alpha.3",
17
+ "@contember/database": "1.4.0-alpha.3",
18
+ "@contember/database-migrations": "1.4.0-alpha.3",
19
+ "@contember/dic": "1.4.0-alpha.3",
20
+ "@contember/graphql-utils": "1.4.0-alpha.3",
21
+ "@contember/logger": "1.4.0-alpha.3",
22
+ "@contember/queryable": "1.4.0-alpha.3",
23
+ "@contember/schema": "1.4.0-alpha.3",
24
+ "@contember/schema-migrations": "1.4.0-alpha.3",
25
+ "@contember/schema-utils": "1.4.0-alpha.3",
26
+ "@graphql-tools/utils": "^10.0.8",
27
+ "graphql-tag": "^2.12.6"
28
28
  },
29
29
  "devDependencies": {
30
- "@contember/engine-api-tester": "1.4.0-alpha.1",
31
- "@contember/schema-definition": "1.4.0-alpha.1",
32
- "@graphql-codegen/cli": "^2.6.2",
33
- "@graphql-codegen/typescript": "^2.5.1",
34
- "@graphql-codegen/typescript-operations": "^2.4.2",
35
- "@graphql-codegen/typescript-resolvers": "^2.6.6",
36
- "@types/node": "^18",
37
- "graphql": "^16.5.0",
38
- "pg": "^8.9.0"
30
+ "@contember/engine-api-tester": "1.4.0-alpha.3",
31
+ "@contember/schema-definition": "1.4.0-alpha.3",
32
+ "@graphql-codegen/cli": "^5.0.0",
33
+ "@graphql-codegen/typescript": "^4.0.1",
34
+ "@graphql-codegen/typescript-operations": "^4.0.1",
35
+ "@graphql-codegen/typescript-resolvers": "^4.0.1",
36
+ "@types/node": "^20.9.0",
37
+ "graphql": "^16.8.1",
38
+ "pg": "^8.11.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "graphql": ">= 14.6.0",
@@ -7,6 +7,7 @@ import {
7
7
  SchemaMigrator,
8
8
  } from '@contember/schema-migrations'
9
9
  import {
10
+ ContentQueryExecutor,
10
11
  EventResponseBuilder,
11
12
  ExecutedMigrationsResolver,
12
13
  IdentityFetcher,
@@ -46,6 +47,7 @@ export class SystemContainerFactory {
46
47
  constructor(
47
48
  private readonly providers: UuidProvider,
48
49
  private readonly modificationHandlerFactory: ModificationHandlerFactory,
50
+ private readonly contentQueryExecutor: ContentQueryExecutor,
49
51
  ) {
50
52
  }
51
53
 
@@ -86,7 +88,7 @@ export class SystemContainerFactory {
86
88
  .addService('migrationsDatabaseMetadataResolverStoreFactory', ({ databaseMetadataResolver }) =>
87
89
  new MigrationsDatabaseMetadataResolverStoreFactory(databaseMetadataResolver))
88
90
  .addService('projectMigrator', ({ migrationDescriber, schemaVersionBuilder, executedMigrationsResolver, migrationsDatabaseMetadataResolverStoreFactory }) =>
89
- new ProjectMigrator(migrationDescriber, schemaVersionBuilder, executedMigrationsResolver, migrationsDatabaseMetadataResolverStoreFactory))
91
+ new ProjectMigrator(migrationDescriber, schemaVersionBuilder, executedMigrationsResolver, migrationsDatabaseMetadataResolverStoreFactory, this.contentQueryExecutor))
90
92
  .addService('projectTruncateExecutor', () =>
91
93
  new ProjectTruncateExecutor())
92
94
  .addService('migrationAlterer', () =>
@@ -18,8 +18,7 @@ import { createLogger, PrettyPrintLoggerHandler } from '@contember/logger'
18
18
  throw new Error()
19
19
  },
20
20
  }),
21
- { db: dbConfig, slug: 'test', stages: [] },
22
- 'system',
21
+ { db: dbConfig, slug: 'test', stages: [], systemSchema: 'system' },
23
22
  {
24
23
  buildSchema: () => Promise.resolve(emptyVersionedSchema),
25
24
  } as unknown as SchemaVersionBuilder,
package/src/index.ts CHANGED
@@ -1,8 +1,12 @@
1
- export { SchemaMigrator, MigrationsResolver } from '@contember/schema-migrations'
1
+ export { SchemaMigrator } from '@contember/schema-migrations'
2
2
 
3
3
  export { typeDefs, devTypeDefs, Schema } from './schema'
4
4
 
5
5
  export {
6
+ ContentQueryExecutor,
7
+ ContentQueryExecutorContext,
8
+ ContentQueryExecutorQuery,
9
+ ContentQueryExecutorResult,
6
10
  DatabaseContext,
7
11
  DatabaseContextFactory,
8
12
  Command,
@@ -0,0 +1,11 @@
1
+ import { MigrationBuilder } from '@contember/database-migrations'
2
+
3
+ const sql = `
4
+ CREATE TYPE SCHEMA_MIGRATION_TYPE AS ENUM('schema', 'content');
5
+ ALTER TABLE schema_migration ALTER checksum DROP NOT NULL,
6
+ ADD COLUMN type SCHEMA_MIGRATION_TYPE NOT NULL DEFAULT 'schema';
7
+ `
8
+
9
+ export default async function (builder: MigrationBuilder) {
10
+ builder.sql(sql)
11
+ }
@@ -1,4 +1,9 @@
1
- import { MigrationGroup, MigrationsRunner as DbMigrationsRunner } from '@contember/database-migrations'
1
+ import {
2
+ GroupMigrationsResolver,
3
+ MigrationGroup,
4
+ MigrationsRunner as DbMigrationsRunner,
5
+ SnapshotMigrationResolver,
6
+ } from '@contember/database-migrations'
2
7
  import _20180804102200init from './2018-08-04-102200-init'
3
8
  import _20180805130501triggereventfunction from './2018-08-05-130501-trigger-event-function'
4
9
  import _20190114143432eventtrxid from './2019-01-14-143432-event-trx-id'
@@ -19,14 +24,13 @@ import _20220208144400dynamicstageschema from './2022-02-08-144400-dynamic-stage
19
24
  import _20221003110000tableondelete from './2022-10-03-110000-table-on-delete'
20
25
  import _20230911174000fixondelete from './2023-09-11-174000-fix-on-delete'
21
26
  import _20231019173000fixunique from './2023-10-19-173000-fix-unique'
27
+ import _20231024140000schemanullablechecksum from './2023-10-24-140000-schema-nullable-checksum'
22
28
  import snapshot from './snapshot'
23
29
 
24
- import { Client, Connection, createDatabaseIfNotExists, DatabaseConfig, DatabaseMetadataResolver } from '@contember/database'
30
+ import { Connection, createDatabaseIfNotExists, DatabaseConfig, DatabaseMetadataResolver } from '@contember/database'
25
31
  import { DatabaseContextFactory, SchemaVersionBuilder } from '../model'
26
32
  import { ProjectConfig } from '../types'
27
33
  import { Logger } from '@contember/logger'
28
- import { GroupMigrationsResolver } from '@contember/database-migrations'
29
- import { SnapshotMigrationResolver } from '@contember/database-migrations'
30
34
 
31
35
  const migrations = {
32
36
  '2018-08-04-102200-init': _20180804102200init,
@@ -49,6 +53,7 @@ const migrations = {
49
53
  '2022-10-03-110000-table-on-delete': _20221003110000tableondelete,
50
54
  '2023-09-11-174000-fix-on-delete': _20230911174000fixondelete,
51
55
  '2023-10-19-173000-fix-unique': _20231019173000fixunique,
56
+ '2023-10-24-140000-schema-nullable-checksum': _20231024140000schemanullablechecksum,
52
57
  }
53
58
 
54
59
 
@@ -56,7 +61,6 @@ export class SystemMigrationsRunner {
56
61
  constructor(
57
62
  private readonly databaseContextFactory: DatabaseContextFactory,
58
63
  private readonly project: ProjectConfig & { db: DatabaseConfig },
59
- private readonly schema: string,
60
64
  private readonly schemaVersionBuilder: SchemaVersionBuilder,
61
65
  private readonly migrationGroups: Record<string, MigrationGroup<unknown>>,
62
66
  private readonly databaseMetadataResolver: DatabaseMetadataResolver,
@@ -84,7 +88,7 @@ export class SystemMigrationsRunner {
84
88
  ]),
85
89
  ),
86
90
  )
87
- const migrationsRunner = new DbMigrationsRunner(connection, this.schema, migrationResolver)
91
+ const migrationsRunner = new DbMigrationsRunner(connection, this.project.systemSchema, migrationResolver)
88
92
  await migrationsRunner.migrate(message => logger.warn(message), {
89
93
  project: this.project,
90
94
  schemaResolver,
@@ -4,6 +4,10 @@ CREATE DOMAIN "event_data_type" AS "text"
4
4
  CONSTRAINT "event_data_type_check" CHECK ((VALUE = ANY (ARRAY['create'::"text", 'update'::"text", 'delete'::"text"])));
5
5
  CREATE DOMAIN "event_type" AS "text"
6
6
  CONSTRAINT "event_type_check" CHECK ((VALUE = ANY (ARRAY['init'::"text", 'create'::"text", 'update'::"text", 'delete'::"text", 'run_migration'::"text"])));
7
+ CREATE TYPE "schema_migration_type" AS ENUM (
8
+ 'schema',
9
+ 'content'
10
+ );
7
11
  CREATE FUNCTION "trigger_event"() RETURNS "trigger"
8
12
  LANGUAGE "plpgsql"
9
13
  AS $$
@@ -116,8 +120,9 @@ CREATE TABLE "schema_migration" (
116
120
  "version" character varying(20) NOT NULL,
117
121
  "name" character varying(255) NOT NULL,
118
122
  "migration" "json" NOT NULL,
119
- "checksum" character(32) NOT NULL,
120
- "executed_at" timestamp with time zone DEFAULT "now"() NOT NULL
123
+ "checksum" character(32),
124
+ "executed_at" timestamp with time zone DEFAULT "now"() NOT NULL,
125
+ "type" "schema_migration_type" DEFAULT 'schema'::"schema_migration_type" NOT NULL
121
126
  );
122
127
  CREATE SEQUENCE "schema_migration_id_seq"
123
128
  AS integer
@@ -8,4 +8,5 @@ export namespace AuthorizationActions {
8
8
  export const PROJECT_HISTORY_ANY = Authorizator.createAction(Resources.project, 'historyAny')
9
9
 
10
10
  export const PROJECT_MIGRATE = Authorizator.createAction(Resources.project, 'migrate')
11
+ export const PROJECT_LIST_MIGRATIONS = Authorizator.createAction(Resources.project, 'listMigrations')
11
12
  }
@@ -7,6 +7,7 @@ export class PermissionsFactory {
7
7
  const permissions = new Permissions()
8
8
  permissions.allow(ProjectRole.ADMIN, { resource: Permissions.ALL, privilege: Permissions.ALL })
9
9
  permissions.allow(ProjectRole.DEPLOYER, AuthorizationActions.PROJECT_MIGRATE)
10
+ permissions.allow(ProjectRole.DEPLOYER, AuthorizationActions.PROJECT_LIST_MIGRATIONS)
10
11
 
11
12
  return permissions
12
13
  }
@@ -1,20 +1,33 @@
1
1
  import { InsertBuilder, QueryBuilder } from '@contember/database'
2
- import { calculateMigrationChecksum, Migration } from '@contember/schema-migrations'
2
+ import { calculateMigrationChecksum } from '@contember/schema-migrations'
3
3
  import { Command } from '../Command'
4
+ import { MigrationInput } from '../../migrations/MigrationInput'
4
5
 
5
- export class SaveMigrationCommand implements Command<void> {
6
- constructor(private readonly migration: Migration) {}
6
+ export class SaveMigrationCommand implements Command<number> {
7
+ constructor(private readonly migration: MigrationInput) {}
8
+
9
+ public async execute({ db }: Command.Args): Promise<number> {
10
+ const { type, ...migrationData } = this.migration
11
+
12
+ if ('queries' in migrationData) {
13
+ // in case of content migration, we don't want to store the queries
14
+ migrationData.queries = []
15
+ }
7
16
 
8
- public async execute({ db }: Command.Args): Promise<void> {
9
17
  const values: QueryBuilder.Values = {
10
18
  version: this.migration.version,
11
19
  name: this.migration.name,
12
- migration: JSON.stringify(this.migration),
13
- checksum: calculateMigrationChecksum(this.migration),
20
+ type,
21
+ migration: JSON.stringify(migrationData),
22
+ checksum: this.migration.type === 'schema' ? calculateMigrationChecksum(this.migration) : null,
14
23
  }
15
- await InsertBuilder.create() //
24
+
25
+ const result = await InsertBuilder.create()
16
26
  .into('schema_migration')
17
27
  .values(values)
28
+ .returning<{id: number}>('id')
18
29
  .execute(db)
30
+
31
+ return result[0].id
19
32
  }
20
33
  }
@@ -0,0 +1,27 @@
1
+ import { Stage } from '../../dtos'
2
+ import { DatabaseContext } from '../../database'
3
+ import { Schema } from '@contember/schema'
4
+ import { ExecutionResult } from 'graphql'
5
+ import { DatabaseMetadata } from '@contember/database'
6
+
7
+ export interface ContentQueryExecutorContext {
8
+ schema: Schema & { id: number }
9
+ identity: { id: string }
10
+ db: DatabaseContext
11
+ stage: Stage
12
+ project: { slug: string; systemSchema: string }
13
+ databaseMetadata: DatabaseMetadata
14
+ }
15
+
16
+ export interface ContentQueryExecutorQuery {
17
+ query: string
18
+ variables?: Record<string, any>
19
+ }
20
+
21
+ export interface ContentQueryExecutor {
22
+ execute: (ctx: ContentQueryExecutorContext, query: ContentQueryExecutorQuery) => Promise<ContentQueryExecutorResult>
23
+ }
24
+
25
+ export type ContentQueryExecutorResult =
26
+ | { ok: false; errors: string[] }
27
+ | { ok: true; result: ExecutionResult }
@@ -1 +1,2 @@
1
1
  export * from './tenant'
2
+ export * from './content/ContentQueryExecutor'
@@ -9,7 +9,9 @@ export class ExecutedMigration implements Migration {
9
9
  public readonly formatVersion: number,
10
10
  public readonly modifications: Migration.Modification[],
11
11
  public readonly executedAt: Date,
12
- public readonly checksum: string,
12
+ public readonly checksum: string | null,
13
+ public readonly type: 'schema' | 'content',
14
+
13
15
  ) {
14
16
  this.version = MigrationVersionHelper.extractVersion(name)
15
17
  }
@@ -0,0 +1,25 @@
1
+ import { Migration } from '@contember/schema-migrations'
2
+
3
+ export type SchemaMigrationInput =
4
+ & {
5
+ readonly type: 'schema'
6
+ }
7
+ & Migration
8
+
9
+ export type ContentMigrationInput = {
10
+ readonly type: 'content'
11
+ readonly version: string // YYYY-MM-DD-HHIISS
12
+ readonly name: string // version-label
13
+ readonly queries: readonly ContentMigrationQuery[]
14
+ }
15
+
16
+ export type ContentMigrationQuery = {
17
+ readonly query: string
18
+ readonly variables?: any
19
+ readonly stage?: string
20
+ readonly checkMutationResult?: boolean
21
+ }
22
+
23
+ export type MigrationInput =
24
+ | SchemaMigrationInput
25
+ | ContentMigrationInput
@@ -1,5 +1,5 @@
1
1
  import { calculateMigrationChecksum, Migration, MigrationDescriber, MigrationVersionHelper } from '@contember/schema-migrations'
2
- import { Client, QueryError, wrapIdentifier } from '@contember/database'
2
+ import { Client, DatabaseMetadata, QueryError, wrapIdentifier } from '@contember/database'
3
3
  import { Schema } from '@contember/schema'
4
4
  import { SaveMigrationCommand } from '../commands'
5
5
  import { Stage } from '../dtos'
@@ -9,10 +9,9 @@ import { MigrateErrorCode } from '../../schema'
9
9
  import { SchemaVersionBuilder } from './SchemaVersionBuilder'
10
10
  import { SchemaValidator, SchemaValidatorSkippedErrors } from '@contember/schema-utils'
11
11
  import { logger } from '@contember/logger'
12
- import {
13
- SchemaDatabaseMetadataResolverStore,
14
- MigrationsDatabaseMetadataResolverStoreFactory,
15
- } from '../metadata/SchemaDatabaseMetadataResolverStore'
12
+ import { MigrationsDatabaseMetadataResolverStoreFactory, SchemaDatabaseMetadataResolverStore } from '../metadata'
13
+ import { ContentMigrationQuery, MigrationInput } from './MigrationInput'
14
+ import { ContentQueryExecutor } from '../dependencies'
16
15
 
17
16
  export class ProjectMigrator {
18
17
  constructor(
@@ -20,17 +19,20 @@ export class ProjectMigrator {
20
19
  private readonly schemaVersionBuilder: SchemaVersionBuilder,
21
20
  private readonly executedMigrationsResolver: ExecutedMigrationsResolver,
22
21
  private readonly migrationsDatabaseMetadataResolverStoreFactory: MigrationsDatabaseMetadataResolverStoreFactory,
22
+ private readonly contentQueryExecutor: ContentQueryExecutor,
23
23
  ) {}
24
24
 
25
- public async migrate(
26
- db: DatabaseContext,
27
- stages: Stage[],
28
- migrationsToExecute: readonly Migration[],
29
- { ignoreOrder = false, skipExecuted = false }: {
25
+ public async migrate({ db, project, identity, options: { ignoreOrder = false, skipExecuted = false }, migrationsToExecute, stages }: {
26
+ db: DatabaseContext
27
+ project: { slug: string; systemSchema: string }
28
+ identity: { id: string }
29
+ stages: Stage[]
30
+ migrationsToExecute: readonly MigrationInput[]
31
+ options: {
30
32
  ignoreOrder?: boolean
31
33
  skipExecuted?: boolean
32
- },
33
- ) {
34
+ }
35
+ }) {
34
36
  if (migrationsToExecute.length === 0) {
35
37
  return
36
38
  }
@@ -42,21 +44,73 @@ export class ProjectMigrator {
42
44
  const metadataStore = this.migrationsDatabaseMetadataResolverStoreFactory.create(db)
43
45
 
44
46
  for (const migration of sorted) {
45
- const formatVersion = migration.formatVersion
46
-
47
- for (const modification of migration.modifications) {
48
- [schema] = await this.applyModification(
49
- db.client,
50
- stages,
51
- schema,
52
- modification,
53
- formatVersion,
54
- migration.version,
55
- db.client.schema,
56
- metadataStore,
57
- )
47
+
48
+ if (migration.type === 'schema') {
49
+ const formatVersion = migration.formatVersion
50
+
51
+ for (const modification of migration.modifications) {
52
+ [schema] = await this.applyModification(
53
+ db.client,
54
+ stages,
55
+ schema,
56
+ modification,
57
+ formatVersion,
58
+ migration.version,
59
+ db.client.schema,
60
+ metadataStore,
61
+ )
62
+ }
63
+ } else if (migration.type === 'content') {
64
+ const schemaWithId = {
65
+ ...schema,
66
+ id,
67
+ }
68
+ for (const query of migration.queries) {
69
+ await this.executeContentMigration({
70
+ version: migration.version,
71
+ query,
72
+ db,
73
+ schema: schemaWithId,
74
+ stages,
75
+ databaseMetadata: await metadataStore.getMetadata(db.client.schema),
76
+ identity,
77
+ project,
78
+ })
79
+ }
80
+
81
+ }
82
+ id = await db.commandBus.execute(new SaveMigrationCommand(migration))
83
+ }
84
+ }
85
+
86
+ private async executeContentMigration({ query, stages, version, ...ctx }: {
87
+ version: string
88
+ db: DatabaseContext
89
+ query: ContentMigrationQuery
90
+ stages: Stage[]
91
+ schema: Schema & { id: number }
92
+ project: { slug: string; systemSchema: string }
93
+ identity: { id: string }
94
+ databaseMetadata: DatabaseMetadata
95
+ }) {
96
+ const queryStages = query.stage ? stages.filter(it => it.slug === query.stage) : stages
97
+ for (const stage of queryStages) {
98
+ const response = await this.contentQueryExecutor.execute({
99
+ stage,
100
+ ...ctx,
101
+ }, query)
102
+
103
+ if (!response.ok) {
104
+ throw new FailedContentMigrationError(version, response.errors.join(', '))
105
+ }
106
+ if (query.checkMutationResult !== false) {
107
+ for (const [key, value] of Object.entries(response.result.data ?? {})) {
108
+ if (typeof value === 'object' && value !== null && 'ok' in value && !value.ok) {
109
+ const errorDetails = 'errorMessage' in value ? value.errorMessage : 'No details available. Please fetch errorMessage field.'
110
+ throw new NotSuccessfulContentMigrationError(version, `Mutation ${key} failed: ${errorDetails}`)
111
+ }
112
+ }
58
113
  }
59
- await db.commandBus.execute(new SaveMigrationCommand(migration))
60
114
  }
61
115
  }
62
116
 
@@ -64,45 +118,49 @@ export class ProjectMigrator {
64
118
  db: DatabaseContext,
65
119
  schema: Schema,
66
120
  version: string,
67
- migrationsToExecute: readonly Migration[],
121
+ migrationsToExecute: readonly MigrationInput[],
68
122
  { ignoreOrder, skipExecuted }: {
69
123
  ignoreOrder: boolean
70
124
  skipExecuted: boolean
71
125
  },
72
- ): Promise<readonly Migration[]> {
126
+ ): Promise<readonly MigrationInput[]> {
73
127
  const executedMigrations = await this.executedMigrationsResolver.getMigrations(db)
74
- const toExecute = []
128
+ const toExecute: MigrationInput[] = []
75
129
  let skippedErrors: SchemaValidatorSkippedErrors[] = []
76
130
  for (const migration of migrationsToExecute) {
77
131
  const executedMigration = executedMigrations.find(it => it.version === migration.version)
78
132
  if (executedMigration) {
79
- if (skipExecuted && executedMigration.checksum === calculateMigrationChecksum(migration)) {
133
+ if (skipExecuted && (migration.type !== 'schema' || executedMigration.checksum === calculateMigrationChecksum(migration))) {
80
134
  continue
81
135
  } else {
82
136
  throw new AlreadyExecutedMigrationError(migration.version, `Migration is already executed`)
83
137
  }
84
138
  }
139
+
85
140
  if (migration.version < version && !ignoreOrder) {
86
141
  throw new MustFollowLatestMigrationError(migration.version, `Must follow latest executed migration ${version}`)
87
142
  }
88
- const described = this.migrationDescriber.describeModifications(schema, migration)
89
- if (described.length === 0) {
90
- continue
91
- }
92
- const latestModification = described[described.length - 1]
93
- schema = latestModification.schema
94
- skippedErrors = [
95
- ...skippedErrors.filter(it => it.skipUntil && MigrationVersionHelper.extractVersion(it.skipUntil) >= migration.version),
96
- ...migration.skippedErrors ?? [],
97
- ]
98
- const errors = SchemaValidator.validate(schema, skippedErrors)
99
- if (errors.length > 0) {
100
- throw new InvalidSchemaError(
101
- migration.version,
102
- 'Migration generates invalid schema: \n' +
103
- errors.map(it => `${it.path.join('.')}: [${it.code}] ${it.message}`).join('\n'),
104
- )
143
+ if (migration.type === 'schema') {
144
+ const described = this.migrationDescriber.describeModifications(schema, migration)
145
+ if (described.length === 0) {
146
+ continue
147
+ }
148
+ const latestModification = described[described.length - 1]
149
+ schema = latestModification.schema
150
+ skippedErrors = [
151
+ ...skippedErrors.filter(it => it.skipUntil && MigrationVersionHelper.extractVersion(it.skipUntil) >= migration.version),
152
+ ...migration.skippedErrors ?? [],
153
+ ]
154
+ const errors = SchemaValidator.validate(schema, skippedErrors)
155
+ if (errors.length > 0) {
156
+ throw new InvalidSchemaError(
157
+ migration.version,
158
+ 'Migration generates invalid schema: \n' +
159
+ errors.map(it => `${it.path.join('.')}: [${it.code}] ${it.message}`).join('\n'),
160
+ )
161
+ }
105
162
  }
163
+
106
164
  toExecute.push(migration)
107
165
  }
108
166
  // intentionally not using skippedErrors here
@@ -180,3 +238,11 @@ export class MigrationFailedError extends MigrationError {
180
238
  export class InvalidSchemaError extends MigrationError {
181
239
  code = MigrateErrorCode.InvalidSchema
182
240
  }
241
+
242
+ export class FailedContentMigrationError extends MigrationError {
243
+ code = MigrateErrorCode.ContentMigrationFailed
244
+ }
245
+
246
+ export class NotSuccessfulContentMigrationError extends MigrationError {
247
+ code = MigrateErrorCode.ContentMigrationNotSuccessful
248
+ }
@@ -22,11 +22,16 @@ export class SchemaVersionBuilder {
22
22
  }
23
23
 
24
24
  const schema = newMigrations.reduce(
25
- (schema, migration) => ({
26
- ...this.schemaMigrator.applyModifications(schema, migration.modifications, migration.formatVersion),
27
- version: migration.version,
28
- id: migration.id,
29
- }),
25
+ (schema, migration) => {
26
+ if (migration.type !== 'schema') {
27
+ return schema
28
+ }
29
+ return ({
30
+ ...this.schemaMigrator.applyModifications(schema, migration.modifications, migration.formatVersion),
31
+ version: migration.version,
32
+ id: migration.id,
33
+ })
34
+ },
30
35
  after?.notNormalized ?? emptyVersionedSchema.notNormalized,
31
36
  )
32
37
  const normalized = normalizeSchema(schema)
@@ -2,7 +2,7 @@ import { SelectBuilder } from '@contember/database'
2
2
  import { ExecutedMigration } from '../../dtos'
3
3
  import { VERSION_INITIAL } from '@contember/schema-migrations'
4
4
 
5
- type ExecutedMigrationRow = { id: number; name: string; migration: any; checksum: string; executed_at: Date }
5
+ type ExecutedMigrationRow = { id: number; name: string; migration: any; checksum: string | null; executed_at: Date; type: 'content' | 'schema' }
6
6
  export const createExecutedMigrationQueryBuilder = () =>
7
7
  SelectBuilder.create<ExecutedMigrationRow>()
8
8
  .select('id')
@@ -10,6 +10,7 @@ export const createExecutedMigrationQueryBuilder = () =>
10
10
  .select('migration')
11
11
  .select('checksum')
12
12
  .select('executed_at')
13
+ .select('type')
13
14
  .from('schema_migration')
14
15
  .orderBy('version')
15
16
 
@@ -21,4 +22,5 @@ export const createExecutedMigrationDto = (row: ExecutedMigrationRow) =>
21
22
  row.migration.modifications,
22
23
  row.executed_at,
23
24
  row.checksum,
25
+ row.type,
24
26
  )
@@ -1,7 +1,7 @@
1
1
  import { Authorizator } from '@contember/authorization'
2
2
  import { Schema } from '@contember/schema'
3
3
  import { ForbiddenError } from '@contember/graphql-utils'
4
- import { DatabaseContext, Identity, SchemaVersionBuilder } from '../model'
4
+ import { DatabaseContext, Identity, VersionedSchema } from '../model'
5
5
  import { ProjectConfig } from '../types'
6
6
  import { StagePermissionsFactory } from '../model/authorization/StagePermissionsFactory'
7
7
  import { StageScope } from '../model/authorization/StageScope'
@@ -13,7 +13,7 @@ export class SystemResolverContextFactory {
13
13
  ) {}
14
14
 
15
15
  public async create(
16
- schema: Schema,
16
+ schema: VersionedSchema,
17
17
  systemDbContext: DatabaseContext,
18
18
  project: ProjectConfig,
19
19
  identity: Identity,