@contember/migrations-client 1.4.0-alpha.5

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 (80) hide show
  1. package/dist/src/JsLoader.d.ts +10 -0
  2. package/dist/src/JsLoader.d.ts.map +1 -0
  3. package/dist/src/JsLoader.js +33 -0
  4. package/dist/src/JsLoader.js.map +1 -0
  5. package/dist/src/JsonLoader.d.ts +9 -0
  6. package/dist/src/JsonLoader.d.ts.map +1 -0
  7. package/dist/src/JsonLoader.js +37 -0
  8. package/dist/src/JsonLoader.js.map +1 -0
  9. package/dist/src/MigrationCreator.d.ts +21 -0
  10. package/dist/src/MigrationCreator.d.ts.map +1 -0
  11. package/dist/src/MigrationCreator.js +47 -0
  12. package/dist/src/MigrationCreator.js.map +1 -0
  13. package/dist/src/MigrationExecuteHelper.d.ts +13 -0
  14. package/dist/src/MigrationExecuteHelper.d.ts.map +1 -0
  15. package/dist/src/MigrationExecuteHelper.js +61 -0
  16. package/dist/src/MigrationExecuteHelper.js.map +1 -0
  17. package/dist/src/MigrationFile.d.ts +36 -0
  18. package/dist/src/MigrationFile.d.ts.map +1 -0
  19. package/dist/src/MigrationFile.js +6 -0
  20. package/dist/src/MigrationFile.js.map +1 -0
  21. package/dist/src/MigrationFileLoader.d.ts +5 -0
  22. package/dist/src/MigrationFileLoader.d.ts.map +1 -0
  23. package/dist/src/MigrationFileLoader.js +3 -0
  24. package/dist/src/MigrationFileLoader.js.map +1 -0
  25. package/dist/src/MigrationFilesManager.d.ts +18 -0
  26. package/dist/src/MigrationFilesManager.d.ts.map +1 -0
  27. package/dist/src/MigrationFilesManager.js +114 -0
  28. package/dist/src/MigrationFilesManager.js.map +1 -0
  29. package/dist/src/MigrationParser.d.ts +6 -0
  30. package/dist/src/MigrationParser.d.ts.map +1 -0
  31. package/dist/src/MigrationParser.js +33 -0
  32. package/dist/src/MigrationParser.js.map +1 -0
  33. package/dist/src/MigrationsResolver.d.ts +14 -0
  34. package/dist/src/MigrationsResolver.d.ts.map +1 -0
  35. package/dist/src/MigrationsResolver.js +37 -0
  36. package/dist/src/MigrationsResolver.js.map +1 -0
  37. package/dist/src/SchemaVersionBuilder.d.ts +13 -0
  38. package/dist/src/SchemaVersionBuilder.d.ts.map +1 -0
  39. package/dist/src/SchemaVersionBuilder.js +26 -0
  40. package/dist/src/SchemaVersionBuilder.js.map +1 -0
  41. package/dist/src/SystemClient.d.ts +29 -0
  42. package/dist/src/SystemClient.d.ts.map +1 -0
  43. package/dist/src/SystemClient.js +117 -0
  44. package/dist/src/SystemClient.js.map +1 -0
  45. package/dist/src/index.d.ts +14 -0
  46. package/dist/src/index.d.ts.map +1 -0
  47. package/dist/src/index.js +37 -0
  48. package/dist/src/index.js.map +1 -0
  49. package/dist/src/migrations.d.ts +56 -0
  50. package/dist/src/migrations.d.ts.map +1 -0
  51. package/dist/src/migrations.js +87 -0
  52. package/dist/src/migrations.js.map +1 -0
  53. package/dist/src/tsconfig.tsbuildinfo +1 -0
  54. package/dist/src/utils/assertNever.d.ts +2 -0
  55. package/dist/src/utils/assertNever.d.ts.map +1 -0
  56. package/dist/src/utils/assertNever.js +8 -0
  57. package/dist/src/utils/assertNever.js.map +1 -0
  58. package/dist/src/utils/json.d.ts +7 -0
  59. package/dist/src/utils/json.d.ts.map +1 -0
  60. package/dist/src/utils/json.js +3 -0
  61. package/dist/src/utils/json.js.map +1 -0
  62. package/package.json +24 -0
  63. package/src/JsLoader.ts +39 -0
  64. package/src/JsonLoader.ts +18 -0
  65. package/src/MigrationCreator.ts +60 -0
  66. package/src/MigrationExecuteHelper.ts +79 -0
  67. package/src/MigrationFile.ts +52 -0
  68. package/src/MigrationFileLoader.ts +5 -0
  69. package/src/MigrationFilesManager.ts +102 -0
  70. package/src/MigrationParser.ts +32 -0
  71. package/src/MigrationsResolver.ts +44 -0
  72. package/src/SchemaVersionBuilder.ts +33 -0
  73. package/src/SystemClient.ts +150 -0
  74. package/src/index.ts +23 -0
  75. package/src/migrations.ts +157 -0
  76. package/src/tsconfig.json +13 -0
  77. package/src/utils/assertNever.ts +3 -0
  78. package/src/utils/json.ts +4 -0
  79. package/tsconfig.json +5 -0
  80. package/tsconfig.settings.json +3 -0
@@ -0,0 +1,52 @@
1
+ import { Migration } from '@contember/schema-migrations'
2
+ import { JSONObject } from './utils/json'
3
+
4
+ export interface MigrationFile {
5
+ path: string
6
+ version: string
7
+ name: string
8
+
9
+ getContent: () => Promise<MigrationContent>
10
+ }
11
+
12
+ export const isSchemaMigration = (content: MigrationContent): content is SchemaMigration => typeof content !== 'function' && 'modifications' in content
13
+
14
+ export type ResolvedMigrationContent =
15
+ | SchemaMigration
16
+ | ContentMigration
17
+
18
+ export type MigrationContent =
19
+ | ResolvedMigrationContent
20
+ | ContentMigrationFactory
21
+
22
+ export type SchemaMigration =
23
+ & {
24
+ type: 'schema'
25
+ }
26
+ & Migration
27
+
28
+ export type ContentMigration = {
29
+ type: 'content'
30
+ version: string
31
+ name: string
32
+ queries: ContentMigrationQuery[]
33
+ }
34
+
35
+ export type ContentMigrationFactoryArgs = {
36
+ apiBaseUrl: string
37
+ projectName: string
38
+ apiToken: string
39
+ }
40
+
41
+ export type ContentMigrationFactory = {
42
+ type: 'factory'
43
+ factory: (args: ContentMigrationFactoryArgs) => Promise<ContentMigration>
44
+ }
45
+
46
+ export type ContentMigrationQuery = {
47
+ query: string
48
+ variables?: JSONObject
49
+ stage?: string
50
+ checkMutationResult?: boolean
51
+ }
52
+
@@ -0,0 +1,5 @@
1
+ import { MigrationContent, MigrationFile } from './MigrationFile'
2
+
3
+ export interface MigrationFileLoader {
4
+ load(file: MigrationFile): Promise<MigrationContent>
5
+ }
@@ -0,0 +1,102 @@
1
+ import { MigrationVersionHelper } from '@contember/engine-common'
2
+ import * as fs from 'node:fs/promises'
3
+ import { MigrationFileLoader } from './MigrationFileLoader'
4
+ import { MigrationContent, MigrationFile } from './MigrationFile'
5
+ import { join } from 'node:path'
6
+ import { Dirent } from 'node:fs'
7
+
8
+ export class MigrationFilesManager {
9
+ private migrations: null | Promise<MigrationFile[]> = null
10
+
11
+ constructor(
12
+ public readonly directory: string,
13
+ private readonly loaders: Record<string, MigrationFileLoader>,
14
+ ) {}
15
+
16
+ public async createFile(content: string, name: string, extension = 'json'): Promise<string> {
17
+ this.migrations = null
18
+ const path = this.formatPath(name, extension)
19
+ await fs.writeFile(path, content, { encoding: 'utf8' })
20
+ return await fs.realpath(path)
21
+ }
22
+
23
+ public async removeFile(name: string) {
24
+ this.migrations = null
25
+ const path = this.formatPath(name)
26
+ await fs.unlink(path)
27
+ }
28
+
29
+ public async moveFile(oldName: string, newName: string) {
30
+ this.migrations = null
31
+ await fs.rename(this.formatPath(oldName), this.formatPath(newName))
32
+ }
33
+
34
+ public async createDirIfNotExist(): Promise<void> {
35
+ try {
36
+ await fs.mkdir(this.directory)
37
+ } catch (e) {
38
+ if (!(e instanceof Error) || !('code' in e) || (e as any).code !== 'EEXIST') {
39
+ throw e
40
+ }
41
+ }
42
+ }
43
+
44
+ public async readFiles(): Promise<MigrationFile[]> {
45
+ return this.migrations ??= this.forceReadFiles()
46
+ }
47
+
48
+ public async forceReadFiles(): Promise<MigrationFile[]> {
49
+ const files = (await this.listFiles()).map(it => ({
50
+ filename: it,
51
+ version: MigrationVersionHelper.extractVersion(it),
52
+ }))
53
+
54
+ return files.map(({ version, filename }): MigrationFile => {
55
+ const ext = filename.substring(filename.lastIndexOf('.') + 1)
56
+ const path = join(this.directory, filename)
57
+ let content: Promise<MigrationContent> | null = null
58
+ const file: MigrationFile = {
59
+ path: path,
60
+ version: version,
61
+ name: MigrationVersionHelper.extractName(path.substring(path.lastIndexOf('/') + 1)),
62
+ getContent: () => {
63
+ content ??= this.loaders[ext].load(file)
64
+ return content
65
+ },
66
+ }
67
+ return file
68
+ })
69
+ }
70
+
71
+ private async tryReadDir(): Promise<Dirent[]> {
72
+ try {
73
+ return await fs.readdir(this.directory, { withFileTypes: true })
74
+ } catch (e) {
75
+ if (e instanceof Error && 'code' in e && (e as any).code === 'ENOENT') {
76
+ return []
77
+ }
78
+ throw e
79
+ }
80
+ }
81
+
82
+ private async listFiles(): Promise<string[]> {
83
+ const files = await this.tryReadDir()
84
+
85
+ const extensions = Object.keys(this.loaders).join('|')
86
+ const regex = new RegExp('^\\d{4}-\\d{2}-\\d{2}-\\d{6}-[\\w-]+\\.(' + extensions + ')$')
87
+ const filteredFiles = await Promise.all(
88
+ files
89
+ .filter(it => it.isFile())
90
+ .map(it => it.name)
91
+ .filter(it => it.match(regex)),
92
+ )
93
+
94
+ return filteredFiles.sort()
95
+ }
96
+
97
+ private formatPath(version: string, extension = 'json'): string {
98
+ const filename = `${version}.${extension}`
99
+ const path = `${this.directory}/${filename}`
100
+ return path
101
+ }
102
+ }
@@ -0,0 +1,32 @@
1
+ import { Migration, VERSION_INITIAL } from '@contember/schema-migrations'
2
+ import { ContentMigrationQuery, MigrationFile, ResolvedMigrationContent } from './MigrationFile'
3
+ import { JSONValue } from './utils/json'
4
+
5
+ export class MigrationParser {
6
+ public parse(migrationFile: MigrationFile, content: JSONValue): ResolvedMigrationContent {
7
+ if (typeof content !== 'object' || content === null) {
8
+ throw new Error('Invalid migration')
9
+ }
10
+
11
+ if ('modifications' in content) {
12
+ const validated = content as Migration // todo validate
13
+
14
+ return {
15
+ type: 'schema',
16
+ version: migrationFile.version,
17
+ name: migrationFile.name,
18
+ formatVersion: validated.formatVersion || VERSION_INITIAL,
19
+ modifications: validated.modifications,
20
+ ...(validated.skippedErrors ? { skippedErrors: validated.skippedErrors } : {}),
21
+ }
22
+ } else if ('queries' in content || 'query' in content) {
23
+ return {
24
+ type: 'content',
25
+ version: migrationFile.version,
26
+ name: migrationFile.name,
27
+ queries: ('queries' in content ? content.queries : [content]) as ContentMigrationQuery[],
28
+ }
29
+ }
30
+ throw new Error('Invalid migration content')
31
+ }
32
+ }
@@ -0,0 +1,44 @@
1
+ import { MigrationFilesManager } from './MigrationFilesManager'
2
+ import { Migration } from '@contember/schema-migrations'
3
+ import { isSchemaMigration, MigrationFile } from './MigrationFile'
4
+ import { MigrationVersionHelper } from '@contember/engine-common'
5
+
6
+ export class MigrationsResolver {
7
+
8
+ constructor(private readonly migrationFilesManager: MigrationFilesManager) {}
9
+
10
+ public get directory(): string {
11
+ return this.migrationFilesManager.directory
12
+ }
13
+
14
+ public async getMigrationFiles(): Promise<MigrationFile[]> {
15
+ return await this.migrationFilesManager.readFiles()
16
+ }
17
+
18
+ public async getSchemaMigrations(): Promise<Migration[]> {
19
+ const migrationFiles = await this.getMigrationFiles()
20
+
21
+ const migrations = await Promise.all(migrationFiles.map(this.convertMigrationFileToSchemaMigration))
22
+
23
+ return migrations.filter((it): it is Migration => it !== null)
24
+ }
25
+
26
+ public async findLatestSchemaMigration(): Promise<Migration | null> {
27
+ const migrations = await this.getSchemaMigrations()
28
+ return migrations.length > 0 ? migrations[migrations.length - 1] : null
29
+ }
30
+
31
+ public async findSchemaMigrationByVersion(version: string): Promise<Migration | null> {
32
+ const migrations = await this.getSchemaMigrations()
33
+ return migrations
34
+ .find(it =>
35
+ MigrationVersionHelper.extractVersion(it.version) === MigrationVersionHelper.extractVersion(version),
36
+ )
37
+ ?? null
38
+ }
39
+
40
+ private async convertMigrationFileToSchemaMigration(file: MigrationFile): Promise<Migration | null> {
41
+ const result = await file.getContent()
42
+ return isSchemaMigration(result) ? result : null
43
+ }
44
+ }
@@ -0,0 +1,33 @@
1
+ import { Schema } from '@contember/schema'
2
+ import { emptySchema } from '@contember/schema-utils'
3
+ import { SchemaMigrator } from '@contember/schema-migrations'
4
+ import { MigrationsResolver } from './MigrationsResolver'
5
+
6
+ export class SchemaVersionBuilder {
7
+ constructor(
8
+ private readonly migrationsResolver: MigrationsResolver,
9
+ private readonly schemaMigrator: SchemaMigrator,
10
+ ) {}
11
+
12
+ async buildSchema(targetVersion?: string): Promise<Schema> {
13
+ return this.buildSchemaAdvanced(emptySchema, version => !targetVersion || version <= targetVersion)
14
+ }
15
+
16
+ async buildSchemaUntil(targetVersion: string): Promise<Schema> {
17
+ return this.buildSchemaAdvanced(emptySchema, version => version < targetVersion)
18
+ }
19
+
20
+ async continue(schema: Schema, previousVersion: string | null, targetVersion: string): Promise<Schema> {
21
+ return this.buildSchemaAdvanced(schema, version => version <= targetVersion && version > (previousVersion || ''))
22
+ }
23
+
24
+ public async buildSchemaAdvanced(initialSchema: Schema, condition: (version: string) => boolean): Promise<Schema> {
25
+ return (await this.migrationsResolver.getSchemaMigrations())
26
+ .filter(({ version }) => condition(version))
27
+ .reduce<Schema>(
28
+ (schema, { modifications, formatVersion }) =>
29
+ this.schemaMigrator.applyModifications(schema, modifications, formatVersion),
30
+ initialSchema,
31
+ )
32
+ }
33
+ }
@@ -0,0 +1,150 @@
1
+ import { GraphQLClient } from 'graphql-request'
2
+ import { ExecutedMigration, ExecutedMigrationInfo } from './migrations'
3
+
4
+ export type MigrateError = {
5
+ readonly code: MigrateErrorCode
6
+ readonly migration: string
7
+ readonly message: string
8
+ }
9
+
10
+ export enum MigrateErrorCode {
11
+ MustFollowLatest = 'MUST_FOLLOW_LATEST',
12
+ AlreadyExecuted = 'ALREADY_EXECUTED',
13
+ InvalidFormat = 'INVALID_FORMAT',
14
+ InvalidSchema = 'INVALID_SCHEMA',
15
+ MigrationFailed = 'MIGRATION_FAILED',
16
+ }
17
+
18
+ export type MigrateResponse = {
19
+ readonly ok: boolean
20
+ readonly errors: MigrateError[]
21
+ }
22
+
23
+ export class SystemClient {
24
+ constructor(private readonly apiClient: GraphQLClient) {}
25
+
26
+ public static create(baseUrl: string, projectName: string, apiToken: string): SystemClient {
27
+ const graphqlClient = new GraphQLClient(createSystemUrl(baseUrl, projectName), {
28
+ headers: {
29
+ Authorization: `Bearer ${apiToken}`,
30
+ },
31
+ })
32
+ return new SystemClient(graphqlClient)
33
+ }
34
+
35
+ public async migrate(migrations: any[], force = false): Promise<void> {
36
+ const query = `
37
+ mutation($migrations: [Migration!]!) {
38
+ migrate: ${force ? 'forceMigrate' : 'migrate'}(migrations: $migrations) {
39
+ ok
40
+ errors {
41
+ code
42
+ migration
43
+ message: developerMessage
44
+ }
45
+ }
46
+ }
47
+ `
48
+ const result = (
49
+ await this.apiClient.request<{
50
+ migrate: { ok: boolean; errors: { code: MigrateErrorCode; migration: string; message: string }[] }
51
+ }>(query, {
52
+ migrations,
53
+ })
54
+ ).migrate
55
+ if (!result.ok) {
56
+ throw result.errors
57
+ }
58
+ }
59
+
60
+ public async migrationDelete(version: string): Promise<void> {
61
+ const query = `
62
+ mutation($version: String!) {
63
+ migrationDelete(migration: $version) {
64
+ ok
65
+ error {
66
+ developerMessage
67
+ }
68
+ }
69
+ }
70
+ `
71
+ const result = (
72
+ await this.apiClient.request<{
73
+ migrationDelete: { ok: boolean; error?: { developerMessage: string } }
74
+ }>(query, {
75
+ version,
76
+ })
77
+ ).migrationDelete
78
+ if (!result.ok) {
79
+ throw result.error?.developerMessage
80
+ }
81
+ }
82
+
83
+ public async migrationModify(version: string, modification: Partial<any>): Promise<void> {
84
+ const query = `
85
+ mutation($version: String!, $modification: MigrationModification!) {
86
+ migrationModify(migration: $version, modification: $modification) {
87
+ ok
88
+ error {
89
+ developerMessage
90
+ }
91
+ }
92
+ }
93
+ `
94
+ const result = (
95
+ await this.apiClient.request<{
96
+ migrationModify: { ok: boolean; error?: { developerMessage: string } }
97
+ }>(query, {
98
+ version,
99
+ modification,
100
+ })
101
+ ).migrationModify
102
+ if (!result.ok) {
103
+ throw result.error?.developerMessage
104
+ }
105
+ }
106
+
107
+ public async listExecutedMigrations(): Promise<ExecutedMigrationInfo[]> {
108
+ const query = `query {
109
+ executedMigrations {
110
+ name
111
+ version
112
+ formatVersion
113
+ checksum
114
+ executedAt
115
+ }
116
+ }`
117
+ return (
118
+ await this.apiClient.request<{
119
+ executedMigrations: ExecutedMigrationInfo[]
120
+ }>(query, {})
121
+ ).executedMigrations.map(it => ({ ...it, executedAt: new Date(it.executedAt) }))
122
+ }
123
+
124
+ public async getExecutedMigration(version: string): Promise<ExecutedMigration> {
125
+ const query = `query($version: String!) {
126
+ executedMigrations(version: $version) {
127
+ name
128
+ version
129
+ formatVersion
130
+ checksum
131
+ executedAt
132
+ modifications
133
+ }
134
+ }`
135
+ return (
136
+ (
137
+ await this.apiClient.request<{
138
+ executedMigrations: ExecutedMigration[]
139
+ }>(query, { version })
140
+ ).executedMigrations.map(it => ({ ...it, executedAt: new Date(it.executedAt) }))[0] || null
141
+ )
142
+ }
143
+ }
144
+
145
+ const createSystemUrl = (baseUrl: string, projectName: string) => {
146
+ if (baseUrl.endsWith('/')) {
147
+ baseUrl = baseUrl.substring(0, baseUrl.length - 1)
148
+ }
149
+ return baseUrl + '/system/' + projectName
150
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ export {
2
+ Migration,
3
+ MigrationDescriber,
4
+ MigrationInfo,
5
+ ModificationHandlerFactory,
6
+ SchemaDiffer,
7
+ SchemaMigrator,
8
+ VERSION_LATEST,
9
+ calculateMigrationChecksum,
10
+ } from '@contember/schema-migrations'
11
+
12
+ export * from './JsLoader'
13
+ export * from './JsonLoader'
14
+ export * from './MigrationCreator'
15
+ export * from './MigrationExecuteHelper'
16
+ export * from './MigrationFile'
17
+ export * from './MigrationFileLoader'
18
+ export * from './MigrationFilesManager'
19
+ export * from './MigrationParser'
20
+ export * from './migrations'
21
+ export * from './MigrationsResolver'
22
+ export * from './SchemaVersionBuilder'
23
+ export * from './SystemClient'
@@ -0,0 +1,157 @@
1
+ import { calculateMigrationChecksum, Migration, MigrationInfo } from '@contember/schema-migrations'
2
+ import { isSchemaMigration, MigrationFile } from './MigrationFile'
3
+
4
+ export interface ExecutedMigrationInfo extends MigrationInfo {
5
+ readonly executedAt: Date
6
+ readonly checksum: string
7
+ }
8
+
9
+ export interface ExecutedMigration extends Migration {
10
+ readonly executedAt: Date
11
+ readonly checksum: string
12
+ }
13
+
14
+ export enum MigrationState {
15
+ TO_EXECUTE_OK = 'to_execute_ok',
16
+ TO_EXECUTE_ERROR = 'to_execute_error',
17
+ EXECUTED_OK = 'executed_ok',
18
+ EXECUTED_ERROR = 'executed_error',
19
+ EXECUTED_MISSING = 'executed_missing',
20
+ }
21
+
22
+ export interface MigrationStatusBase<S extends MigrationState> {
23
+ state: S
24
+ version: string
25
+ name: string
26
+ }
27
+
28
+ export interface MigrationToExecuteOkStatus extends MigrationStatusBase<MigrationState.TO_EXECUTE_OK> {
29
+ localMigration: MigrationFile
30
+ }
31
+
32
+ interface MigrationToExecuteInvalidStatus extends MigrationStatusBase<MigrationState.TO_EXECUTE_ERROR> {
33
+ localMigration: MigrationFile
34
+ errorMessage: string
35
+ }
36
+
37
+ interface MigrationExecutedOkStatus extends MigrationStatusBase<MigrationState.EXECUTED_OK>{
38
+ executedMigration: ExecutedMigrationInfo
39
+ localMigration: MigrationFile
40
+ }
41
+
42
+ interface MigrationExecutedMissingStatus extends MigrationStatusBase<MigrationState.EXECUTED_MISSING>{
43
+ executedMigration: ExecutedMigrationInfo
44
+ errorMessage: string
45
+ }
46
+
47
+ interface MigrationExecutedErrorStatus extends MigrationStatusBase<MigrationState.EXECUTED_ERROR>{
48
+ executedMigration: ExecutedMigrationInfo
49
+ localMigration: MigrationFile
50
+ errorMessage: string
51
+ }
52
+
53
+ export type ErrorMigrationStatus =
54
+ | MigrationToExecuteInvalidStatus
55
+ | MigrationExecutedMissingStatus
56
+ | MigrationExecutedErrorStatus
57
+
58
+ export type AnyMigrationStatus =
59
+ | ErrorMigrationStatus
60
+ | MigrationToExecuteOkStatus
61
+ | MigrationExecutedOkStatus
62
+
63
+ const isErrorMigrationStatus = (migration: AnyMigrationStatus): migration is ErrorMigrationStatus =>
64
+ [MigrationState.EXECUTED_ERROR, MigrationState.EXECUTED_MISSING, MigrationState.TO_EXECUTE_ERROR].includes(
65
+ migration.state,
66
+ )
67
+
68
+ export const sortMigrations = <M extends { version: string }>(migrations: M[]): M[] => {
69
+ return [...migrations].sort((a, b) => a.version.localeCompare(b.version))
70
+ }
71
+
72
+ export class MigrationsStatusResolver {
73
+
74
+ async getMigrationsStatus(
75
+ executedMigrations: ExecutedMigrationInfo[],
76
+ localMigrations: MigrationFile[],
77
+ force: boolean = false,
78
+ ): Promise<{
79
+ allMigrations: AnyMigrationStatus[]
80
+ errorMigrations: ErrorMigrationStatus[]
81
+ migrationsToExecute: MigrationToExecuteOkStatus[]
82
+ }> {
83
+ const sortedExecuted = sortMigrations(executedMigrations)
84
+
85
+ const allMigrations: AnyMigrationStatus[] = []
86
+ for (const executed of sortedExecuted) {
87
+ const localMigration = localMigrations.find(it => it.version === executed.version)
88
+ if (!localMigration) {
89
+ allMigrations.push({
90
+ state: MigrationState.EXECUTED_MISSING,
91
+ executedMigration: executed,
92
+ version: executed.version,
93
+ name: executed.name,
94
+ errorMessage: `Migration ${executed.name} is missing locally`,
95
+ })
96
+ continue
97
+ }
98
+ const migrationContent = await localMigration.getContent()
99
+
100
+ const localChecksum = !isSchemaMigration(migrationContent) ? null : calculateMigrationChecksum(migrationContent)
101
+
102
+ if (localChecksum !== executed.checksum) {
103
+ allMigrations.push({
104
+ state: MigrationState.EXECUTED_ERROR,
105
+ executedMigration: executed,
106
+ version: executed.version,
107
+ name: executed.name,
108
+ localMigration,
109
+ errorMessage: `Checksum of executed migration "${executed.checksum}" does not match "${localChecksum}"`,
110
+ })
111
+ continue
112
+ }
113
+ allMigrations.push({
114
+ state: MigrationState.EXECUTED_OK,
115
+ executedMigration: executed,
116
+ version: executed.version,
117
+ name: executed.name,
118
+ localMigration,
119
+ })
120
+ }
121
+
122
+ const latestExecuted = sortedExecuted.length > 0 ? sortedExecuted[sortedExecuted.length - 1] : null
123
+ for (const localMigration of localMigrations) {
124
+ if (allMigrations.find(it => it.version === localMigration.version)) {
125
+ continue
126
+ }
127
+
128
+ if (latestExecuted && latestExecuted.version > localMigration.version) {
129
+ allMigrations.push({
130
+ state: MigrationState.TO_EXECUTE_ERROR,
131
+ localMigration,
132
+ version: localMigration.version,
133
+ name: localMigration.name,
134
+ errorMessage: `Must follow latest executed migration ${latestExecuted.name}`,
135
+ })
136
+ continue
137
+ }
138
+ allMigrations.push({
139
+ state: MigrationState.TO_EXECUTE_OK,
140
+ localMigration,
141
+ version: localMigration.version,
142
+ name: localMigration.name,
143
+ })
144
+ }
145
+ const allMigrationsSorted = sortMigrations(allMigrations)
146
+
147
+ return {
148
+ allMigrations: allMigrationsSorted,
149
+ errorMigrations: allMigrationsSorted.filter(isErrorMigrationStatus),
150
+ migrationsToExecute: allMigrationsSorted.filter(
151
+ (it): it is MigrationToExecuteOkStatus =>
152
+ it.state === MigrationState.TO_EXECUTE_OK || (force && it.state === MigrationState.TO_EXECUTE_ERROR),
153
+ ),
154
+ }
155
+ }
156
+
157
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../tsconfig.settings.json",
3
+ "compilerOptions": {
4
+ "esModuleInterop": true,
5
+ "outDir": "../dist/src"
6
+ },
7
+ "references": [
8
+ { "path": "../../engine-common/src" },
9
+ { "path": "../../schema/src" },
10
+ { "path": "../../schema-migrations/src" },
11
+ { "path": "../../schema-utils/src" }
12
+ ]
13
+ }
@@ -0,0 +1,3 @@
1
+ export function assertNever(_: never): never {
2
+ throw new Error()
3
+ }
@@ -0,0 +1,4 @@
1
+ export type JSONPrimitive = string | number | boolean | null | undefined | unknown
2
+ export type JSONValue = JSONPrimitive | JSONObject | JSONArray
3
+ export type JSONObject = { [member: string]: JSONValue }
4
+ export type JSONArray = JSONValue[]
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "files": [],
3
+ "include": [],
4
+ "references": [{ "path": "./src" }]
5
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../tsconfig.settings.json"
3
+ }