revisium 2.0.1 → 2.2.0
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.
- package/.github/workflows/ci.yml +32 -0
- package/README.md +23 -3
- package/dist/e2e/setup/global-setup.js +2 -3
- package/dist/e2e/setup/global-setup.js.map +1 -1
- package/dist/e2e/setup/global-teardown.js +0 -2
- package/dist/e2e/setup/global-teardown.js.map +1 -1
- package/dist/e2e/utils/constants.d.ts +0 -2
- package/dist/e2e/utils/constants.js +0 -2
- package/dist/e2e/utils/constants.js.map +1 -1
- package/dist/e2e/utils/docker-helper.d.ts +0 -2
- package/dist/e2e/utils/docker-helper.js +0 -13
- package/dist/e2e/utils/docker-helper.js.map +1 -1
- package/dist/package.json +27 -14
- package/dist/src/commands/migration/apply-migrations.command.d.ts +1 -1
- package/dist/src/commands/migration/apply-migrations.command.js +4 -5
- package/dist/src/commands/migration/apply-migrations.command.js.map +1 -1
- package/dist/src/commands/migration/save-migrations.command.d.ts +1 -1
- package/dist/src/commands/migration/save-migrations.command.js +6 -6
- package/dist/src/commands/migration/save-migrations.command.js.map +1 -1
- package/dist/src/commands/rows/save-rows.command.d.ts +1 -1
- package/dist/src/commands/rows/save-rows.command.js +12 -10
- package/dist/src/commands/rows/save-rows.command.js.map +1 -1
- package/dist/src/commands/rows/upload-rows.command.d.ts +1 -1
- package/dist/src/commands/rows/upload-rows.command.js +12 -15
- package/dist/src/commands/rows/upload-rows.command.js.map +1 -1
- package/dist/src/commands/schema/create-migrations.command.js.map +1 -1
- package/dist/src/commands/schema/save-schema.command.d.ts +1 -1
- package/dist/src/commands/schema/save-schema.command.js +7 -7
- package/dist/src/commands/schema/save-schema.command.js.map +1 -1
- package/dist/src/config/meta-schema.d.ts +3 -0
- package/dist/src/config/meta-schema.js +43 -1
- package/dist/src/config/meta-schema.js.map +1 -1
- package/dist/src/services/connection/api-client-adapter.d.ts +2 -4
- package/dist/src/services/connection/api-client-adapter.js +40 -36
- package/dist/src/services/connection/api-client-adapter.js.map +1 -1
- package/dist/src/services/connection/api-client.d.ts +3 -4
- package/dist/src/services/connection/api-client.js +11 -33
- package/dist/src/services/connection/api-client.js.map +1 -1
- package/dist/src/services/connection/connection-factory.service.d.ts +4 -6
- package/dist/src/services/connection/connection-factory.service.js +18 -39
- package/dist/src/services/connection/connection-factory.service.js.map +1 -1
- package/dist/src/services/connection/connection.service.d.ts +2 -73
- package/dist/src/services/connection/connection.service.js +2 -11
- package/dist/src/services/connection/connection.service.js.map +1 -1
- package/dist/src/services/sync/commit-revision.service.js +6 -28
- package/dist/src/services/sync/commit-revision.service.js.map +1 -1
- package/dist/src/services/sync/row-sync.service.d.ts +5 -5
- package/dist/src/services/sync/row-sync.service.js +10 -10
- package/dist/src/services/sync/row-sync.service.js.map +1 -1
- package/dist/src/services/sync/sync-data.service.d.ts +1 -0
- package/dist/src/services/sync/sync-data.service.js +21 -21
- package/dist/src/services/sync/sync-data.service.js.map +1 -1
- package/dist/src/services/sync/sync-schema.service.d.ts +1 -0
- package/dist/src/services/sync/sync-schema.service.js +11 -10
- package/dist/src/services/sync/sync-schema.service.js.map +1 -1
- package/dist/src/services/url/auth-prompt.service.js +1 -1
- package/dist/src/services/url/auth-prompt.service.js.map +1 -1
- package/dist/src/types/migration.types.d.ts +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/authentication.md +3 -3
- package/docs/configuration.md +58 -9
- package/docs/docker-deployment.md +48 -13
- package/docs/migrate-commands.md +35 -10
- package/docs/rows-commands.md +30 -7
- package/docs/schema-commands.md +21 -5
- package/docs/sync-commands.md +44 -12
- package/docs/url-format.md +2 -2
- package/e2e/setup/global-setup.ts +3 -9
- package/e2e/setup/global-teardown.ts +0 -6
- package/e2e/utils/constants.ts +0 -2
- package/e2e/utils/docker-helper.ts +0 -23
- package/package.json +27 -14
- package/src/commands/migration/apply-migrations.command.ts +5 -6
- package/src/commands/migration/save-migrations.command.ts +7 -6
- package/src/commands/rows/save-rows.command.ts +14 -28
- package/src/commands/rows/upload-rows.command.ts +7 -15
- package/src/commands/schema/create-migrations.command.ts +1 -1
- package/src/commands/schema/save-schema.command.ts +9 -14
- package/src/config/meta-schema.ts +47 -0
- package/src/services/connection/__tests__/connection-factory.service.spec.ts +117 -0
- package/src/services/connection/__tests__/connection.service.spec.ts +27 -117
- package/src/services/connection/api-client-adapter.ts +41 -45
- package/src/services/connection/api-client.ts +11 -50
- package/src/services/connection/connection-factory.service.ts +35 -65
- package/src/services/connection/connection.service.ts +3 -14
- package/src/services/sync/__tests__/row-sync.service.spec.ts +3 -6
- package/src/services/sync/commit-revision.service.ts +7 -51
- package/src/services/sync/row-sync.service.ts +4 -18
- package/src/services/sync/sync-data.service.ts +32 -45
- package/src/services/sync/sync-schema.service.ts +14 -22
- package/src/services/url/auth-prompt.service.ts +1 -1
- package/src/types/migration.types.ts +2 -2
- package/dist/src/__generated__/api.d.ts +0 -688
- package/dist/src/__generated__/api.js +0 -698
- package/dist/src/__generated__/api.js.map +0 -1
- package/e2e/docker-compose.e2e.yml +0 -31
- package/src/__generated__/api.ts +0 -2598
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "revisium",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"homepage": "https://revisium.io",
|
|
5
5
|
"description": "A CLI tool for interacting with Revisium instances, providing migration management, schema export, and data export capabilities.",
|
|
6
6
|
"author": "Anton Kashirov",
|
|
@@ -27,13 +27,11 @@
|
|
|
27
27
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
28
28
|
"test:e2e": "jest --config ./e2e/jest-e2e.json",
|
|
29
29
|
"test:e2e:cov": "npm run build:instrumented && E2E_INSTRUMENTED=1 jest --config ./e2e/jest-e2e.json",
|
|
30
|
+
"test:e2e:up": "FILE_PLUGIN_PUBLIC_ENDPOINT=https://cdn.example.com npx @revisium/standalone --auth --port 8082 --data .e2e-test & echo $! > .e2e-test.pid",
|
|
31
|
+
"test:e2e:down": "if [ -f .e2e-test.pid ]; then kill $(cat .e2e-test.pid) 2>/dev/null; rm -f .e2e-test.pid; fi; rm -rf .e2e-test",
|
|
30
32
|
"build:instrumented": "npm run build && rm -rf dist-instrumented && mkdir -p dist-instrumented && cp dist/package.json dist-instrumented/ && npx nyc instrument dist/src dist-instrumented/src --include='**/*.js'",
|
|
31
33
|
"test:all": "npm run test:cov && npm run test:e2e:cov && npm run coverage:merge",
|
|
32
|
-
"coverage:merge": "node scripts/merge-coverage.js"
|
|
33
|
-
"docker:e2e:up": "docker-compose -f e2e/docker-compose.e2e.yml up -d",
|
|
34
|
-
"docker:e2e:down": "docker-compose -f e2e/docker-compose.e2e.yml down -v",
|
|
35
|
-
"docker:e2e:logs": "docker-compose -f e2e/docker-compose.e2e.yml logs -f",
|
|
36
|
-
"generate:api": "npx swagger-typescript-api generate -p https://cloud.revisium.io/api-json -o src/__generated__ -n api.ts --extract-request-params --disable-throw-on-error"
|
|
34
|
+
"coverage:merge": "node scripts/merge-coverage.js"
|
|
37
35
|
},
|
|
38
36
|
"bin": {
|
|
39
37
|
"revisium": "dist/src/main.js"
|
|
@@ -44,22 +42,24 @@
|
|
|
44
42
|
"@nestjs/config": "^4.0.2",
|
|
45
43
|
"@nestjs/core": "^11.0.1",
|
|
46
44
|
"@nestjs/platform-express": "^11.0.1",
|
|
45
|
+
"@revisium/client": "^0.4.0",
|
|
47
46
|
"@revisium/schema-toolkit": "^0.4.1",
|
|
48
47
|
"@types/object-hash": "^3.0.6",
|
|
49
|
-
"ajv": "^8.
|
|
48
|
+
"ajv": "^8.18.0",
|
|
50
49
|
"ajv-formats": "^3.0.1",
|
|
51
50
|
"nest-commander": "^3.18.0",
|
|
52
51
|
"object-hash": "^3.0.0",
|
|
53
52
|
"reflect-metadata": "^0.2.2",
|
|
54
|
-
"rxjs": "^7.8.1"
|
|
55
|
-
"swagger-typescript-api": "^13.2.7"
|
|
53
|
+
"rxjs": "^7.8.1"
|
|
56
54
|
},
|
|
57
55
|
"devDependencies": {
|
|
58
56
|
"@eslint/eslintrc": "^3.2.0",
|
|
59
57
|
"@eslint/js": "^9.18.0",
|
|
58
|
+
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
|
60
59
|
"@nestjs/cli": "^11.0.0",
|
|
61
60
|
"@nestjs/schematics": "^11.0.0",
|
|
62
61
|
"@nestjs/testing": "^11.0.1",
|
|
62
|
+
"@revisium/standalone": "2.6.0",
|
|
63
63
|
"@swc/cli": "^0.6.0",
|
|
64
64
|
"@swc/core": "^1.10.7",
|
|
65
65
|
"@types/express": "^5.0.0",
|
|
@@ -69,9 +69,10 @@
|
|
|
69
69
|
"eslint": "^9.18.0",
|
|
70
70
|
"eslint-config-prettier": "^10.0.1",
|
|
71
71
|
"eslint-plugin-prettier": "^5.2.2",
|
|
72
|
-
"eslint-plugin-sonarjs": "^
|
|
72
|
+
"eslint-plugin-sonarjs": "^4.0.2",
|
|
73
73
|
"globals": "^16.0.0",
|
|
74
74
|
"jest": "^29.7.0",
|
|
75
|
+
"nyc": "^17.1.0",
|
|
75
76
|
"prettier": "^3.4.2",
|
|
76
77
|
"source-map-support": "^0.5.21",
|
|
77
78
|
"supertest": "^7.0.0",
|
|
@@ -80,9 +81,22 @@
|
|
|
80
81
|
"ts-node": "^10.9.2",
|
|
81
82
|
"tsconfig-paths": "^4.2.0",
|
|
82
83
|
"typescript": "^5.7.3",
|
|
83
|
-
"typescript-eslint": "^8.20.0"
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
"typescript-eslint": "^8.20.0"
|
|
85
|
+
},
|
|
86
|
+
"overrides": {
|
|
87
|
+
"hono": "^4.12.7",
|
|
88
|
+
"@hono/node-server": "^1.19.10",
|
|
89
|
+
"lodash": "^4.17.23",
|
|
90
|
+
"@angular-devkit/core": {
|
|
91
|
+
"ajv": "^8.18.0"
|
|
92
|
+
},
|
|
93
|
+
"schema-utils@3": {
|
|
94
|
+
"ajv": "^6.14.0"
|
|
95
|
+
},
|
|
96
|
+
"schema-utils@4": {
|
|
97
|
+
"ajv": "^8.18.0"
|
|
98
|
+
},
|
|
99
|
+
"file-type": "^21.3.2"
|
|
86
100
|
},
|
|
87
101
|
"jest": {
|
|
88
102
|
"modulePaths": [
|
|
@@ -102,7 +116,6 @@
|
|
|
102
116
|
"<rootDir>/src/**/*.(t|j)s",
|
|
103
117
|
"!<rootDir>/src/main.ts",
|
|
104
118
|
"!<rootDir>/src/app.module.ts",
|
|
105
|
-
"!<rootDir>/src/__generated__/**",
|
|
106
119
|
"!<rootDir>/src/**/__tests__/**",
|
|
107
120
|
"!<rootDir>/src/**/index.ts"
|
|
108
121
|
],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { Option, SubCommand } from 'nest-commander';
|
|
3
|
+
import { RevisionScope } from '@revisium/client';
|
|
3
4
|
import { BaseCommand, BaseOptions } from 'src/commands/base.command';
|
|
4
5
|
import { ConnectionService } from 'src/services/connection';
|
|
5
6
|
import { JsonValidatorService, LoggerService } from 'src/services/common';
|
|
@@ -61,8 +62,6 @@ export class ApplyMigrationsCommand extends BaseCommand {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
private async applyMigration(migrations: Migration[]) {
|
|
64
|
-
const revisionId = this.connectionService.draftRevisionId;
|
|
65
|
-
|
|
66
65
|
if (migrations.length === 0) {
|
|
67
66
|
this.logger.success(
|
|
68
67
|
'No migrations to apply - all migrations are up to date',
|
|
@@ -76,11 +75,11 @@ export class ApplyMigrationsCommand extends BaseCommand {
|
|
|
76
75
|
|
|
77
76
|
try {
|
|
78
77
|
for (const localMigration of migrations) {
|
|
79
|
-
const
|
|
78
|
+
const results = await this.revisionScope.applyMigrationsWithStatus([
|
|
80
79
|
localMigration,
|
|
81
80
|
]);
|
|
82
81
|
|
|
83
|
-
const response =
|
|
82
|
+
const response = results[0];
|
|
84
83
|
|
|
85
84
|
if (response.status === 'failed') {
|
|
86
85
|
this.logger.migrationFailed(response);
|
|
@@ -114,8 +113,8 @@ export class ApplyMigrationsCommand extends BaseCommand {
|
|
|
114
113
|
return countAppliedMigrations;
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
private get
|
|
118
|
-
return this.connectionService.
|
|
116
|
+
private get revisionScope(): RevisionScope {
|
|
117
|
+
return this.connectionService.revisionScope;
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
@Option({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
2
|
import { Option, SubCommand } from 'nest-commander';
|
|
3
|
+
import { RevisionScope } from '@revisium/client';
|
|
3
4
|
import { BaseCommand, BaseOptions } from 'src/commands/base.command';
|
|
4
5
|
import { ConnectionService } from 'src/services/connection';
|
|
5
6
|
import { LoggerService } from 'src/services/common';
|
|
@@ -26,14 +27,14 @@ export class SaveMigrationsCommand extends BaseCommand {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
await this.connectionService.connect(options);
|
|
29
|
-
await this.saveFile(
|
|
30
|
+
await this.saveFile(options.file);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
private async saveFile(
|
|
33
|
+
private async saveFile(filePath: string) {
|
|
33
34
|
try {
|
|
34
|
-
const
|
|
35
|
+
const migrations = await this.revisionScope.getMigrations();
|
|
35
36
|
|
|
36
|
-
await writeFile(filePath, JSON.stringify(
|
|
37
|
+
await writeFile(filePath, JSON.stringify(migrations, null, 2), 'utf-8');
|
|
37
38
|
|
|
38
39
|
this.logger.success(`Save migrations to: ${filePath}`);
|
|
39
40
|
} catch (error) {
|
|
@@ -53,7 +54,7 @@ export class SaveMigrationsCommand extends BaseCommand {
|
|
|
53
54
|
return value;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
private get
|
|
57
|
-
return this.connectionService.
|
|
57
|
+
private get revisionScope(): RevisionScope {
|
|
58
|
+
return this.connectionService.revisionScope;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { Option, SubCommand } from 'nest-commander';
|
|
4
|
+
import { RevisionScope } from '@revisium/client';
|
|
4
5
|
import { BaseCommand, BaseOptions } from 'src/commands/base.command';
|
|
5
6
|
import { ConnectionService } from 'src/services/connection';
|
|
6
7
|
import { LoggerService } from 'src/services/common';
|
|
@@ -32,30 +33,19 @@ export class SaveRowsCommand extends BaseCommand {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
await this.connectionService.connect(options);
|
|
35
|
-
await this.saveAllTableRows(
|
|
36
|
-
this.connectionService.revisionId,
|
|
37
|
-
options.folder,
|
|
38
|
-
options.tables,
|
|
39
|
-
);
|
|
36
|
+
await this.saveAllTableRows(options.folder, options.tables);
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
private async saveAllTableRows(
|
|
43
|
-
revisionId: string,
|
|
44
|
-
folderPath: string,
|
|
45
|
-
tableFilter?: string,
|
|
46
|
-
) {
|
|
39
|
+
private async saveAllTableRows(folderPath: string, tableFilter?: string) {
|
|
47
40
|
try {
|
|
48
41
|
await mkdir(folderPath, { recursive: true });
|
|
49
42
|
|
|
50
|
-
const tablesToProcess = await this.getTargetTables(
|
|
51
|
-
revisionId,
|
|
52
|
-
tableFilter,
|
|
53
|
-
);
|
|
43
|
+
const tablesToProcess = await this.getTargetTables(tableFilter);
|
|
54
44
|
|
|
55
45
|
this.logger.foundItems(tablesToProcess.length, 'tables to process');
|
|
56
46
|
|
|
57
47
|
for (const tableId of tablesToProcess) {
|
|
58
|
-
await this.saveRowsFromTable(
|
|
48
|
+
await this.saveRowsFromTable(tableId, folderPath);
|
|
59
49
|
}
|
|
60
50
|
|
|
61
51
|
this.logger.summary(
|
|
@@ -69,26 +59,19 @@ export class SaveRowsCommand extends BaseCommand {
|
|
|
69
59
|
}
|
|
70
60
|
}
|
|
71
61
|
|
|
72
|
-
private async getTargetTables(
|
|
73
|
-
revisionId: string,
|
|
74
|
-
tableFilter?: string,
|
|
75
|
-
): Promise<string[]> {
|
|
62
|
+
private async getTargetTables(tableFilter?: string): Promise<string[]> {
|
|
76
63
|
if (tableFilter) {
|
|
77
64
|
return tableFilter.split(',').map((id) => id.trim());
|
|
78
65
|
}
|
|
79
66
|
|
|
80
67
|
const { items } = await fetchAllPages((params) =>
|
|
81
|
-
this.
|
|
68
|
+
this.revisionScope.getTables(params).then((data) => ({ data })),
|
|
82
69
|
);
|
|
83
70
|
|
|
84
71
|
return items.map((table) => table.id);
|
|
85
72
|
}
|
|
86
73
|
|
|
87
|
-
private async saveRowsFromTable(
|
|
88
|
-
revisionId: string,
|
|
89
|
-
tableId: string,
|
|
90
|
-
folderPath: string,
|
|
91
|
-
) {
|
|
74
|
+
private async saveRowsFromTable(tableId: string, folderPath: string) {
|
|
92
75
|
try {
|
|
93
76
|
this.logger.processingTable(tableId);
|
|
94
77
|
|
|
@@ -96,7 +79,10 @@ export class SaveRowsCommand extends BaseCommand {
|
|
|
96
79
|
await mkdir(tableFolderPath, { recursive: true });
|
|
97
80
|
|
|
98
81
|
const { processed, total } = await fetchAndProcessPages(
|
|
99
|
-
(params) =>
|
|
82
|
+
(params) =>
|
|
83
|
+
this.revisionScope
|
|
84
|
+
.getRows(tableId, params)
|
|
85
|
+
.then((data) => ({ data })),
|
|
100
86
|
async (row) => {
|
|
101
87
|
const filePath = join(tableFolderPath, `${row.id}.json`);
|
|
102
88
|
await writeFile(filePath, JSON.stringify(row, null, 2), 'utf-8');
|
|
@@ -119,8 +105,8 @@ export class SaveRowsCommand extends BaseCommand {
|
|
|
119
105
|
}
|
|
120
106
|
}
|
|
121
107
|
|
|
122
|
-
private get
|
|
123
|
-
return this.connectionService.
|
|
108
|
+
private get revisionScope(): RevisionScope {
|
|
109
|
+
return this.connectionService.revisionScope;
|
|
124
110
|
}
|
|
125
111
|
|
|
126
112
|
@Option({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Option, SubCommand } from 'nest-commander';
|
|
2
|
+
import { RevisionScope } from '@revisium/client';
|
|
2
3
|
import { BaseCommand, BaseOptions } from 'src/commands/base.command';
|
|
3
4
|
import {
|
|
4
5
|
ConnectionService,
|
|
@@ -57,11 +58,9 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
await this.connectionService.connect(options);
|
|
60
|
-
const revisionId = this.connectionService.draftRevisionId;
|
|
61
61
|
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
62
62
|
|
|
63
63
|
const totalStats = await this.uploadAllTableRows(
|
|
64
|
-
revisionId,
|
|
65
64
|
options.folder,
|
|
66
65
|
options.tables,
|
|
67
66
|
batchSize,
|
|
@@ -76,7 +75,6 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
private async uploadAllTableRows(
|
|
79
|
-
revisionId: string,
|
|
80
78
|
folderPath: string,
|
|
81
79
|
tableFilter: string | undefined,
|
|
82
80
|
batchSize: number,
|
|
@@ -87,7 +85,7 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
87
85
|
);
|
|
88
86
|
this.logger.foundItems(tableIds.length, 'tables to process');
|
|
89
87
|
|
|
90
|
-
const tableSchemas = await this.fetchTableSchemas(
|
|
88
|
+
const tableSchemas = await this.fetchTableSchemas(tableIds);
|
|
91
89
|
const sortedTables = this.getSortedTables(tableSchemas, tableIds);
|
|
92
90
|
|
|
93
91
|
const totalStats = createEmptyUploadStats();
|
|
@@ -95,7 +93,6 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
95
93
|
for (const tableId of sortedTables) {
|
|
96
94
|
try {
|
|
97
95
|
const tableStats = await this.uploadTableRows(
|
|
98
|
-
revisionId,
|
|
99
96
|
tableId,
|
|
100
97
|
folderPath,
|
|
101
98
|
tableSchemas[tableId],
|
|
@@ -114,17 +111,14 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
private async fetchTableSchemas(
|
|
117
|
-
revisionId: string,
|
|
118
114
|
tables: string[],
|
|
119
115
|
): Promise<Record<string, JsonSchema>> {
|
|
120
116
|
const schemas: Record<string, JsonSchema> = {};
|
|
121
117
|
|
|
122
118
|
for (const tableId of tables) {
|
|
123
119
|
try {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
schemas[tableId] = result.data as JsonSchema;
|
|
127
|
-
}
|
|
120
|
+
const schema = await this.revisionScope.getTableSchema(tableId);
|
|
121
|
+
schemas[tableId] = schema as JsonSchema;
|
|
128
122
|
} catch (error) {
|
|
129
123
|
this.logger.warn(
|
|
130
124
|
`Could not fetch schema for table ${tableId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -153,7 +147,6 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
153
147
|
}
|
|
154
148
|
|
|
155
149
|
private async uploadTableRows(
|
|
156
|
-
revisionId: string,
|
|
157
150
|
tableId: string,
|
|
158
151
|
folderPath: string,
|
|
159
152
|
schema: JsonSchema | undefined,
|
|
@@ -190,10 +183,9 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
190
183
|
return stats;
|
|
191
184
|
}
|
|
192
185
|
|
|
193
|
-
const apiClient = createApiClientAdapter(this.
|
|
186
|
+
const apiClient = createApiClientAdapter(this.revisionScope);
|
|
194
187
|
const syncStats = await this.rowSyncService.syncTableRows(
|
|
195
188
|
apiClient,
|
|
196
|
-
revisionId,
|
|
197
189
|
tableId,
|
|
198
190
|
loadResult.rows,
|
|
199
191
|
batchSize,
|
|
@@ -235,8 +227,8 @@ export class UploadRowsCommand extends BaseCommand {
|
|
|
235
227
|
}
|
|
236
228
|
}
|
|
237
229
|
|
|
238
|
-
private get
|
|
239
|
-
return this.connectionService.
|
|
230
|
+
private get revisionScope(): RevisionScope {
|
|
231
|
+
return this.connectionService.revisionScope;
|
|
240
232
|
}
|
|
241
233
|
|
|
242
234
|
@Option({
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join, extname } from 'node:path';
|
|
3
3
|
import { CommandRunner, Option, SubCommand } from 'nest-commander';
|
|
4
|
+
import type { InitMigrationDto } from '@revisium/client';
|
|
4
5
|
import { JsonValidatorService, LoggerService } from 'src/services/common';
|
|
5
6
|
import { TableDependencyService } from 'src/services/sync';
|
|
6
7
|
import { JsonSchema } from 'src/types/schema.types';
|
|
7
|
-
import { InitMigrationDto } from 'src/__generated__/api';
|
|
8
8
|
import * as objectHash from 'object-hash';
|
|
9
9
|
|
|
10
10
|
type Options = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { Option, SubCommand } from 'nest-commander';
|
|
4
|
+
import { RevisionScope } from '@revisium/client';
|
|
4
5
|
import { BaseCommand, BaseOptions } from 'src/commands/base.command';
|
|
5
6
|
import { ConnectionService } from 'src/services/connection';
|
|
6
7
|
import { LoggerService } from 'src/services/common';
|
|
@@ -28,13 +29,10 @@ export class SaveSchemaCommand extends BaseCommand {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
await this.connectionService.connect(options);
|
|
31
|
-
await this.saveAllTableSchemas(
|
|
32
|
-
this.connectionService.revisionId,
|
|
33
|
-
options.folder,
|
|
34
|
-
);
|
|
32
|
+
await this.saveAllTableSchemas(options.folder);
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
private async saveAllTableSchemas(
|
|
35
|
+
private async saveAllTableSchemas(folderPath: string) {
|
|
38
36
|
try {
|
|
39
37
|
await mkdir(folderPath, { recursive: true });
|
|
40
38
|
|
|
@@ -43,19 +41,16 @@ export class SaveSchemaCommand extends BaseCommand {
|
|
|
43
41
|
let totalTables = 0;
|
|
44
42
|
|
|
45
43
|
const { processed } = await fetchAndProcessPages(
|
|
46
|
-
(params) =>
|
|
44
|
+
(params) =>
|
|
45
|
+
this.revisionScope.getTables(params).then((data) => ({ data })),
|
|
47
46
|
async (table, index) => {
|
|
48
47
|
this.logger.processingTable(table.id);
|
|
49
48
|
|
|
50
|
-
const
|
|
49
|
+
const schema = await this.revisionScope.getTableSchema(table.id);
|
|
51
50
|
const fileName = `${table.id}.json`;
|
|
52
51
|
const filePath = join(folderPath, fileName);
|
|
53
52
|
|
|
54
|
-
await writeFile(
|
|
55
|
-
filePath,
|
|
56
|
-
JSON.stringify(schemaResult.data, null, 2),
|
|
57
|
-
'utf-8',
|
|
58
|
-
);
|
|
53
|
+
await writeFile(filePath, JSON.stringify(schema, null, 2), 'utf-8');
|
|
59
54
|
|
|
60
55
|
this.logger.success(
|
|
61
56
|
`Saved schema: ${fileName} (${index + 1}/${totalTables})`,
|
|
@@ -80,8 +75,8 @@ export class SaveSchemaCommand extends BaseCommand {
|
|
|
80
75
|
}
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
private get
|
|
84
|
-
return this.connectionService.
|
|
78
|
+
private get revisionScope(): RevisionScope {
|
|
79
|
+
return this.connectionService.revisionScope;
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
@Option({
|
|
@@ -3,6 +3,45 @@ import { sharedFields } from 'src/config/shared-fields';
|
|
|
3
3
|
|
|
4
4
|
// https://json-schema.org/specification#single-vocabulary-meta-schemas
|
|
5
5
|
|
|
6
|
+
export const xFormulaSchema: Schema = {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
version: {
|
|
10
|
+
const: 1,
|
|
11
|
+
},
|
|
12
|
+
expression: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
minLength: 1,
|
|
15
|
+
maxLength: 10000,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
required: ['version', 'expression'],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// When x-formula is present, readOnly must be true
|
|
23
|
+
export const xFormulaRequiresReadOnly: Schema = {
|
|
24
|
+
if: {
|
|
25
|
+
properties: { 'x-formula': { type: 'object' } },
|
|
26
|
+
required: ['x-formula'],
|
|
27
|
+
},
|
|
28
|
+
then: {
|
|
29
|
+
properties: { readOnly: { const: true } },
|
|
30
|
+
required: ['readOnly'],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// foreignKey and x-formula are mutually exclusive
|
|
35
|
+
export const foreignKeyExcludesFormula: Schema = {
|
|
36
|
+
if: {
|
|
37
|
+
properties: { foreignKey: { type: 'string' } },
|
|
38
|
+
required: ['foreignKey'],
|
|
39
|
+
},
|
|
40
|
+
then: {
|
|
41
|
+
not: { required: ['x-formula'] },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
6
45
|
export const refMetaSchema: Schema = {
|
|
7
46
|
type: 'object',
|
|
8
47
|
properties: {
|
|
@@ -60,18 +99,22 @@ export const stringMetaSchema: Schema = {
|
|
|
60
99
|
foreignKey: {
|
|
61
100
|
type: 'string',
|
|
62
101
|
},
|
|
102
|
+
'x-formula': xFormulaSchema,
|
|
63
103
|
},
|
|
64
104
|
additionalProperties: false,
|
|
65
105
|
required: ['type', 'default'],
|
|
106
|
+
allOf: [xFormulaRequiresReadOnly, foreignKeyExcludesFormula],
|
|
66
107
|
};
|
|
67
108
|
|
|
68
109
|
export const noForeignKeyStringMetaSchema: Schema = {
|
|
69
110
|
type: 'object',
|
|
70
111
|
properties: {
|
|
71
112
|
...baseStringFields,
|
|
113
|
+
'x-formula': xFormulaSchema,
|
|
72
114
|
},
|
|
73
115
|
additionalProperties: false,
|
|
74
116
|
required: ['type', 'default'],
|
|
117
|
+
...xFormulaRequiresReadOnly,
|
|
75
118
|
};
|
|
76
119
|
|
|
77
120
|
export const numberMetaSchema: Schema = {
|
|
@@ -87,9 +130,11 @@ export const numberMetaSchema: Schema = {
|
|
|
87
130
|
type: 'boolean',
|
|
88
131
|
},
|
|
89
132
|
...sharedFields,
|
|
133
|
+
'x-formula': xFormulaSchema,
|
|
90
134
|
},
|
|
91
135
|
additionalProperties: false,
|
|
92
136
|
required: ['type', 'default'],
|
|
137
|
+
...xFormulaRequiresReadOnly,
|
|
93
138
|
};
|
|
94
139
|
|
|
95
140
|
export const booleanMetaSchema: Schema = {
|
|
@@ -105,9 +150,11 @@ export const booleanMetaSchema: Schema = {
|
|
|
105
150
|
type: 'boolean',
|
|
106
151
|
},
|
|
107
152
|
...sharedFields,
|
|
153
|
+
'x-formula': xFormulaSchema,
|
|
108
154
|
},
|
|
109
155
|
additionalProperties: false,
|
|
110
156
|
required: ['type', 'default'],
|
|
157
|
+
...xFormulaRequiresReadOnly,
|
|
111
158
|
};
|
|
112
159
|
|
|
113
160
|
export const objectMetaSchema: Schema = {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ConnectionFactoryService } from '../connection-factory.service';
|
|
2
|
+
import { UrlBuilderService, RevisiumUrlComplete } from '../../url';
|
|
3
|
+
import { LoggerService } from '../../common';
|
|
4
|
+
import { RevisiumApiClient } from '../api-client';
|
|
5
|
+
|
|
6
|
+
jest.mock('../api-client');
|
|
7
|
+
|
|
8
|
+
describe('ConnectionFactoryService', () => {
|
|
9
|
+
let service: ConnectionFactoryService;
|
|
10
|
+
let urlBuilderFake: { formatAsRevisiumUrl: jest.Mock };
|
|
11
|
+
let loggerFake: {
|
|
12
|
+
connecting: jest.Mock;
|
|
13
|
+
connected: jest.Mock;
|
|
14
|
+
authenticated: jest.Mock;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const mockDraftScope = {
|
|
18
|
+
revisionId: 'draft-456',
|
|
19
|
+
isDraft: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const mockHeadScope = {
|
|
23
|
+
revisionId: 'head-123',
|
|
24
|
+
isDraft: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const mockExplicitScope = {
|
|
28
|
+
revisionId: 'specific-rev-id',
|
|
29
|
+
isDraft: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mockBranchScope = {
|
|
33
|
+
draft: jest.fn().mockReturnValue(mockDraftScope),
|
|
34
|
+
head: jest.fn().mockReturnValue(mockHeadScope),
|
|
35
|
+
revision: jest.fn().mockResolvedValue(mockExplicitScope),
|
|
36
|
+
headRevisionId: 'head-123',
|
|
37
|
+
draftRevisionId: 'draft-456',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const baseUrl: RevisiumUrlComplete = {
|
|
41
|
+
baseUrl: 'https://cloud.revisium.io',
|
|
42
|
+
organization: 'test-org',
|
|
43
|
+
project: 'test-project',
|
|
44
|
+
branch: 'main',
|
|
45
|
+
revision: 'draft',
|
|
46
|
+
auth: { method: 'token', token: 'test-token' },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
urlBuilderFake = {
|
|
51
|
+
formatAsRevisiumUrl: jest.fn().mockReturnValue('revisium://...'),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
loggerFake = {
|
|
55
|
+
connecting: jest.fn(),
|
|
56
|
+
connected: jest.fn(),
|
|
57
|
+
authenticated: jest.fn(),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const MockApiClient = RevisiumApiClient as jest.MockedClass<
|
|
61
|
+
typeof RevisiumApiClient
|
|
62
|
+
>;
|
|
63
|
+
MockApiClient.mockImplementation(
|
|
64
|
+
() =>
|
|
65
|
+
({
|
|
66
|
+
client: {
|
|
67
|
+
branch: jest.fn().mockResolvedValue(mockBranchScope),
|
|
68
|
+
},
|
|
69
|
+
authenticate: jest.fn().mockResolvedValue('test-user'),
|
|
70
|
+
}) as unknown as RevisiumApiClient,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
service = new ConnectionFactoryService(
|
|
74
|
+
urlBuilderFake as unknown as UrlBuilderService,
|
|
75
|
+
loggerFake as unknown as LoggerService,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
afterEach(() => {
|
|
80
|
+
jest.clearAllMocks();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('revision resolution', () => {
|
|
84
|
+
it('uses draft scope when revision is draft', async () => {
|
|
85
|
+
const url = { ...baseUrl, revision: 'draft' };
|
|
86
|
+
|
|
87
|
+
const result = await service.createConnection(url);
|
|
88
|
+
|
|
89
|
+
expect(mockBranchScope.draft).toHaveBeenCalled();
|
|
90
|
+
expect(mockBranchScope.head).not.toHaveBeenCalled();
|
|
91
|
+
expect(mockBranchScope.revision).not.toHaveBeenCalled();
|
|
92
|
+
expect(result.revisionScope).toBe(mockDraftScope);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('uses head scope when revision is head', async () => {
|
|
96
|
+
const url = { ...baseUrl, revision: 'head' };
|
|
97
|
+
|
|
98
|
+
const result = await service.createConnection(url);
|
|
99
|
+
|
|
100
|
+
expect(mockBranchScope.head).toHaveBeenCalled();
|
|
101
|
+
expect(mockBranchScope.draft).not.toHaveBeenCalled();
|
|
102
|
+
expect(mockBranchScope.revision).not.toHaveBeenCalled();
|
|
103
|
+
expect(result.revisionScope).toBe(mockHeadScope);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('uses explicit revision scope for specific revision ID', async () => {
|
|
107
|
+
const url = { ...baseUrl, revision: 'specific-rev-id' };
|
|
108
|
+
|
|
109
|
+
const result = await service.createConnection(url);
|
|
110
|
+
|
|
111
|
+
expect(mockBranchScope.revision).toHaveBeenCalledWith('specific-rev-id');
|
|
112
|
+
expect(mockBranchScope.draft).not.toHaveBeenCalled();
|
|
113
|
+
expect(mockBranchScope.head).not.toHaveBeenCalled();
|
|
114
|
+
expect(result.revisionScope).toBe(mockExplicitScope);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|