@contember/engine-system-api 1.1.2 → 1.2.0-alpha.11

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 (73) hide show
  1. package/dist/src/index.d.ts +1 -1
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +10 -9
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/migrations/2020-05-06-150000-composed-primary.d.ts +2 -1
  6. package/dist/src/migrations/2020-05-06-150000-composed-primary.d.ts.map +1 -1
  7. package/dist/src/migrations/2020-05-06-150000-composed-primary.js +1 -1
  8. package/dist/src/migrations/2020-05-06-150000-composed-primary.js.map +1 -1
  9. package/dist/src/migrations/2020-06-01-103000-event-trigger-perf.d.ts +2 -1
  10. package/dist/src/migrations/2020-06-01-103000-event-trigger-perf.d.ts.map +1 -1
  11. package/dist/src/migrations/2020-06-01-103000-event-trigger-perf.js +1 -1
  12. package/dist/src/migrations/2020-06-01-103000-event-trigger-perf.js.map +1 -1
  13. package/dist/src/migrations/2021-05-07-155800-event-log-rework.d.ts +2 -1
  14. package/dist/src/migrations/2021-05-07-155800-event-log-rework.d.ts.map +1 -1
  15. package/dist/src/migrations/2021-05-07-155800-event-log-rework.js +1 -1
  16. package/dist/src/migrations/2021-05-07-155800-event-log-rework.js.map +1 -1
  17. package/dist/src/migrations/2021-05-19-1232000-event-log-fix-constraint.d.ts +2 -1
  18. package/dist/src/migrations/2021-05-19-1232000-event-log-fix-constraint.d.ts.map +1 -1
  19. package/dist/src/migrations/2021-05-19-1232000-event-log-fix-constraint.js +1 -1
  20. package/dist/src/migrations/2021-05-19-1232000-event-log-fix-constraint.js.map +1 -1
  21. package/dist/src/migrations/runner.d.ts +2 -1
  22. package/dist/src/migrations/runner.d.ts.map +1 -1
  23. package/dist/src/migrations/runner.js.map +1 -1
  24. package/dist/src/migrations/types.d.ts +2 -1
  25. package/dist/src/migrations/types.d.ts.map +1 -1
  26. package/dist/src/model/ProjectInitializer.d.ts +3 -4
  27. package/dist/src/model/ProjectInitializer.d.ts.map +1 -1
  28. package/dist/src/model/ProjectInitializer.js +20 -20
  29. package/dist/src/model/ProjectInitializer.js.map +1 -1
  30. package/dist/src/model/authorization/AuthorizationActions.d.ts +2 -0
  31. package/dist/src/model/authorization/AuthorizationActions.d.ts.map +1 -1
  32. package/dist/src/model/database/DatabaseContext.d.ts +1 -0
  33. package/dist/src/model/database/DatabaseContext.d.ts.map +1 -1
  34. package/dist/src/model/database/DatabaseContext.js +1 -0
  35. package/dist/src/model/database/DatabaseContext.js.map +1 -1
  36. package/dist/src/model/helpers/modelHelpers.js +1 -1
  37. package/dist/src/model/helpers/modelHelpers.js.map +1 -1
  38. package/dist/src/model/migrations/ProjectMigrator.d.ts +4 -1
  39. package/dist/src/model/migrations/ProjectMigrator.d.ts.map +1 -1
  40. package/dist/src/model/migrations/ProjectMigrator.js +16 -9
  41. package/dist/src/model/migrations/ProjectMigrator.js.map +1 -1
  42. package/dist/src/resolvers/ResolverFactory.d.ts +1 -1
  43. package/dist/src/resolvers/ResolverFactory.d.ts.map +1 -1
  44. package/dist/src/resolvers/mutation/MigrateMutationResolver.d.ts.map +1 -1
  45. package/dist/src/resolvers/mutation/MigrateMutationResolver.js +7 -3
  46. package/dist/src/resolvers/mutation/MigrateMutationResolver.js.map +1 -1
  47. package/dist/src/schema/types.d.ts +126 -141
  48. package/dist/src/schema/types.d.ts.map +1 -1
  49. package/dist/src/schema/types.js +6 -6
  50. package/dist/src/schema/types.js.map +1 -1
  51. package/dist/src/tsconfig.tsbuildinfo +1 -1
  52. package/dist/tests/cases/migrations/2020-05-06-150000-composed-primary.test.js +1 -0
  53. package/dist/tests/cases/migrations/2020-05-06-150000-composed-primary.test.js.map +1 -1
  54. package/dist/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.js +1 -0
  55. package/dist/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.js.map +1 -1
  56. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  57. package/graphql.codegen.yml +0 -2
  58. package/package.json +20 -20
  59. package/src/index.ts +11 -10
  60. package/src/migrations/2020-05-06-150000-composed-primary.ts +3 -2
  61. package/src/migrations/2020-06-01-103000-event-trigger-perf.ts +3 -2
  62. package/src/migrations/2021-05-07-155800-event-log-rework.ts +3 -2
  63. package/src/migrations/2021-05-19-1232000-event-log-fix-constraint.ts +3 -2
  64. package/src/migrations/runner.ts +2 -1
  65. package/src/migrations/types.ts +2 -1
  66. package/src/model/ProjectInitializer.ts +26 -33
  67. package/src/model/database/DatabaseContext.ts +3 -1
  68. package/src/model/helpers/modelHelpers.ts +1 -1
  69. package/src/model/migrations/ProjectMigrator.ts +22 -14
  70. package/src/resolvers/mutation/MigrateMutationResolver.ts +8 -3
  71. package/src/schema/types.ts +126 -148
  72. package/tests/cases/migrations/2020-05-06-150000-composed-primary.test.ts +1 -0
  73. package/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.ts +1 -0
@@ -15,8 +15,6 @@ generates:
15
15
  - "typescript"
16
16
  - "typescript-operations"
17
17
  - "typescript-resolvers"
18
- require:
19
- - "ts-node/register"
20
18
  hooks:
21
19
  afterAllFileWrite:
22
20
  - eslint --fix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-system-api",
3
- "version": "1.1.2",
3
+ "version": "1.2.0-alpha.11",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
@@ -9,28 +9,28 @@
9
9
  "test": "vitest"
10
10
  },
11
11
  "dependencies": {
12
- "@contember/authorization": "^1.1.2",
13
- "@contember/database": "^1.1.2",
14
- "@contember/database-migrations": "^1.1.2",
15
- "@contember/dic": "^1.1.2",
16
- "@contember/engine-common": "^1.1.2",
17
- "@contember/graphql-utils": "^1.1.2",
18
- "@contember/queryable": "^1.1.2",
19
- "@contember/schema": "^1.1.2",
20
- "@contember/schema-migrations": "^1.1.2",
21
- "@contember/schema-utils": "^1.1.2",
22
- "@graphql-tools/utils": "^7.10.0",
12
+ "@contember/authorization": "^1.2.0-alpha.11",
13
+ "@contember/database": "^1.2.0-alpha.11",
14
+ "@contember/database-migrations": "^1.2.0-alpha.11",
15
+ "@contember/dic": "^1.2.0-alpha.11",
16
+ "@contember/engine-common": "^1.2.0-alpha.11",
17
+ "@contember/graphql-utils": "^1.2.0-alpha.11",
18
+ "@contember/queryable": "^1.2.0-alpha.11",
19
+ "@contember/schema": "^1.2.0-alpha.11",
20
+ "@contember/schema-migrations": "^1.2.0-alpha.11",
21
+ "@contember/schema-utils": "^1.2.0-alpha.11",
22
+ "@graphql-tools/utils": "^8.6.13",
23
23
  "graphql-tag": "^2.12.5"
24
24
  },
25
25
  "devDependencies": {
26
- "@contember/engine-api-tester": "^1.1.2",
27
- "@contember/schema-definition": "^1.1.2",
28
- "@graphql-codegen/cli": "^1.15.2",
29
- "@graphql-codegen/typescript": "^1.15.2",
30
- "@graphql-codegen/typescript-operations": "^1.15.2",
31
- "@graphql-codegen/typescript-resolvers": "^1.15.2",
32
- "@types/node": "^17.0.5",
33
- "graphql": "^15.4.0",
26
+ "@contember/engine-api-tester": "^1.2.0-alpha.11",
27
+ "@contember/schema-definition": "^1.2.0-alpha.11",
28
+ "@graphql-codegen/cli": "^2.6.2",
29
+ "@graphql-codegen/typescript": "^2.5.1",
30
+ "@graphql-codegen/typescript-operations": "^2.4.2",
31
+ "@graphql-codegen/typescript-resolvers": "^2.6.6",
32
+ "@types/node": "^18",
33
+ "graphql": "^16.5.0",
34
34
  "pg": "^8.5.0"
35
35
  },
36
36
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -3,21 +3,22 @@ export { SchemaMigrator, MigrationsResolver } from '@contember/schema-migrations
3
3
  export { typeDefs, devTypeDefs, Schema } from './schema'
4
4
 
5
5
  export {
6
- Identity,
7
6
  DatabaseContext,
8
- setupSystemVariables,
9
- StageBySlugQuery,
10
- unnamedIdentity,
11
- SchemaVersionBuilder,
7
+ DatabaseContextFactory,
12
8
  formatSchemaName,
13
- VersionedSchema,
14
- ProjectInitializer,
9
+ getJunctionTables,
10
+ Identity,
15
11
  LatestTransactionIdByStageQuery,
16
- DatabaseContextFactory,
12
+ ProjectInitializer,
17
13
  ProjectMigrator,
18
- StageCreator,
19
- getJunctionTables,
14
+ SchemaVersionBuilder,
15
+ setupSystemVariables,
20
16
  Stage,
17
+ StageBySlugQuery,
18
+ StageCreator,
19
+ StagesQuery,
20
+ unnamedIdentity,
21
+ VersionedSchema,
21
22
  } from './model'
22
23
  export * from './SystemContainer'
23
24
  export * from './resolvers'
@@ -1,6 +1,7 @@
1
1
  import { escapeValue, MigrationBuilder, Name } from '@contember/database-migrations'
2
2
  import { SystemMigrationArgs } from './types'
3
3
  import { getJunctionTables } from '../model'
4
+ import { MigrationArgs } from '@contember/database-migrations/dist/src/Migration'
4
5
 
5
6
  const createEventTrigger = (builder: MigrationBuilder, tableName: Name, primaryColumns: string[]) => {
6
7
  builder.createTrigger(tableName, 'log_event', {
@@ -56,8 +57,8 @@ const migrateEvents = (builder: MigrationBuilder, tableName: string, primaryKeys
56
57
  `)
57
58
  }
58
59
 
59
- export default async function (builder: MigrationBuilder, args: SystemMigrationArgs) {
60
- const schema = await args.schemaResolver()
60
+ export default async function (builder: MigrationBuilder, args: MigrationArgs<SystemMigrationArgs>) {
61
+ const schema = await args.schemaResolver(args.connection)
61
62
  const tablesToMigrate = getJunctionTables(schema.model)
62
63
  const schemas = args.project.stages.map(it => `stage_${it.slug}`)
63
64
  for (const table of tablesToMigrate) {
@@ -1,6 +1,7 @@
1
1
  import { MigrationBuilder, Name } from '@contember/database-migrations'
2
2
  import { SystemMigrationArgs } from './types'
3
3
  import { getJunctionTables } from '../model'
4
+ import { MigrationArgs } from '@contember/database-migrations/dist/src/Migration'
4
5
 
5
6
  const createEventStatementTrigger = (builder: MigrationBuilder, tableName: Name) => {
6
7
  builder.createTrigger(tableName, 'log_event_statement', {
@@ -14,8 +15,8 @@ const createEventStatementTrigger = (builder: MigrationBuilder, tableName: Name)
14
15
  })
15
16
  }
16
17
 
17
- export default async function (builder: MigrationBuilder, args: SystemMigrationArgs) {
18
- const schema = await args.schemaResolver()
18
+ export default async function (builder: MigrationBuilder, args: MigrationArgs<SystemMigrationArgs>) {
19
+ const schema = await args.schemaResolver(args.connection)
19
20
  const junctionTables = getJunctionTables(schema.model)
20
21
  const schemas = args.project.stages.map(it => `stage_${it.slug}`)
21
22
  builder.sql(`CREATE OR REPLACE FUNCTION "trigger_event"() RETURNS TRIGGER AS $$
@@ -1,6 +1,7 @@
1
1
  import { MigrationBuilder, Name } from '@contember/database-migrations'
2
2
  import { SystemMigrationArgs } from './types'
3
3
  import { getJunctionTables } from '../model'
4
+ import { MigrationArgs } from '@contember/database-migrations/dist/src/Migration'
4
5
 
5
6
  const createTrxEvent = (builder: MigrationBuilder, tableName: Name) => {
6
7
  builder.createTrigger(tableName, 'log_event_trx', {
@@ -16,8 +17,8 @@ const createTrxEvent = (builder: MigrationBuilder, tableName: Name) => {
16
17
  })
17
18
  }
18
19
 
19
- export default async function (builder: MigrationBuilder, args: SystemMigrationArgs) {
20
- const schema = await args.schemaResolver()
20
+ export default async function (builder: MigrationBuilder, args: MigrationArgs<SystemMigrationArgs>) {
21
+ const schema = await args.schemaResolver(args.connection)
21
22
  const junctionTables = getJunctionTables(schema.model)
22
23
  const schemas = args.project.stages.map(it => `stage_${it.slug}`)
23
24
  builder.sql(`LOCK TABLE event`)
@@ -1,6 +1,7 @@
1
1
  import { MigrationBuilder, Name } from '@contember/database-migrations'
2
2
  import { SystemMigrationArgs } from './types'
3
3
  import { getJunctionTables } from '../model'
4
+ import { MigrationArgs } from '@contember/database-migrations'
4
5
 
5
6
  const recreateTrigger = (builder: MigrationBuilder, tableName: Name) => {
6
7
  builder.dropTrigger(tableName, 'log_event_trx', { ifExists: true })
@@ -19,8 +20,8 @@ const recreateTrigger = (builder: MigrationBuilder, tableName: Name) => {
19
20
  })
20
21
  }
21
22
 
22
- export default async function (builder: MigrationBuilder, args: SystemMigrationArgs) {
23
- const schema = await args.schemaResolver()
23
+ export default async function (builder: MigrationBuilder, args: MigrationArgs<SystemMigrationArgs>) {
24
+ const schema = await args.schemaResolver(args.connection)
24
25
  const junctionTables = getJunctionTables(schema.model)
25
26
  const schemas = args.project.stages.map(it => `stage_${it.slug}`)
26
27
 
@@ -4,8 +4,9 @@ import composedPrimary from './2020-05-06-150000-composed-primary'
4
4
  import eventTriggerPerf from './2020-06-01-103000-event-trigger-perf'
5
5
  import eventLogRework from './2021-05-07-155800-event-log-rework'
6
6
  import eventLogFixConstraint from './2021-05-19-1232000-event-log-fix-constraint'
7
+ import { SystemMigrationArgs } from './types'
7
8
 
8
- export const getSystemMigrations = (): Promise<Migration[]> => {
9
+ export const getSystemMigrations = (): Promise<Migration<SystemMigrationArgs>[]> => {
9
10
  return loadMigrations(process.env.CONTEMBER_SYSTEM_MIGRATIONS_DIR || __dirname, [
10
11
  new Migration('2020-03-27-130000-project-migrations', projectMigrations),
11
12
  new Migration('2020-05-06-150000-composed-primary', composedPrimary),
@@ -1,7 +1,8 @@
1
1
  import { Schema } from '@contember/schema'
2
2
  import { ProjectConfig } from '../types'
3
+ import { Connection } from '@contember/database'
3
4
 
4
5
  export interface SystemMigrationArgs {
5
- schemaResolver: () => Promise<Schema>
6
+ schemaResolver: (connection: Connection.ConnectionLike) => Promise<Schema>
6
7
  project: ProjectConfig
7
8
  }
@@ -4,15 +4,13 @@ import { StageCreator } from './stages'
4
4
  import { DatabaseContext, DatabaseContextFactory } from './database'
5
5
  import { SystemDbMigrationsRunnerFactory } from '../SystemContainer'
6
6
  import {
7
+ Client,
7
8
  Connection,
8
9
  createDatabaseIfNotExists,
9
- DatabaseCredentials,
10
+ DatabaseConfig,
10
11
  retryTransaction,
11
- SingleConnection,
12
12
  } from '@contember/database'
13
13
  import { Logger } from '@contember/engine-common'
14
- import { Migration } from '@contember/schema-migrations'
15
- import { StagesQuery } from './queries'
16
14
 
17
15
  export class ProjectInitializer {
18
16
  constructor(
@@ -24,45 +22,46 @@ export class ProjectInitializer {
24
22
 
25
23
  public async initialize(
26
24
  databaseContextFactory: DatabaseContextFactory,
27
- project: ProjectConfig & { db?: DatabaseCredentials },
25
+ project: ProjectConfig & { db?: DatabaseConfig },
28
26
  logger: Logger,
29
- migrations?: Migration[],
30
27
  ) {
31
28
  const dbContext = databaseContextFactory.create()
32
29
  if (project.db) {
33
30
  // todo: use dbContext
34
31
  logger.group(`Executing system schema migration`)
35
32
  await createDatabaseIfNotExists(project.db, logger.write.bind(logger))
36
- const singleConnection = new SingleConnection(project.db, {})
37
- const systemSchema = dbContext.client.schema
38
- const dbContextMigrations = databaseContextFactory
39
- .withClient(singleConnection.createClient(systemSchema, { module: 'system' }))
40
- .create()
33
+ const singleConnection = Connection.createSingle(project.db)
34
+ await singleConnection.scope(async connection => {
35
+ const systemSchema = dbContext.client.schema
41
36
 
42
- const schemaResolver = () => this.schemaVersionBuilder.buildSchema(dbContextMigrations)
43
- await this.systemDbMigrationsRunnerFactory(singleConnection, systemSchema).migrate(
44
- logger.write.bind(logger),
45
- {
46
- schemaResolver,
47
- project,
48
- },
49
- )
37
+ const schemaResolver = (connection: Connection.ConnectionLike) => {
38
+ const dbContextMigrations = databaseContextFactory
39
+ .withClient(new Client(connection, systemSchema, { module: 'system' }))
40
+ .create()
41
+ return this.schemaVersionBuilder.buildSchema(dbContextMigrations)
42
+ }
43
+ await this.systemDbMigrationsRunnerFactory(connection, systemSchema).migrate(
44
+ logger.write.bind(logger),
45
+ {
46
+ schemaResolver,
47
+ project,
48
+ },
49
+ )
50
+ })
50
51
  await singleConnection.end()
51
52
  logger.groupEnd()
52
53
  }
53
- return await retryTransaction(() =>
54
+ await retryTransaction(() =>
54
55
  dbContext.transaction(async trx => {
55
- await this.initStages(trx, project, logger, migrations)
56
+ await this.initStages(trx, project, logger)
56
57
  }),
57
58
  )
59
+ if (dbContext.client.connection instanceof Connection) {
60
+ await dbContext.client.connection.clearPool()
61
+ }
58
62
  }
59
63
 
60
- private async initStages(
61
- db: DatabaseContext<Connection.TransactionLike>,
62
- project: ProjectConfig,
63
- logger: Logger,
64
- migrations?: Migration[],
65
- ) {
64
+ private async initStages(db: DatabaseContext<Connection.TransactionLike>, project: ProjectConfig, logger: Logger) {
66
65
 
67
66
  logger.group(`Creating stages`)
68
67
 
@@ -76,11 +75,5 @@ export class ProjectInitializer {
76
75
  }
77
76
 
78
77
  logger.groupEnd()
79
- if (migrations) {
80
- logger.group(`Executing project migrations`)
81
- const stages = await db.queryHandler.fetch(new StagesQuery())
82
- await this.projectMigrator.migrate(db, stages, migrations, logger.write.bind(logger))
83
- logger.groupEnd()
84
- }
85
78
  }
86
79
  }
@@ -1,4 +1,4 @@
1
- import { Client, Connection, DatabaseQueryable } from '@contember/database'
1
+ import { Client, Connection, DatabaseQueryable, withDatabaseAdvisoryLock } from '@contember/database'
2
2
  import { QueryHandler } from '@contember/queryable'
3
3
  import { UuidProvider } from '../../utils'
4
4
  import { CommandBus } from '../commands'
@@ -7,6 +7,7 @@ export interface DatabaseContext<ConnectionType extends Connection.ConnectionLik
7
7
  client: Client<ConnectionType>
8
8
  queryHandler: QueryHandler<DatabaseQueryable>
9
9
  transaction: <T>(cb: (db: DatabaseContext<Connection.TransactionLike>) => Promise<T> | T) => Promise<T>
10
+ locked: <T>(lock: number, cb: (db: DatabaseContext<Connection.ConnectionLike>) => Promise<T> | T) => Promise<T>
10
11
  commandBus: CommandBus
11
12
  }
12
13
 
@@ -34,4 +35,5 @@ const createDatabaseContext = <ConnectionType extends Connection.ConnectionLike
34
35
  return cb(createDatabaseContext(client, providers))
35
36
  }),
36
37
  commandBus: new CommandBus(client, providers),
38
+ locked: (lock, cb) => client.locked(lock, client => cb(createDatabaseContext(client, providers))),
37
39
  })
@@ -5,7 +5,7 @@ export const getJunctionTables = (model: Model.Schema): Model.JoiningTable[] =>
5
5
  const tables: Model.JoiningTable[] = []
6
6
  Object.values(model.entities).forEach(entity => {
7
7
  acceptEveryFieldVisitor(model, entity, {
8
- visitManyHasManyOwning: ({}, relation) => {
8
+ visitManyHasManyOwning: ({ relation }) => {
9
9
  tables.push(relation.joiningTable)
10
10
  },
11
11
  visitColumn: () => {},
@@ -1,6 +1,5 @@
1
- import { Migration, MigrationDescriber } from '@contember/schema-migrations'
1
+ import { calculateMigrationChecksum, Migration, MigrationDescriber } from '@contember/schema-migrations'
2
2
  import { Client, QueryError, wrapIdentifier } from '@contember/database'
3
- import { formatSchemaName } from '../helpers'
4
3
  import { Schema } from '@contember/schema'
5
4
  import { SaveMigrationCommand } from '../commands'
6
5
  import { Stage } from '../dtos'
@@ -21,19 +20,20 @@ export class ProjectMigrator {
21
20
  db: DatabaseContext,
22
21
  stages: Stage[],
23
22
  migrationsToExecute: readonly Migration[],
24
- logger: (message: string) => void,
25
- ignoreOrder: boolean = false,
23
+ { ignoreOrder = false, skipExecuted = false }: {
24
+ ignoreOrder?: boolean
25
+ skipExecuted?: boolean
26
+ },
26
27
  ) {
27
28
  if (migrationsToExecute.length === 0) {
28
29
  return
29
30
  }
30
31
  let { version, notNormalized, ...schema } = await this.schemaVersionBuilder.buildSchema(db)
31
- await this.validateMigrations(db, schema, version, migrationsToExecute, ignoreOrder)
32
+ const validated = await this.validateMigrations(db, schema, version, migrationsToExecute, { ignoreOrder, skipExecuted })
32
33
 
33
- const sorted = [...migrationsToExecute].sort((a, b) => a.version.localeCompare(b.version))
34
+ const sorted = [...validated].sort((a, b) => a.version.localeCompare(b.version))
34
35
 
35
36
  for (const migration of sorted) {
36
- logger(`Executing migration ${migration.name}...`)
37
37
  const formatVersion = migration.formatVersion
38
38
 
39
39
  for (const modification of migration.modifications) {
@@ -48,10 +48,7 @@ export class ProjectMigrator {
48
48
  )
49
49
  }
50
50
  await db.commandBus.execute(new SaveMigrationCommand(migration))
51
- logger(`Done`)
52
51
  }
53
-
54
- logger(`Done`)
55
52
  }
56
53
 
57
54
  private async validateMigrations(
@@ -59,12 +56,21 @@ export class ProjectMigrator {
59
56
  schema: Schema,
60
57
  version: string,
61
58
  migrationsToExecute: readonly Migration[],
62
- ignoreOrder: boolean = false,
63
- ) {
59
+ { ignoreOrder, skipExecuted }: {
60
+ ignoreOrder: boolean
61
+ skipExecuted: boolean
62
+ },
63
+ ): Promise<readonly Migration[]> {
64
64
  const executedMigrations = await this.executedMigrationsResolver.getMigrations(db)
65
+ const toExecute = []
65
66
  for (const migration of migrationsToExecute) {
66
- if (executedMigrations.find(it => it.version === migration.version)) {
67
- throw new AlreadyExecutedMigrationError(migration.version, `Migration is already executed`)
67
+ const executedMigration = executedMigrations.find(it => it.version === migration.version)
68
+ if (executedMigration) {
69
+ if (skipExecuted && executedMigration.checksum === calculateMigrationChecksum(migration)) {
70
+ continue
71
+ } else {
72
+ throw new AlreadyExecutedMigrationError(migration.version, `Migration is already executed`)
73
+ }
68
74
  }
69
75
  if (migration.version < version && !ignoreOrder) {
70
76
  throw new MustFollowLatestMigrationError(migration.version, `Must follow latest executed migration ${version}`)
@@ -83,7 +89,9 @@ export class ProjectMigrator {
83
89
  errors.map(it => it.path.join('.') + ': ' + it.message).join('\n'),
84
90
  )
85
91
  }
92
+ toExecute.push(migration)
86
93
  }
94
+ return toExecute
87
95
  }
88
96
 
89
97
  private async applyModification(
@@ -5,6 +5,8 @@ import { MigrateResponse, MutationMigrateArgs } from '../../schema'
5
5
  import { Migration } from '@contember/schema-migrations'
6
6
  import { AuthorizationActions, MigrationError, ProjectMigrator, StagesQuery } from '../../model'
7
7
 
8
+ const pg_lock_id = 1597474138739147
9
+
8
10
  export class MigrateMutationResolver implements MutationResolver<'migrate'> {
9
11
  constructor(private readonly projectMigrator: ProjectMigrator) {}
10
12
 
@@ -26,13 +28,16 @@ export class MigrateMutationResolver implements MutationResolver<'migrate'> {
26
28
  ): Promise<MigrateResponse> {
27
29
  const migrations = args.migrations as readonly Migration[]
28
30
 
29
- return context.db.transaction(async trx => {
31
+ return context.db.locked(pg_lock_id, db => db.transaction(async trx => {
30
32
  const stages = await trx.queryHandler.fetch(new StagesQuery())
31
33
  for (const stage of stages) {
32
34
  await context.requireAccess(AuthorizationActions.PROJECT_MIGRATE, stage.slug)
33
35
  }
34
36
  try {
35
- await this.projectMigrator.migrate(trx, stages, migrations, () => null, force)
37
+ await this.projectMigrator.migrate(trx, stages, migrations, {
38
+ ignoreOrder: force,
39
+ skipExecuted: true,
40
+ })
36
41
  } catch (e) {
37
42
  if (e instanceof MigrationError) {
38
43
  await trx.client.connection.rollback()
@@ -54,6 +59,6 @@ export class MigrateMutationResolver implements MutationResolver<'migrate'> {
54
59
  ok: true,
55
60
  errors: [],
56
61
  }
57
- })
62
+ }))
58
63
  }
59
64
  }