@restforgejs/platform 4.3.8 → 5.0.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/bin/sdf-tools.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/payload/migrate.js +96 -96
- package/generators/lib/dbschema-kit/apply-engine.js +211 -46
- package/generators/lib/dbschema-kit/diff-engine.js +14 -2
- package/generators/lib/dbschema-kit/emitters/alter-table.js +96 -2
- package/generators/lib/dbschema-kit/introspect-mapper.js +9 -0
- package/generators/lib/migrate/backend-payload-migrator.js +221 -221
- package/generators/lib/migrate/field-type-resolver.js +319 -319
- package/generators/lib/migrate/label-generator.js +38 -38
- package/generators/lib/migrate/migrate-runner.js +187 -187
- package/generators/lib/migrate/naming.js +43 -43
- package/generators/lib/migrate/sql-parser.js +124 -124
- package/generators/lib/templates/dashboard-catalog.js +1 -1
- package/generators/lib/templates/db-connection-env.js +1 -1
- package/generators/lib/templates/dbschema-catalog.js +1 -1
- package/generators/lib/templates/field-validation-catalog.js +1 -1
- package/generators/lib/templates/mysql-template.js +1 -1
- package/generators/lib/templates/oracle-template.js +1 -1
- package/generators/lib/templates/postgres-template.js +1 -1
- package/generators/lib/templates/query-declarative-catalog.js +1 -1
- package/generators/lib/templates/sqlite-template.js +1 -1
- package/integrity-manifest.json +18 -18
- package/node_modules/brace-expansion/index.js +1 -1
- package/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/dayjs/CHANGELOG.md +7 -0
- package/node_modules/dayjs/README.md +12 -10
- package/node_modules/dayjs/dayjs.min.js +1 -1
- package/node_modules/dayjs/esm/constant.js +1 -1
- package/node_modules/dayjs/esm/plugin/duration/index.js +5 -4
- package/node_modules/dayjs/locale.json +1 -1
- package/node_modules/dayjs/package.json +2 -2
- package/node_modules/dayjs/plugin/duration.js +1 -1
- package/node_modules/tmp/lib/tmp.js +37 -7
- package/node_modules/tmp/package.json +4 -16
- package/package.json +1 -1
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- package/src/utils/workflow-hook-executor.js +1 -1
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Contract: payload migrate
|
|
5
|
-
*
|
|
6
|
-
* Konversi file payload backend (RDF / RESTForge consumer) menjadi file payload
|
|
7
|
-
* frontend (UDF / designer). Port dari `rfd migrate` di packages/designer/.
|
|
8
|
-
*
|
|
9
|
-
* Perbedaan dengan `rfd migrate`:
|
|
10
|
-
* - `apiBaseUrl` di-construct dari SERVER_ADDRESS + SERVER_PORT (db-connection.env)
|
|
11
|
-
* + --project sehingga sesuai dengan runtime server actual
|
|
12
|
-
* - Port di-baca dari db-connection.env (SERVER_PORT), bukan hard-coded 3000
|
|
13
|
-
* - Primary key (constraints.primaryKey=true) di-skip dari fields[] apapun
|
|
14
|
-
* tipenya (uuid/string/integer), karena PK adalah identifier teknis dan
|
|
15
|
-
* tidak perlu di-render di UI form
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const runner = require('../../lib/migrate/migrate-runner');
|
|
19
|
-
|
|
20
|
-
module.exports = {
|
|
21
|
-
resource: 'payload',
|
|
22
|
-
verb: 'migrate',
|
|
23
|
-
description: 'Convert backend payload (RDF) file into frontend payload (UDF) for the designer',
|
|
24
|
-
category: 'generation',
|
|
25
|
-
flags: {
|
|
26
|
-
name: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
required: true,
|
|
29
|
-
description: 'Backend payload file name (e.g. visitors.json). Relative to cwd or cwd/payload/.'
|
|
30
|
-
},
|
|
31
|
-
output: {
|
|
32
|
-
type: 'string',
|
|
33
|
-
required: false,
|
|
34
|
-
default: null,
|
|
35
|
-
description: 'Output directory (the file will be written with the same name as --name inside it). If ending with `.json`, treated as an explicit file path. Default: frontend/payload/'
|
|
36
|
-
},
|
|
37
|
-
config: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
required: false,
|
|
40
|
-
default: null,
|
|
41
|
-
description: 'Database config file (.env). Used to read SERVER_ADDRESS and SERVER_PORT. Falls back to `.restforge/defaults.json`.'
|
|
42
|
-
},
|
|
43
|
-
project: {
|
|
44
|
-
type: 'string',
|
|
45
|
-
required: true,
|
|
46
|
-
description: 'Project name (kebab-case code) used as the path segment in apiBaseUrl: http://{host}:{port}/api/{project}'
|
|
47
|
-
},
|
|
48
|
-
'app-name': {
|
|
49
|
-
type: 'string',
|
|
50
|
-
required: false,
|
|
51
|
-
default: null,
|
|
52
|
-
description: 'Application name (default: "My Application")'
|
|
53
|
-
},
|
|
54
|
-
'app-code': {
|
|
55
|
-
type: 'string',
|
|
56
|
-
required: false,
|
|
57
|
-
default: null,
|
|
58
|
-
description: 'Application code in kebab-case (default: follows --project)'
|
|
59
|
-
},
|
|
60
|
-
plugin: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
required: false,
|
|
63
|
-
default: null,
|
|
64
|
-
description: 'Designer plugin ID (default: "vanilla-js-basic")'
|
|
65
|
-
},
|
|
66
|
-
port: {
|
|
67
|
-
type: 'number',
|
|
68
|
-
required: false,
|
|
69
|
-
default: null,
|
|
70
|
-
description: 'Frontend application port written to appConfig.port (default: 8000). Independent from the backend port used in apiBaseUrl.'
|
|
71
|
-
},
|
|
72
|
-
overwrite: {
|
|
73
|
-
type: 'boolean',
|
|
74
|
-
required: false,
|
|
75
|
-
default: false,
|
|
76
|
-
description: 'Overwrite the output file if it already exists'
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
examples: [
|
|
80
|
-
'npx restforge payload migrate --name=visitors.json --output=..\\sandbox\\frontend\\payload --config=db.env --project=myapp',
|
|
81
|
-
'npx restforge payload migrate --name=visitors.json --project=myapp --overwrite'
|
|
82
|
-
],
|
|
83
|
-
async handler(args) {
|
|
84
|
-
await runner.run({
|
|
85
|
-
name: args.name,
|
|
86
|
-
output: args.output || null,
|
|
87
|
-
config: args.config || null,
|
|
88
|
-
project: args.project,
|
|
89
|
-
appName: args['app-name'] || null,
|
|
90
|
-
appCode: args['app-code'] || null,
|
|
91
|
-
plugin: args.plugin || null,
|
|
92
|
-
port: typeof args.port === 'number' ? args.port : null,
|
|
93
|
-
overwrite: args.overwrite === true
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contract: payload migrate
|
|
5
|
+
*
|
|
6
|
+
* Konversi file payload backend (RDF / RESTForge consumer) menjadi file payload
|
|
7
|
+
* frontend (UDF / designer). Port dari `rfd migrate` di packages/designer/.
|
|
8
|
+
*
|
|
9
|
+
* Perbedaan dengan `rfd migrate`:
|
|
10
|
+
* - `apiBaseUrl` di-construct dari SERVER_ADDRESS + SERVER_PORT (db-connection.env)
|
|
11
|
+
* + --project sehingga sesuai dengan runtime server actual
|
|
12
|
+
* - Port di-baca dari db-connection.env (SERVER_PORT), bukan hard-coded 3000
|
|
13
|
+
* - Primary key (constraints.primaryKey=true) di-skip dari fields[] apapun
|
|
14
|
+
* tipenya (uuid/string/integer), karena PK adalah identifier teknis dan
|
|
15
|
+
* tidak perlu di-render di UI form
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const runner = require('../../lib/migrate/migrate-runner');
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
resource: 'payload',
|
|
22
|
+
verb: 'migrate',
|
|
23
|
+
description: 'Convert backend payload (RDF) file into frontend payload (UDF) for the designer',
|
|
24
|
+
category: 'generation',
|
|
25
|
+
flags: {
|
|
26
|
+
name: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
required: true,
|
|
29
|
+
description: 'Backend payload file name (e.g. visitors.json). Relative to cwd or cwd/payload/.'
|
|
30
|
+
},
|
|
31
|
+
output: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
required: false,
|
|
34
|
+
default: null,
|
|
35
|
+
description: 'Output directory (the file will be written with the same name as --name inside it). If ending with `.json`, treated as an explicit file path. Default: frontend/payload/'
|
|
36
|
+
},
|
|
37
|
+
config: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
required: false,
|
|
40
|
+
default: null,
|
|
41
|
+
description: 'Database config file (.env). Used to read SERVER_ADDRESS and SERVER_PORT. Falls back to `.restforge/defaults.json`.'
|
|
42
|
+
},
|
|
43
|
+
project: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
required: true,
|
|
46
|
+
description: 'Project name (kebab-case code) used as the path segment in apiBaseUrl: http://{host}:{port}/api/{project}'
|
|
47
|
+
},
|
|
48
|
+
'app-name': {
|
|
49
|
+
type: 'string',
|
|
50
|
+
required: false,
|
|
51
|
+
default: null,
|
|
52
|
+
description: 'Application name (default: "My Application")'
|
|
53
|
+
},
|
|
54
|
+
'app-code': {
|
|
55
|
+
type: 'string',
|
|
56
|
+
required: false,
|
|
57
|
+
default: null,
|
|
58
|
+
description: 'Application code in kebab-case (default: follows --project)'
|
|
59
|
+
},
|
|
60
|
+
plugin: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
required: false,
|
|
63
|
+
default: null,
|
|
64
|
+
description: 'Designer plugin ID (default: "vanilla-js-basic")'
|
|
65
|
+
},
|
|
66
|
+
port: {
|
|
67
|
+
type: 'number',
|
|
68
|
+
required: false,
|
|
69
|
+
default: null,
|
|
70
|
+
description: 'Frontend application port written to appConfig.port (default: 8000). Independent from the backend port used in apiBaseUrl.'
|
|
71
|
+
},
|
|
72
|
+
overwrite: {
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
required: false,
|
|
75
|
+
default: false,
|
|
76
|
+
description: 'Overwrite the output file if it already exists'
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
examples: [
|
|
80
|
+
'npx restforge payload migrate --name=visitors.json --output=..\\sandbox\\frontend\\payload --config=db.env --project=myapp',
|
|
81
|
+
'npx restforge payload migrate --name=visitors.json --project=myapp --overwrite'
|
|
82
|
+
],
|
|
83
|
+
async handler(args) {
|
|
84
|
+
await runner.run({
|
|
85
|
+
name: args.name,
|
|
86
|
+
output: args.output || null,
|
|
87
|
+
config: args.config || null,
|
|
88
|
+
project: args.project,
|
|
89
|
+
appName: args['app-name'] || null,
|
|
90
|
+
appCode: args['app-code'] || null,
|
|
91
|
+
plugin: args.plugin || null,
|
|
92
|
+
port: typeof args.port === 'number' ? args.port : null,
|
|
93
|
+
overwrite: args.overwrite === true
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -5,34 +5,40 @@
|
|
|
5
5
|
* statement incremental. Komplemen `ddl-generator.js` yang menghasilkan full
|
|
6
6
|
* CREATE TABLE.
|
|
7
7
|
*
|
|
8
|
-
* Operasi yang di-handle
|
|
9
|
-
* - ADD COLUMN
|
|
10
|
-
* - DROP COLUMN
|
|
11
|
-
* - MODIFY COLUMN
|
|
12
|
-
* - CREATE INDEX
|
|
13
|
-
* - DROP INDEX
|
|
14
|
-
* - ADD CONSTRAINT UNIQUE
|
|
15
|
-
* - DROP CONSTRAINT UNIQUE
|
|
8
|
+
* Operasi yang di-handle:
|
|
9
|
+
* - ADD COLUMN (additive, default)
|
|
10
|
+
* - DROP COLUMN (destruktif, butuh allowDrop)
|
|
11
|
+
* - MODIFY COLUMN (length / nullable, butuh allowModify)
|
|
12
|
+
* - CREATE INDEX (additive)
|
|
13
|
+
* - DROP INDEX (destruktif, butuh allowDrop)
|
|
14
|
+
* - ADD CONSTRAINT UNIQUE (additive)
|
|
15
|
+
* - DROP CONSTRAINT UNIQUE (destruktif, butuh allowDrop)
|
|
16
|
+
* - ADD FOREIGN KEY (additive, default — kecuali sqlite)
|
|
17
|
+
* - DROP FOREIGN KEY (destruktif, butuh allowDrop — kecuali sqlite)
|
|
18
|
+
* - REPLACE FOREIGN KEY action (destruktif: DROP+ADD, butuh allowModify — kecuali sqlite)
|
|
16
19
|
*
|
|
17
20
|
* Operasi yang TIDAK di-handle (deferred):
|
|
18
21
|
* - ALTER COLUMN type change (butuh data conversion strategy per dialect)
|
|
19
22
|
* - PK changes (butuh rebuild table)
|
|
20
|
-
* - FK changes (butuh cross-model validation + dialect syntax kompleks)
|
|
21
23
|
* - DEFAULT value changes (defer ke fase berikutnya)
|
|
22
24
|
* - CHECK constraint changes
|
|
25
|
+
* - FK changes pada sqlite (butuh rebuild table)
|
|
23
26
|
*
|
|
24
|
-
* SQLite tidak mendukung ALTER COLUMN / DROP COLUMN
|
|
25
|
-
* Untuk dialect ini, MODIFY
|
|
26
|
-
* 'sqlite limitation' walaupun flag opt-in
|
|
27
|
+
* SQLite tidak mendukung ALTER COLUMN / DROP COLUMN / ALTER ADD CONSTRAINT FK
|
|
28
|
+
* tanpa rebuild table. Untuk dialect ini, MODIFY/DROP COLUMN dan semua perubahan
|
|
29
|
+
* FK otomatis di-skip dengan reason 'sqlite limitation' walaupun flag opt-in
|
|
30
|
+
* aktif.
|
|
27
31
|
*
|
|
28
32
|
* Output dipesan dengan urutan aman untuk dependency:
|
|
29
|
-
* 1. ADD COLUMN
|
|
30
|
-
* 2. CREATE INDEX
|
|
31
|
-
* 3. ADD CONSTRAINT UNIQUE
|
|
32
|
-
* 4.
|
|
33
|
-
* 5.
|
|
34
|
-
* 6. DROP
|
|
35
|
-
* 7. DROP
|
|
33
|
+
* 1. ADD COLUMN (no dependencies)
|
|
34
|
+
* 2. CREATE INDEX (depend on column existence)
|
|
35
|
+
* 3. ADD CONSTRAINT UNIQUE (depend on column existence)
|
|
36
|
+
* 4. ADD CONSTRAINT FOREIGN KEY (depend on column existence di kedua sisi)
|
|
37
|
+
* 5. ALTER/MODIFY COLUMN (modify after additive done)
|
|
38
|
+
* 6. DROP CONSTRAINT FK (release FK sebelum drop unique/index/column)
|
|
39
|
+
* 7. DROP CONSTRAINT UNIQUE (before drop column to avoid dependency error)
|
|
40
|
+
* 8. DROP INDEX (before drop column)
|
|
41
|
+
* 9. DROP COLUMN (last)
|
|
36
42
|
*
|
|
37
43
|
* @module lib/dbschema-kit/apply-engine
|
|
38
44
|
*/
|
|
@@ -45,7 +51,9 @@ const {
|
|
|
45
51
|
emitCreateIndex,
|
|
46
52
|
emitDropIndex,
|
|
47
53
|
emitAddUnique,
|
|
48
|
-
emitDropUnique
|
|
54
|
+
emitDropUnique,
|
|
55
|
+
emitAddForeignKey,
|
|
56
|
+
emitDropForeignKey
|
|
49
57
|
} = require('./emitters/alter-table');
|
|
50
58
|
|
|
51
59
|
const VALID_DIALECTS = ['postgres', 'mysql', 'oracle', 'sqlite'];
|
|
@@ -142,7 +150,10 @@ function buildBuckets() {
|
|
|
142
150
|
addColumns: [],
|
|
143
151
|
createIndexes: [],
|
|
144
152
|
addUniques: [],
|
|
153
|
+
addForeignKeys: [],
|
|
145
154
|
modifyColumns: [],
|
|
155
|
+
dropForeignKeys: [],
|
|
156
|
+
replaceForeignKeysAdd: [],
|
|
146
157
|
dropConstraints: [],
|
|
147
158
|
dropIndexes: [],
|
|
148
159
|
dropColumns: []
|
|
@@ -154,7 +165,10 @@ function flattenBuckets(buckets) {
|
|
|
154
165
|
...buckets.addColumns,
|
|
155
166
|
...buckets.createIndexes,
|
|
156
167
|
...buckets.addUniques,
|
|
168
|
+
...buckets.addForeignKeys,
|
|
157
169
|
...buckets.modifyColumns,
|
|
170
|
+
...buckets.dropForeignKeys,
|
|
171
|
+
...buckets.replaceForeignKeysAdd,
|
|
158
172
|
...buckets.dropConstraints,
|
|
159
173
|
...buckets.dropIndexes,
|
|
160
174
|
...buckets.dropColumns
|
|
@@ -412,51 +426,201 @@ function processUniques(delta, tableIR, dialect, options, buckets, skipped, summ
|
|
|
412
426
|
}
|
|
413
427
|
}
|
|
414
428
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const sdf = Array.isArray(delta.primaryKey.sdf) ? delta.primaryKey.sdf : [];
|
|
419
|
-
const db = Array.isArray(delta.primaryKey.db) ? delta.primaryKey.db : [];
|
|
420
|
-
skipped.push({
|
|
421
|
-
table: delta.tableName,
|
|
422
|
-
kind: 'primary-key',
|
|
423
|
-
target: `pk(${sdf.join(',') || '∅'} vs ${db.join(',') || '∅'})`,
|
|
424
|
-
reason: 'deferred',
|
|
425
|
-
description: `Primary key changes for ${delta.tableName} deferred from MVP (requires table rebuild)`
|
|
426
|
-
});
|
|
427
|
-
}
|
|
429
|
+
// ─────────────────────────────────────────────────────────────
|
|
430
|
+
// FK delta processing
|
|
431
|
+
// ─────────────────────────────────────────────────────────────
|
|
428
432
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
+
// FK delta entries dari diff-engine memuat triplet (localKey, target, references)
|
|
434
|
+
// plus optional `name` (relName dari relations map) dan `dbName` (untuk mismatched).
|
|
435
|
+
// Apply path hanya menjalankan validator opsional, jadi kita fallback ke localKey
|
|
436
|
+
// untuk derive constraint name bila relName tidak tersedia.
|
|
437
|
+
function pickRelName(fk) {
|
|
438
|
+
if (fk && typeof fk.name === 'string' && fk.name !== '') return fk.name;
|
|
439
|
+
if (fk && typeof fk.localKey === 'string' && fk.localKey !== '') return fk.localKey;
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function fkSkipTarget(fk) {
|
|
444
|
+
const local = fk && fk.localKey ? fk.localKey : '?';
|
|
445
|
+
const target = fk && fk.target ? fk.target : '?';
|
|
446
|
+
const ref = fk && fk.references ? fk.references : '?';
|
|
447
|
+
return `${local} -> ${target}.${ref}`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function processForeignKeys(delta, tableIR, dialect, options, buckets, skipped, summary) {
|
|
451
|
+
if (!delta.foreignKeys) return;
|
|
452
|
+
|
|
453
|
+
const fkOnlySdf = Array.isArray(delta.foreignKeys.onlyInSdf) ? delta.foreignKeys.onlyInSdf : [];
|
|
454
|
+
const fkOnlyDb = Array.isArray(delta.foreignKeys.onlyInDb) ? delta.foreignKeys.onlyInDb : [];
|
|
455
|
+
const fkMismatch = Array.isArray(delta.foreignKeys.mismatched) ? delta.foreignKeys.mismatched : [];
|
|
456
|
+
|
|
457
|
+
// SQLite: semua perubahan FK butuh table rebuild — defer dengan reason akurat
|
|
458
|
+
if (dialect.name === 'sqlite') {
|
|
433
459
|
for (const fk of fkOnlySdf) {
|
|
434
460
|
skipped.push({
|
|
435
461
|
table: delta.tableName,
|
|
436
462
|
kind: 'foreign-key',
|
|
437
|
-
target:
|
|
438
|
-
reason: '
|
|
439
|
-
description: `
|
|
463
|
+
target: fkSkipTarget(fk),
|
|
464
|
+
reason: 'sqlite limitation',
|
|
465
|
+
description: `SQLite does not support ALTER TABLE ADD CONSTRAINT FOREIGN KEY (${delta.tableName}.${fk.localKey})`
|
|
440
466
|
});
|
|
441
467
|
}
|
|
442
468
|
for (const fk of fkOnlyDb) {
|
|
443
469
|
skipped.push({
|
|
444
470
|
table: delta.tableName,
|
|
445
471
|
kind: 'foreign-key',
|
|
446
|
-
target:
|
|
447
|
-
reason: '
|
|
448
|
-
description: `
|
|
472
|
+
target: fkSkipTarget(fk),
|
|
473
|
+
reason: 'sqlite limitation',
|
|
474
|
+
description: `SQLite does not support ALTER TABLE DROP CONSTRAINT FOREIGN KEY (${delta.tableName}.${fk.localKey})`
|
|
449
475
|
});
|
|
450
476
|
}
|
|
451
477
|
for (const fk of fkMismatch) {
|
|
452
478
|
skipped.push({
|
|
453
479
|
table: delta.tableName,
|
|
454
480
|
kind: 'foreign-key',
|
|
455
|
-
target:
|
|
456
|
-
reason: '
|
|
457
|
-
description: `
|
|
481
|
+
target: fkSkipTarget(fk),
|
|
482
|
+
reason: 'sqlite limitation',
|
|
483
|
+
description: `SQLite does not support ALTER TABLE for FK action change (${delta.tableName}.${fk.localKey})`
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// FK additive (onlyInSdf) — non-destruktif, emit langsung tanpa flag opt-in
|
|
490
|
+
for (const fk of fkOnlySdf) {
|
|
491
|
+
const relName = pickRelName(fk);
|
|
492
|
+
if (!relName) {
|
|
493
|
+
skipped.push({
|
|
494
|
+
table: delta.tableName,
|
|
495
|
+
kind: 'foreign-key',
|
|
496
|
+
target: fkSkipTarget(fk),
|
|
497
|
+
reason: 'emit-error',
|
|
498
|
+
description: `Foreign key for ${delta.tableName} missing both name and localKey`
|
|
458
499
|
});
|
|
500
|
+
continue;
|
|
459
501
|
}
|
|
502
|
+
try {
|
|
503
|
+
buckets.addForeignKeys.push(emitAddForeignKey(tableIR, relName, fk, dialect));
|
|
504
|
+
summary.totalAdditions++;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
skipped.push({
|
|
507
|
+
table: delta.tableName,
|
|
508
|
+
kind: 'foreign-key',
|
|
509
|
+
target: fkSkipTarget(fk),
|
|
510
|
+
reason: 'emit-error',
|
|
511
|
+
description: err && err.message ? err.message : String(err)
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// FK drop (onlyInDb) — destruktif, butuh --allow-drop
|
|
517
|
+
for (const fk of fkOnlyDb) {
|
|
518
|
+
const relName = pickRelName(fk);
|
|
519
|
+
if (!relName) {
|
|
520
|
+
skipped.push({
|
|
521
|
+
table: delta.tableName,
|
|
522
|
+
kind: 'foreign-key',
|
|
523
|
+
target: fkSkipTarget(fk),
|
|
524
|
+
reason: 'emit-error',
|
|
525
|
+
description: `Foreign key for ${delta.tableName} missing both name and localKey`
|
|
526
|
+
});
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (!options.allowDrop) {
|
|
530
|
+
skipped.push({
|
|
531
|
+
table: delta.tableName,
|
|
532
|
+
kind: 'foreign-key',
|
|
533
|
+
target: fkSkipTarget(fk),
|
|
534
|
+
reason: 'requires --allow-drop',
|
|
535
|
+
description: `Drop foreign key on ${delta.tableName}.${fk.localKey} requires --allow-drop`
|
|
536
|
+
});
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
// DROP memakai conname aktual dari introspeksi (fk.dbConstraintName) agar
|
|
541
|
+
// statement menargetkan constraint yang benar-benar ada di DB. Bila conname
|
|
542
|
+
// tidak tersedia (driver kustom), emitter fallback ke derivasi nama lama
|
|
543
|
+
// (generateConstraintName) — perilaku legacy, tanpa entry skipped/warning.
|
|
544
|
+
buckets.dropForeignKeys.push(
|
|
545
|
+
emitDropForeignKey(tableIR, relName, dialect, { name: fk.dbConstraintName })
|
|
546
|
+
);
|
|
547
|
+
summary.totalDeletions++;
|
|
548
|
+
} catch (err) {
|
|
549
|
+
skipped.push({
|
|
550
|
+
table: delta.tableName,
|
|
551
|
+
kind: 'foreign-key',
|
|
552
|
+
target: fkSkipTarget(fk),
|
|
553
|
+
reason: 'emit-error',
|
|
554
|
+
description: err && err.message ? err.message : String(err)
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// FK mismatch (action change) — destruktif (DROP + ADD), butuh --allow-modify
|
|
560
|
+
for (const fk of fkMismatch) {
|
|
561
|
+
const relName = pickRelName(fk);
|
|
562
|
+
if (!relName) {
|
|
563
|
+
skipped.push({
|
|
564
|
+
table: delta.tableName,
|
|
565
|
+
kind: 'foreign-key',
|
|
566
|
+
target: fkSkipTarget(fk),
|
|
567
|
+
reason: 'emit-error',
|
|
568
|
+
description: `Foreign key for ${delta.tableName} missing both name and localKey`
|
|
569
|
+
});
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (!options.allowModify) {
|
|
573
|
+
skipped.push({
|
|
574
|
+
table: delta.tableName,
|
|
575
|
+
kind: 'foreign-key',
|
|
576
|
+
target: fkSkipTarget(fk),
|
|
577
|
+
reason: 'requires --allow-modify',
|
|
578
|
+
description: `Foreign key action change on ${delta.tableName}.${fk.localKey} requires --allow-modify`
|
|
579
|
+
});
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const sdfActions = fk.sdf || {};
|
|
584
|
+
const addRel = {
|
|
585
|
+
localKey: fk.localKey,
|
|
586
|
+
target: fk.target,
|
|
587
|
+
references: fk.references,
|
|
588
|
+
onDelete: sdfActions.onDelete,
|
|
589
|
+
onUpdate: sdfActions.onUpdate
|
|
590
|
+
};
|
|
591
|
+
// DROP memakai conname aktual dari DB (fk.dbConstraintName, fallback derive
|
|
592
|
+
// bila absen); ADD memakai nama SDF-derive (relName) konsisten dengan
|
|
593
|
+
// create-table. ADD masuk replaceForeignKeysAdd bucket supaya terbit SETELAH
|
|
594
|
+
// semua DROP FK, mencegah konflik bila nama lama dan baru kebetulan sama.
|
|
595
|
+
buckets.dropForeignKeys.push(
|
|
596
|
+
emitDropForeignKey(tableIR, relName, dialect, { name: fk.dbConstraintName })
|
|
597
|
+
);
|
|
598
|
+
buckets.replaceForeignKeysAdd.push(emitAddForeignKey(tableIR, relName, addRel, dialect));
|
|
599
|
+
summary.totalModifications++;
|
|
600
|
+
} catch (err) {
|
|
601
|
+
skipped.push({
|
|
602
|
+
table: delta.tableName,
|
|
603
|
+
kind: 'foreign-key',
|
|
604
|
+
target: fkSkipTarget(fk),
|
|
605
|
+
reason: 'emit-error',
|
|
606
|
+
description: err && err.message ? err.message : String(err)
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function noteDeferredSections(delta, skipped) {
|
|
613
|
+
// PK changes — deferred dari MVP karena butuh rebuild table.
|
|
614
|
+
if (delta.primaryKey && delta.primaryKey.match === false) {
|
|
615
|
+
const sdf = Array.isArray(delta.primaryKey.sdf) ? delta.primaryKey.sdf : [];
|
|
616
|
+
const db = Array.isArray(delta.primaryKey.db) ? delta.primaryKey.db : [];
|
|
617
|
+
skipped.push({
|
|
618
|
+
table: delta.tableName,
|
|
619
|
+
kind: 'primary-key',
|
|
620
|
+
target: `pk(${sdf.join(',') || '∅'} vs ${db.join(',') || '∅'})`,
|
|
621
|
+
reason: 'deferred',
|
|
622
|
+
description: `Primary key changes for ${delta.tableName} deferred from MVP (requires table rebuild)`
|
|
623
|
+
});
|
|
460
624
|
}
|
|
461
625
|
|
|
462
626
|
if (delta.checks) {
|
|
@@ -548,6 +712,7 @@ function generateAlterStatements(deltas, options) {
|
|
|
548
712
|
processFieldsMismatched(delta, tableIR, dialect, localOptions, buckets, skipped, sdfModels, summary);
|
|
549
713
|
processIndexes(delta, tableIR, dialect, localOptions, buckets, skipped, summary);
|
|
550
714
|
processUniques(delta, tableIR, dialect, localOptions, buckets, skipped, summary);
|
|
715
|
+
processForeignKeys(delta, tableIR, dialect, localOptions, buckets, skipped, summary);
|
|
551
716
|
noteDeferredSections(delta, skipped);
|
|
552
717
|
|
|
553
718
|
const flat = flattenBuckets(buckets);
|
|
@@ -410,7 +410,11 @@ function extractForeignKeys(ir) {
|
|
|
410
410
|
target: stripSchemaPrefix(rel.target),
|
|
411
411
|
references: rel.references || null,
|
|
412
412
|
onDelete: rel.onDelete,
|
|
413
|
-
onUpdate: rel.onUpdate
|
|
413
|
+
onUpdate: rel.onUpdate,
|
|
414
|
+
// Carry the actual DB constraint name (introspect-mapper sets it on the
|
|
415
|
+
// rel) past this whitelist so DROP/MODIFY can target the real conname.
|
|
416
|
+
// Undefined for SDF-side FKs, which never emit a DROP.
|
|
417
|
+
_dbConstraintName: rel._dbConstraintName
|
|
414
418
|
});
|
|
415
419
|
}
|
|
416
420
|
return out;
|
|
@@ -456,11 +460,14 @@ function compareFkActions(sdfFk, dbFk) {
|
|
|
456
460
|
|
|
457
461
|
function formatFkEntry(fk) {
|
|
458
462
|
return {
|
|
463
|
+
name: fk.name,
|
|
459
464
|
localKey: fk.localKey,
|
|
460
465
|
target: fk.target,
|
|
461
466
|
references: fk.references,
|
|
462
467
|
onDelete: normalizeFkAction(fk.onDelete),
|
|
463
|
-
onUpdate: normalizeFkAction(fk.onUpdate)
|
|
468
|
+
onUpdate: normalizeFkAction(fk.onUpdate),
|
|
469
|
+
// Actual DB constraint name for onlyInDb (DROP); undefined for onlyInSdf (ADD).
|
|
470
|
+
dbConstraintName: fk._dbConstraintName
|
|
464
471
|
};
|
|
465
472
|
}
|
|
466
473
|
|
|
@@ -493,6 +500,11 @@ function diffForeignKeys(sdfIR, dbIR) {
|
|
|
493
500
|
const reasons = compareFkActions(sdfFk, dbFk);
|
|
494
501
|
if (reasons.length > 0) {
|
|
495
502
|
mismatched.push({
|
|
503
|
+
name: sdfFk.name,
|
|
504
|
+
dbName: dbFk.name,
|
|
505
|
+
// Actual DB constraint name (conname) from the DB side, used to DROP
|
|
506
|
+
// the existing constraint before re-adding with SDF-derived naming.
|
|
507
|
+
dbConstraintName: dbFk._dbConstraintName,
|
|
496
508
|
localKey: sdfFk.localKey,
|
|
497
509
|
target: sdfFk.target,
|
|
498
510
|
references: sdfFk.references,
|