@restforgejs/platform 5.2.16 → 5.3.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.
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/endpoint/create.js +69 -6
- package/generators/cli/payload/sync.js +16 -6
- package/generators/cli/project/auth.js +2 -2
- package/generators/cli/project/sdk.js +112 -0
- package/generators/lib/arg-parser.js +6 -0
- package/generators/lib/auth/processor-generator.js +5 -3
- package/generators/lib/auth/templates/processor/google.js.tmpl +178 -0
- package/generators/lib/auth/templates/processor/login.js.tmpl +8 -8
- package/generators/lib/auth/templates/processor/logout.js.tmpl +2 -2
- package/generators/lib/auth/templates/processor/me.js.tmpl +2 -2
- package/generators/lib/auth/templates/processor/refresh.js.tmpl +6 -6
- package/generators/lib/auth/templates/processor/register.js.tmpl +4 -4
- package/generators/lib/auth/templates/processor/reset-password.js.tmpl +7 -7
- package/generators/lib/auth/templates/rfx_auth.js.tmpl +3 -0
- package/generators/lib/generators/model-generator.js +46 -59
- package/generators/lib/help-generator.js +41 -3
- package/generators/lib/payload/endpoint-schema-validator.js +8 -3
- package/generators/lib/payload/field-projections.js +116 -0
- package/generators/lib/payload/payload-runner.js +164 -48
- package/generators/lib/payload/schema-diff.js +108 -0
- package/generators/lib/sdk/generator.js +719 -0
- package/generators/lib/sdk/naming.js +48 -0
- package/generators/lib/sdk/runtime/README.md.tmpl +207 -0
- package/generators/lib/sdk/runtime/auth-client.js +186 -0
- package/generators/lib/sdk/runtime/deploy.mjs.tmpl +85 -0
- package/generators/lib/sdk/runtime/http-client.js +81 -0
- package/generators/lib/sdk/runtime/resource-client.js +59 -0
- package/generators/lib/sdk/runtime/storage.js +31 -0
- 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/generators/lib/utils/cli-output.js +40 -0
- package/generators/lib/utils/config-resolver.js +61 -0
- package/generators/lib/utils/database-introspector.js +28 -5
- package/integrity-manifest.json +18 -18
- 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/soft-delete-dashboard-guard.js +1 -1
- package/src/utils/sql-table-extractor.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
|
@@ -22,7 +22,7 @@ module.exports = {
|
|
|
22
22
|
return {
|
|
23
23
|
success: false,
|
|
24
24
|
statusCode: 400,
|
|
25
|
-
message: '
|
|
25
|
+
message: 'Username and password are required.',
|
|
26
26
|
timestamp: new Date().toISOString()
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -35,7 +35,7 @@ module.exports = {
|
|
|
35
35
|
return {
|
|
36
36
|
success: false,
|
|
37
37
|
statusCode: 409,
|
|
38
|
-
message: 'Username
|
|
38
|
+
message: 'Username is already taken.',
|
|
39
39
|
timestamp: new Date().toISOString()
|
|
40
40
|
};
|
|
41
41
|
}
|
|
@@ -55,7 +55,7 @@ module.exports = {
|
|
|
55
55
|
return {
|
|
56
56
|
success: true,
|
|
57
57
|
statusCode: 201,
|
|
58
|
-
message: '
|
|
58
|
+
message: 'Registration successful.',
|
|
59
59
|
data: {
|
|
60
60
|
user_id: userId,
|
|
61
61
|
username,
|
|
@@ -69,7 +69,7 @@ module.exports = {
|
|
|
69
69
|
return {
|
|
70
70
|
success: false,
|
|
71
71
|
statusCode: 500,
|
|
72
|
-
message: '
|
|
72
|
+
message: 'An internal server error occurred.',
|
|
73
73
|
timestamp: new Date().toISOString()
|
|
74
74
|
};
|
|
75
75
|
}
|
|
@@ -25,7 +25,7 @@ module.exports = {
|
|
|
25
25
|
return {
|
|
26
26
|
success: false,
|
|
27
27
|
statusCode: 400,
|
|
28
|
-
message: 'Email, password
|
|
28
|
+
message: 'Email, new password, and confirmation are required.',
|
|
29
29
|
timestamp: new Date().toISOString()
|
|
30
30
|
};
|
|
31
31
|
}
|
|
@@ -33,7 +33,7 @@ module.exports = {
|
|
|
33
33
|
return {
|
|
34
34
|
success: false,
|
|
35
35
|
statusCode: 400,
|
|
36
|
-
message: `Password
|
|
36
|
+
message: `Password must be at least ${MIN_PASSWORD} characters.`,
|
|
37
37
|
timestamp: new Date().toISOString()
|
|
38
38
|
};
|
|
39
39
|
}
|
|
@@ -41,7 +41,7 @@ module.exports = {
|
|
|
41
41
|
return {
|
|
42
42
|
success: false,
|
|
43
43
|
statusCode: 400,
|
|
44
|
-
message: '
|
|
44
|
+
message: 'Password confirmation does not match.',
|
|
45
45
|
timestamp: new Date().toISOString()
|
|
46
46
|
};
|
|
47
47
|
}
|
|
@@ -55,7 +55,7 @@ module.exports = {
|
|
|
55
55
|
return {
|
|
56
56
|
success: false,
|
|
57
57
|
statusCode: 404,
|
|
58
|
-
message: 'Email
|
|
58
|
+
message: 'Email is not registered.',
|
|
59
59
|
timestamp: new Date().toISOString()
|
|
60
60
|
};
|
|
61
61
|
}
|
|
@@ -64,7 +64,7 @@ module.exports = {
|
|
|
64
64
|
return {
|
|
65
65
|
success: false,
|
|
66
66
|
statusCode: 409,
|
|
67
|
-
message: 'Email
|
|
67
|
+
message: 'Email is registered to multiple accounts. Contact administrator.',
|
|
68
68
|
timestamp: new Date().toISOString()
|
|
69
69
|
};
|
|
70
70
|
}
|
|
@@ -90,7 +90,7 @@ module.exports = {
|
|
|
90
90
|
return {
|
|
91
91
|
success: true,
|
|
92
92
|
statusCode: 200,
|
|
93
|
-
message: 'Password
|
|
93
|
+
message: 'Password updated successfully. Please sign in with your new password.',
|
|
94
94
|
timestamp: new Date().toISOString()
|
|
95
95
|
};
|
|
96
96
|
} catch (error) {
|
|
@@ -98,7 +98,7 @@ module.exports = {
|
|
|
98
98
|
return {
|
|
99
99
|
success: false,
|
|
100
100
|
statusCode: 500,
|
|
101
|
-
message: '
|
|
101
|
+
message: 'An internal server error occurred.',
|
|
102
102
|
timestamp: new Date().toISOString()
|
|
103
103
|
};
|
|
104
104
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* POST /api/{{PROJECT_NAME}}/rfx_auth/register — buat user baru
|
|
8
8
|
* POST /api/{{PROJECT_NAME}}/rfx_auth/login — login, terbitkan access + refresh token
|
|
9
|
+
* POST /api/{{PROJECT_NAME}}/rfx_auth/google — login via Google ID token (find-or-create)
|
|
9
10
|
* POST /api/{{PROJECT_NAME}}/rfx_auth/refresh — tukar refresh token (rotation)
|
|
10
11
|
* POST /api/{{PROJECT_NAME}}/rfx_auth/logout — revoke refresh token
|
|
11
12
|
* POST /api/{{PROJECT_NAME}}/rfx_auth/reset-password — reset password via email (tanpa token)
|
|
@@ -21,6 +22,7 @@ const services = resolveServices();
|
|
|
21
22
|
|
|
22
23
|
const register = require('./processor/auth/register');
|
|
23
24
|
const login = require('./processor/auth/login');
|
|
25
|
+
const google = require('./processor/auth/google');
|
|
24
26
|
const refresh = require('./processor/auth/refresh');
|
|
25
27
|
const logout = require('./processor/auth/logout');
|
|
26
28
|
const me = require('./processor/auth/me');
|
|
@@ -83,6 +85,7 @@ function handle(processor, requiredFields, actionLabel) {
|
|
|
83
85
|
|
|
84
86
|
router.post('/register', handle(register, ['username', 'password'], 'register'));
|
|
85
87
|
router.post('/login', handle(login, ['username', 'password'], 'login'));
|
|
88
|
+
router.post('/google', handle(google, ['credential'], 'google'));
|
|
86
89
|
router.post('/refresh', handle(refresh, ['refresh_token'], 'refresh'));
|
|
87
90
|
router.post('/logout', handle(logout, [], 'logout'));
|
|
88
91
|
router.post('/reset-password', handle(resetPassword, ['email', 'new_password', 'confirm_password'], 'reset-password'));
|
|
@@ -61,38 +61,32 @@ class ModelGenerator {
|
|
|
61
61
|
*/
|
|
62
62
|
static generateModelContent(moduleName, endpointName, payload, databaseType) {
|
|
63
63
|
try {
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
console.log(`Using
|
|
79
|
-
return
|
|
80
|
-
} else {
|
|
81
|
-
console.log(`MySQL template not available, falling back to PostgreSQL template`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (databaseType === 'oracle') {
|
|
86
|
-
const oracleContent = this.generateOracleModelContent(moduleName, endpointName, payload);
|
|
87
|
-
if (oracleContent) {
|
|
88
|
-
console.log(`Using Oracle-specific template for ${endpointName} model`);
|
|
89
|
-
return oracleContent;
|
|
90
|
-
} else {
|
|
91
|
-
console.log(`Oracle template not available, falling back to PostgreSQL template`);
|
|
64
|
+
// Tipe database non-postgres yang diminta eksplisit WAJIB memakai template
|
|
65
|
+
// dialeknya. Bila template-nya tidak tersedia, kosong, atau melempar error,
|
|
66
|
+
// generator HARD-FAIL — tidak boleh fallback senyap ke PostgreSQL karena akan
|
|
67
|
+
// menghasilkan model dialek salah (mis. sintaks pg untuk project Oracle).
|
|
68
|
+
const dialectGenerators = {
|
|
69
|
+
sqlite: { label: 'SQLite', fn: this.generateSqliteModelContent },
|
|
70
|
+
mysql: { label: 'MySQL', fn: this.generateMysqlModelContent },
|
|
71
|
+
oracle: { label: 'Oracle', fn: this.generateOracleModelContent }
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const dialect = dialectGenerators[databaseType];
|
|
75
|
+
if (dialect) {
|
|
76
|
+
const content = dialect.fn.call(this, moduleName, endpointName, payload);
|
|
77
|
+
if (content) {
|
|
78
|
+
console.log(`Using ${dialect.label}-specific template for ${endpointName} model`);
|
|
79
|
+
return content;
|
|
92
80
|
}
|
|
81
|
+
throw new Error(
|
|
82
|
+
`${dialect.label} model template unavailable or produced empty output for ` +
|
|
83
|
+
`'${moduleName}/${endpointName}'. Refusing to fall back to the PostgreSQL template ` +
|
|
84
|
+
`for an explicitly requested '${databaseType}' model. ` +
|
|
85
|
+
`Verify the ${dialect.label} template is installed and supports this payload.`
|
|
86
|
+
);
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
// Default PostgreSQL template
|
|
89
|
+
// Default PostgreSQL template (databaseType === 'postgres')
|
|
96
90
|
return this.generatePostgresModelContent(moduleName, endpointName, payload);
|
|
97
91
|
|
|
98
92
|
} catch (error) {
|
|
@@ -109,18 +103,17 @@ class ModelGenerator {
|
|
|
109
103
|
* @returns {string|null} Oracle model content atau null
|
|
110
104
|
*/
|
|
111
105
|
static generateOracleModelContent(moduleName, endpointName, payload) {
|
|
106
|
+
const oracleUtils = this.loadOracleUtils();
|
|
107
|
+
if (!oracleUtils || !oracleUtils.createOracleModelTemplate) {
|
|
108
|
+
return null; // modul template Oracle benar-benar tidak tersedia
|
|
109
|
+
}
|
|
110
|
+
// Error saat membangun template adalah bug nyata — jangan ditelan,
|
|
111
|
+
// bungkus dengan konteks lalu lempar agar terlihat oleh pemanggil.
|
|
112
112
|
try {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
const content = oracleUtils.createOracleModelTemplate(moduleName, endpointName, payload);
|
|
116
|
-
if (content && content.trim().length > 0) {
|
|
117
|
-
return content;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
113
|
+
const content = oracleUtils.createOracleModelTemplate(moduleName, endpointName, payload);
|
|
114
|
+
return content && content.trim().length > 0 ? content : null;
|
|
121
115
|
} catch (error) {
|
|
122
|
-
|
|
123
|
-
return null;
|
|
116
|
+
throw new Error(`Oracle model template failed for '${moduleName}/${endpointName}': ${error.message}`);
|
|
124
117
|
}
|
|
125
118
|
}
|
|
126
119
|
|
|
@@ -144,18 +137,15 @@ class ModelGenerator {
|
|
|
144
137
|
* @returns {string|null} SQLite model content atau null
|
|
145
138
|
*/
|
|
146
139
|
static generateSqliteModelContent(moduleName, endpointName, payload) {
|
|
140
|
+
const sqliteUtils = this.loadSqliteUtils();
|
|
141
|
+
if (!sqliteUtils || !sqliteUtils.createSqliteModelTemplate) {
|
|
142
|
+
return null; // modul template SQLite benar-benar tidak tersedia
|
|
143
|
+
}
|
|
147
144
|
try {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
const content = sqliteUtils.createSqliteModelTemplate(moduleName, endpointName, payload);
|
|
151
|
-
if (content && content.trim().length > 0) {
|
|
152
|
-
return content;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
145
|
+
const content = sqliteUtils.createSqliteModelTemplate(moduleName, endpointName, payload);
|
|
146
|
+
return content && content.trim().length > 0 ? content : null;
|
|
156
147
|
} catch (error) {
|
|
157
|
-
|
|
158
|
-
return null;
|
|
148
|
+
throw new Error(`SQLite model template failed for '${moduleName}/${endpointName}': ${error.message}`);
|
|
159
149
|
}
|
|
160
150
|
}
|
|
161
151
|
|
|
@@ -179,18 +169,15 @@ class ModelGenerator {
|
|
|
179
169
|
* @returns {string|null} MySQL model content atau null
|
|
180
170
|
*/
|
|
181
171
|
static generateMysqlModelContent(moduleName, endpointName, payload) {
|
|
172
|
+
const mysqlUtils = this.loadMysqlUtils();
|
|
173
|
+
if (!mysqlUtils || !mysqlUtils.createMysqlModelTemplate) {
|
|
174
|
+
return null; // modul template MySQL benar-benar tidak tersedia
|
|
175
|
+
}
|
|
182
176
|
try {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
const content = mysqlUtils.createMysqlModelTemplate(moduleName, endpointName, payload);
|
|
186
|
-
if (content && content.trim().length > 0) {
|
|
187
|
-
return content;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
177
|
+
const content = mysqlUtils.createMysqlModelTemplate(moduleName, endpointName, payload);
|
|
178
|
+
return content && content.trim().length > 0 ? content : null;
|
|
191
179
|
} catch (error) {
|
|
192
|
-
|
|
193
|
-
return null;
|
|
180
|
+
throw new Error(`MySQL model template failed for '${moduleName}/${endpointName}': ${error.message}`);
|
|
194
181
|
}
|
|
195
182
|
}
|
|
196
183
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const HELP_WIDTH = 100;
|
|
4
|
+
|
|
3
5
|
const CATEGORY_ORDER = ['generation', 'management', 'introspection', 'utility'];
|
|
4
6
|
const CATEGORY_LABEL = {
|
|
5
7
|
generation: 'Generation',
|
|
@@ -131,6 +133,27 @@ function flagSignature(flagDef) {
|
|
|
131
133
|
return ` <${flagDef.type}>`;
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
function wrapText(text, firstAvail, contIndent) {
|
|
137
|
+
const words = text.split(' ');
|
|
138
|
+
const lines = [];
|
|
139
|
+
let current = '';
|
|
140
|
+
let avail = firstAvail;
|
|
141
|
+
|
|
142
|
+
for (const word of words) {
|
|
143
|
+
if (!current) {
|
|
144
|
+
current = word;
|
|
145
|
+
} else if (current.length + 1 + word.length <= avail) {
|
|
146
|
+
current += ' ' + word;
|
|
147
|
+
} else {
|
|
148
|
+
lines.push(current);
|
|
149
|
+
current = word;
|
|
150
|
+
avail = HELP_WIDTH - contIndent;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (current) lines.push(current);
|
|
154
|
+
return lines;
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
function defaultRepr(def) {
|
|
135
158
|
if (def === null) return 'null';
|
|
136
159
|
if (def === undefined) return 'undefined';
|
|
@@ -201,7 +224,12 @@ function generateCommandHelp(contract) {
|
|
|
201
224
|
for (const posDef of positional) {
|
|
202
225
|
const sig = posDef.required ? `<${posDef.name}>` : `[${posDef.name}]`;
|
|
203
226
|
const label = `${sig} <${posDef.type}>`;
|
|
204
|
-
|
|
227
|
+
const prefix = ` ${padRight(label, colWidth)} `;
|
|
228
|
+
const descLines = wrapText(posDef.description, HELP_WIDTH - prefix.length, prefix.length);
|
|
229
|
+
lines.push(`${prefix}${descLines[0]}`);
|
|
230
|
+
for (let i = 1; i < descLines.length; i++) {
|
|
231
|
+
lines.push(`${' '.repeat(prefix.length)}${descLines[i]}`);
|
|
232
|
+
}
|
|
205
233
|
}
|
|
206
234
|
lines.push('');
|
|
207
235
|
}
|
|
@@ -211,7 +239,12 @@ function generateCommandHelp(contract) {
|
|
|
211
239
|
for (const fname of requiredFlagNames) {
|
|
212
240
|
const fdef = flags[fname];
|
|
213
241
|
const label = `--${fname}${flagSignature(fdef)}`;
|
|
214
|
-
|
|
242
|
+
const prefix = ` ${padRight(label, colWidth)} `;
|
|
243
|
+
const descLines = wrapText(fdef.description, HELP_WIDTH - prefix.length, prefix.length);
|
|
244
|
+
lines.push(`${prefix}${descLines[0]}`);
|
|
245
|
+
for (let i = 1; i < descLines.length; i++) {
|
|
246
|
+
lines.push(`${' '.repeat(prefix.length)}${descLines[i]}`);
|
|
247
|
+
}
|
|
215
248
|
}
|
|
216
249
|
lines.push('');
|
|
217
250
|
}
|
|
@@ -222,7 +255,12 @@ function generateCommandHelp(contract) {
|
|
|
222
255
|
const fdef = flags[fname];
|
|
223
256
|
const label = `--${fname}${flagSignature(fdef)}`;
|
|
224
257
|
const suffix = ` (default: ${defaultRepr(fdef.default)})`;
|
|
225
|
-
|
|
258
|
+
const prefix = ` ${padRight(label, colWidth)} `;
|
|
259
|
+
const descLines = wrapText(`${fdef.description}${suffix}`, HELP_WIDTH - prefix.length, prefix.length);
|
|
260
|
+
lines.push(`${prefix}${descLines[0]}`);
|
|
261
|
+
for (let i = 1; i < descLines.length; i++) {
|
|
262
|
+
lines.push(`${' '.repeat(prefix.length)}${descLines[i]}`);
|
|
263
|
+
}
|
|
226
264
|
}
|
|
227
265
|
lines.push('');
|
|
228
266
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
const path = require('path');
|
|
24
24
|
const { DatabaseIntrospector } = require('../utils/database-introspector');
|
|
25
25
|
const { resolveConfig, printDefaultConfigWarning } = require('../utils/config-resolver');
|
|
26
|
-
const { compareSchemaStrict, formatDriftReport } = require('./schema-diff');
|
|
26
|
+
const { compareSchemaStrict, formatDriftReport, resolveFieldProjectionInputs } = require('./schema-diff');
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Buat Error object dengan property `exitCode` agar cli-entry dapat
|
|
@@ -57,7 +57,8 @@ function createExitError(message, exitCode) {
|
|
|
57
57
|
* @returns {Promise<{
|
|
58
58
|
* status: 'ok' | 'skipped',
|
|
59
59
|
* reason?: string,
|
|
60
|
-
* columnsChecked?: number
|
|
60
|
+
* columnsChecked?: number,
|
|
61
|
+
* projectionInputs?: {physicalColumns: string[], readSourceColumns: string[], datatablesColumns: string[]|null}
|
|
61
62
|
* }>}
|
|
62
63
|
* @throws {Error} Dengan property exitCode (1=drift, 2=usage, 3=connection)
|
|
63
64
|
*/
|
|
@@ -128,8 +129,11 @@ async function validateEndpointSchema(options) {
|
|
|
128
129
|
: path.join(workingDir || process.cwd(), 'payload');
|
|
129
130
|
|
|
130
131
|
let comparison;
|
|
132
|
+
let projectionInputs = null;
|
|
131
133
|
try {
|
|
132
134
|
comparison = await compareSchemaStrict(payload, db, { payloadDir });
|
|
135
|
+
// Reuse db handle (D2): compute per-source column inputs untuk deriveFieldProjections
|
|
136
|
+
projectionInputs = await resolveFieldProjectionInputs(payload, db, { payloadDir });
|
|
133
137
|
} finally {
|
|
134
138
|
try { await db.close(); } catch (_e) { /* ignore */ }
|
|
135
139
|
}
|
|
@@ -158,7 +162,8 @@ async function validateEndpointSchema(options) {
|
|
|
158
162
|
|
|
159
163
|
return {
|
|
160
164
|
status: 'ok',
|
|
161
|
-
columnsChecked: comparison.totalColumnsChecked || 0
|
|
165
|
+
columnsChecked: comparison.totalColumnsChecked || 0,
|
|
166
|
+
projectionInputs
|
|
162
167
|
};
|
|
163
168
|
}
|
|
164
169
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isSoftDeleteEnabled, SOFT_DELETE_COLUMNS } = require('../dbschema-kit/soft-delete-constants');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Derive field projection whitelists from fieldName and introspected source columns.
|
|
7
|
+
* Pure function — no DB, no side effects.
|
|
8
|
+
*
|
|
9
|
+
* Kebijakan case-matching: case-insensitive (lowercase normalization).
|
|
10
|
+
* DB introspection mengembalikan nama kolom dengan case yang bisa berbeda
|
|
11
|
+
* tergantung dialect (Oracle uppercase, PostgreSQL lowercase, dll.).
|
|
12
|
+
* Pencocokan dilakukan lowercase di kedua sisi agar konsisten.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} input
|
|
15
|
+
* @param {string[]} input.fieldName - Array fieldName dari payload
|
|
16
|
+
* @param {string[]} input.physicalColumns - Kolom fisik tabel dari introspeksi DB
|
|
17
|
+
* @param {string[]|null} input.readSourceColumns - Kolom output sumber-baca-resolusi
|
|
18
|
+
* (viewName → viewQuery → tableName). Null atau [] → sumber gagal diintrospeksi.
|
|
19
|
+
* @param {string[]|null} input.datatablesColumns - Kolom output datatablesQuery.
|
|
20
|
+
* null = tidak ada datatablesQuery (fallback ke schemaFields).
|
|
21
|
+
* [] = datatablesQuery ada tapi introspeksi gagal (hard-fail kecuali ada override).
|
|
22
|
+
* @param {Object} [input.overrides={}]
|
|
23
|
+
* @param {string[]} [input.overrides.readableFields] - Override eksplisit readableFields
|
|
24
|
+
* @param {string[]} [input.overrides.datatablesFields] - Override eksplisit datatablesFields
|
|
25
|
+
* @returns {{ schemaFields: string[], readableFields: string[], datatablesFields: string[] }}
|
|
26
|
+
* @throws {Error} Bila validasi override gagal atau hard-fail D3 terpenuhi
|
|
27
|
+
*/
|
|
28
|
+
function deriveFieldProjections({
|
|
29
|
+
fieldName,
|
|
30
|
+
physicalColumns,
|
|
31
|
+
readSourceColumns,
|
|
32
|
+
datatablesColumns,
|
|
33
|
+
overrides = {}
|
|
34
|
+
}) {
|
|
35
|
+
// Validasi override: tiap elemen harus ⊆ fieldName
|
|
36
|
+
for (const key of ['readableFields', 'datatablesFields']) {
|
|
37
|
+
if (!overrides[key]) continue;
|
|
38
|
+
for (const f of overrides[key]) {
|
|
39
|
+
if (!fieldName.includes(f)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Override ${key} berisi "${f}" yang tidak ada di fieldName. ` +
|
|
42
|
+
`Nilai valid: [${fieldName.join(', ')}]`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const toSet = (arr) => new Set((arr || []).map(c => String(c).toLowerCase()));
|
|
49
|
+
|
|
50
|
+
// schemaFields = fieldName ∩ physicalColumns (pertahankan urutan fieldName)
|
|
51
|
+
const physicalSet = toSet(physicalColumns);
|
|
52
|
+
const schemaFields = fieldName.filter(f => physicalSet.has(f.toLowerCase()));
|
|
53
|
+
|
|
54
|
+
// readableFields
|
|
55
|
+
let readableFields;
|
|
56
|
+
if (overrides.readableFields) {
|
|
57
|
+
readableFields = overrides.readableFields;
|
|
58
|
+
} else {
|
|
59
|
+
// Hard-fail D3: sumber-baca tidak terintrospeksi dan tidak ada override
|
|
60
|
+
if (!readSourceColumns || readSourceColumns.length === 0) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
'Cannot derive readableFields: read-source resolution ' +
|
|
63
|
+
'(viewName → viewQuery → tableName) produced no columns. ' +
|
|
64
|
+
'Provide payload.readableFields as an override to bypass.'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const readSet = toSet(readSourceColumns);
|
|
68
|
+
readableFields = fieldName.filter(f => readSet.has(f.toLowerCase()));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// datatablesFields
|
|
72
|
+
let datatablesFields;
|
|
73
|
+
if (overrides.datatablesFields) {
|
|
74
|
+
datatablesFields = overrides.datatablesFields;
|
|
75
|
+
} else if (datatablesColumns === null) {
|
|
76
|
+
// Tidak ada datatablesQuery → fallback ke schemaFields
|
|
77
|
+
datatablesFields = schemaFields;
|
|
78
|
+
} else if (datatablesColumns.length === 0) {
|
|
79
|
+
// Hard-fail D3: datatablesQuery ada tapi introspeksi gagal
|
|
80
|
+
throw new Error(
|
|
81
|
+
'Cannot derive datatablesFields: datatablesQuery is set but ' +
|
|
82
|
+
'introspection produced no columns. ' +
|
|
83
|
+
'Provide payload.datatablesFields as an override to bypass.'
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
const dtSet = toSet(datatablesColumns);
|
|
87
|
+
datatablesFields = fieldName.filter(f => dtSet.has(f.toLowerCase()));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { schemaFields, readableFields, datatablesFields };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Augmentasi proyeksi dengan kolom soft-delete bila soft-delete aktif (D9).
|
|
95
|
+
* Meniru buildValidFieldsString: kolom soft-delete disuntik middleware ke WHERE,
|
|
96
|
+
* sehingga WAJIB ada di ketiga proyeksi agar validasi whitelist lolos.
|
|
97
|
+
* Augmentasi tak-bersyarat — tidak digate interseksi kolom sumber.
|
|
98
|
+
* Mutasi in-place; mengembalikan objek yang sama.
|
|
99
|
+
*
|
|
100
|
+
* @param {{ schemaFields: string[], readableFields: string[], datatablesFields: string[] }} fieldProjections
|
|
101
|
+
* @param {Object} payload - IR/payload dengan blok softDelete opsional
|
|
102
|
+
* @returns {typeof fieldProjections}
|
|
103
|
+
*/
|
|
104
|
+
function augmentProjectionsForSoftDelete(fieldProjections, payload) {
|
|
105
|
+
if (!isSoftDeleteEnabled(payload)) return fieldProjections;
|
|
106
|
+
for (const proj of ['schemaFields', 'readableFields', 'datatablesFields']) {
|
|
107
|
+
for (const col of SOFT_DELETE_COLUMNS) {
|
|
108
|
+
if (!fieldProjections[proj].includes(col)) {
|
|
109
|
+
fieldProjections[proj].push(col);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return fieldProjections;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { deriveFieldProjections, augmentProjectionsForSoftDelete };
|