@restforgejs/platform 5.3.7 → 5.3.9
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/cli/designer.js +1 -1
- package/generators/cli/fast-track.js +193 -45
- package/generators/cli/init.js +37 -3
- 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/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
package/cli/designer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
'use strict';const
|
|
3
|
+
'use strict';const a0_0x4ba6d1=a0_0x432f;(function(_0x52d2c5,_0x541a5b){const _0x19578f=a0_0x432f,_0x513db3=_0x52d2c5();while(!![]){try{const _0x30c732=parseInt(_0x19578f(0x198))/0x1+parseInt(_0x19578f(0x192))/0x2+parseInt(_0x19578f(0x193))/0x3+parseInt(_0x19578f(0x18d))/0x4*(parseInt(_0x19578f(0x18c))/0x5)+parseInt(_0x19578f(0x19d))/0x6*(-parseInt(_0x19578f(0x194))/0x7)+-parseInt(_0x19578f(0x18f))/0x8+-parseInt(_0x19578f(0x19a))/0x9;if(_0x30c732===_0x541a5b)break;else _0x513db3['push'](_0x513db3['shift']());}catch(_0x27979b){_0x513db3['push'](_0x513db3['shift']());}}}(a0_0xff40,0x78bf4));function a0_0x432f(_0x2a88bd,_0x3d73de){_0x2a88bd=_0x2a88bd-0x18b;const _0xff40da=a0_0xff40();let _0x432f9d=_0xff40da[_0x2a88bd];if(a0_0x432f['iXVUhf']===undefined){var _0x59f6d6=function(_0x5eb74b){const _0x5bad87='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x201b56='',_0x4d4d53='';for(let _0xd8c081=0x0,_0x2661c0,_0x1b6a94,_0x3b3beb=0x0;_0x1b6a94=_0x5eb74b['charAt'](_0x3b3beb++);~_0x1b6a94&&(_0x2661c0=_0xd8c081%0x4?_0x2661c0*0x40+_0x1b6a94:_0x1b6a94,_0xd8c081++%0x4)?_0x201b56+=String['fromCharCode'](0xff&_0x2661c0>>(-0x2*_0xd8c081&0x6)):0x0){_0x1b6a94=_0x5bad87['indexOf'](_0x1b6a94);}for(let _0x1b7d08=0x0,_0x1e9f72=_0x201b56['length'];_0x1b7d08<_0x1e9f72;_0x1b7d08++){_0x4d4d53+='%'+('00'+_0x201b56['charCodeAt'](_0x1b7d08)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x4d4d53);};a0_0x432f['EueaTq']=_0x59f6d6,a0_0x432f['IVlthe']={},a0_0x432f['iXVUhf']=!![];}const _0x1fad21=_0xff40da[0x0],_0x239d45=_0x2a88bd+_0x1fad21,_0x24600f=a0_0x432f['IVlthe'][_0x239d45];return!_0x24600f?(_0x432f9d=a0_0x432f['EueaTq'](_0x432f9d),a0_0x432f['IVlthe'][_0x239d45]=_0x432f9d):_0x432f9d=_0x24600f,_0x432f9d;}const os=require('os'),path=require('path'),fs=require('fs'),{spawn}=require(a0_0x4ba6d1(0x19e));function resolveBinaryPath(){const _0x459539=a0_0x4ba6d1,_0x17ac1b={'XUiEd':function(_0x3f3706,_0xb5c54e){return _0x3f3706===_0xb5c54e;},'bEZLI':_0x459539(0x191),'xrTiG':'linux'},_0x16574b=os['platform'](),_0x1efcd9=path['resolve'](__dirname,'..',_0x459539(0x19c));if(_0x17ac1b[_0x459539(0x199)](_0x16574b,_0x17ac1b[_0x459539(0x19f)]))return path['join'](_0x1efcd9,'restforge-designer.exe');if(_0x16574b===_0x17ac1b['xrTiG'])return path[_0x459539(0x19b)](_0x1efcd9,'restforge-designer-linux');return null;}const binaryPath=resolveBinaryPath();!binaryPath&&(console[a0_0x4ba6d1(0x18b)]('Error:\x20restforge-designer\x20tidak\x20didukung\x20di\x20platform\x20ini\x20('+os[a0_0x4ba6d1(0x195)]()+').'),process[a0_0x4ba6d1(0x18e)](0x1));function a0_0xff40(){const _0x4bd115=['n2rzuvjSsG','CgXHDgzVCM0','A2LSBa','rxjYB3iGBwvUAMfSyw5Ryw4GCMvZDgzVCMDLlwrLC2LNBMvYoIa','oteXnJKYr2rvBhHi','wfvPrwq','nZiWmZuYohjWCNj3Dq','AM9PBG','yMLU','mtq0mJC5nMvSA09Htq','y2HPBgrFChjVy2vZCW','yKvAteK','zxjYB3i','oda1mgDQs2rurq','mJa0q3LxBwzX','zxHPDa','ndq4ndi4mfzuB0rfBq','CgLK','D2LUmZi','mtq5nJyZmg5ur0jfEG','mta2mtu2oenmyKvRBq'];a0_0xff40=function(){return _0x4bd115;};return a0_0xff40();}!fs['existsSync'](binaryPath)&&(console[a0_0x4ba6d1(0x18b)]('Error:\x20binary\x20restforge-designer\x20tidak\x20ditemukan\x20di\x20'+binaryPath+'.'),console['error']('Install\x20ulang\x20@restforgejs/platform\x20untuk\x20mendapatkan\x20binary\x20yang\x20sesuai.'),process[a0_0x4ba6d1(0x18e)](0x1));if(os['platform']()!=='win32')try{fs['chmodSync'](binaryPath,0x1ed);}catch{}const child=spawn(binaryPath,process['argv']['slice'](0x2),{'stdio':'inherit'});child['on']('error',_0x5a904c=>{const _0xc7abf5=a0_0x4ba6d1;console[_0xc7abf5(0x18b)](_0xc7abf5(0x197)+_0x5a904c['message']),process[_0xc7abf5(0x18e)](0x1);}),child['on']('exit',(_0x3a2b86,_0x411211)=>{const _0x5af42e=a0_0x4ba6d1,_0x3e4d86={'KzkEJ':function(_0x4d7da3,_0x284b66){return _0x4d7da3??_0x284b66;}};if(_0x411211)process[_0x5af42e(0x196)](process[_0x5af42e(0x190)],_0x411211);else process['exit'](_0x3e4d86['KzkEJ'](_0x3a2b86,0x0));});
|
|
@@ -54,7 +54,8 @@ const DEFAULTS = {
|
|
|
54
54
|
DB_USER: 'postgres',
|
|
55
55
|
DB_PASSWORD: 'postgres1234',
|
|
56
56
|
DB_NAME: 'dbsandbox',
|
|
57
|
-
DB_FILE: './data/sandbox.db'
|
|
57
|
+
DB_FILE: './data/sandbox.db',
|
|
58
|
+
CORS_ENABLED: 'true'
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
const DEFAULT_CONFIG_FILE = 'db-connection.env';
|
|
@@ -208,6 +209,16 @@ function describeDatabase(cfg) {
|
|
|
208
209
|
return `${cfg.DB_TYPE} @ ${cfg.DB_HOST}:${cfg.DB_PORT}/${cfg.DB_NAME}`;
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Default CORS_ORIGINS diturunkan dari origin web server (frontend) yang sudah
|
|
214
|
+
* diinput, mis. SERVER_ADDRESS=127.0.0.1 + WEB_SERVER_PORT=8000 →
|
|
215
|
+
* `http://127.0.0.1:8000`. Origin frontend inilah yang harus di-whitelist CORS
|
|
216
|
+
* agar aplikasi web bisa memanggil REST API dari browser.
|
|
217
|
+
*/
|
|
218
|
+
function defaultCorsOrigins(cfg) {
|
|
219
|
+
return `http://${cfg.SERVER_ADDRESS}:${cfg.WEB_SERVER_PORT}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
211
222
|
// ---------------------------------------------------------------------------
|
|
212
223
|
// Pengumpulan data preview (read-only, tanpa efek samping)
|
|
213
224
|
// ---------------------------------------------------------------------------
|
|
@@ -341,6 +352,34 @@ function resolveSourceConfig(cwd, explicitConfig) {
|
|
|
341
352
|
return { configName, fileCfg: data || {} };
|
|
342
353
|
}
|
|
343
354
|
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Normalisasi path file SQLite
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Normalisasi input DB_FILE SQLite dari user menjadi path yang lengkap.
|
|
361
|
+
*
|
|
362
|
+
* Aturan:
|
|
363
|
+
* - Tanpa komponen direktori (mis. "myapp" atau "myapp.db")
|
|
364
|
+
* → dimasukkan ke folder ./data/ → "./data/myapp.db"
|
|
365
|
+
* - Dengan direktori custom (mis. "./dataku/myapp")
|
|
366
|
+
* → path-nya dihormati, hanya ekstensi yang ditambahkan → "./dataku/myapp.db"
|
|
367
|
+
* - Ekstensi .db selalu ditambahkan jika belum ada.
|
|
368
|
+
*
|
|
369
|
+
* Contoh:
|
|
370
|
+
* "myapp" → "./data/myapp.db"
|
|
371
|
+
* "myapp.db" → "./data/myapp.db"
|
|
372
|
+
* "./dataku/myapp" → "./dataku/myapp.db"
|
|
373
|
+
* "./data/myapp.db" → "./data/myapp.db" (tidak berubah)
|
|
374
|
+
*/
|
|
375
|
+
function normalizeDbFilePath(input) {
|
|
376
|
+
let result = input.trim();
|
|
377
|
+
if (!result.endsWith('.db')) result += '.db';
|
|
378
|
+
const hasDir = result.includes('/') || result.includes('\\');
|
|
379
|
+
if (!hasDir) result = './data/' + result;
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
344
383
|
// ---------------------------------------------------------------------------
|
|
345
384
|
// Fase input konfigurasi (LICENSE + database), mengikuti fast-track.mjs
|
|
346
385
|
// ---------------------------------------------------------------------------
|
|
@@ -375,6 +414,11 @@ async function collectConfig(args, ask, fileCfg = {}) {
|
|
|
375
414
|
cfg.SERVER_PORT = await askField('REST_API_PORT', fileCfg.SERVER_PORT || DEFAULTS.SERVER_PORT);
|
|
376
415
|
cfg.WEB_SERVER_PORT = await askField('WEB_SERVER_PORT', DEFAULTS.WEB_SERVER_PORT);
|
|
377
416
|
|
|
417
|
+
// CORS: CORS_ENABLED diikutkan apa adanya (default true, tidak diprompt edit);
|
|
418
|
+
// CORS_ORIGINS dapat diedit dengan default origin web server yang baru diisi.
|
|
419
|
+
cfg.CORS_ENABLED = fileCfg.CORS_ENABLED || DEFAULTS.CORS_ENABLED;
|
|
420
|
+
cfg.CORS_ORIGINS = await askField('CORS_ORIGINS', fileCfg.CORS_ORIGINS || defaultCorsOrigins(cfg));
|
|
421
|
+
|
|
378
422
|
// DB_TYPE menentukan atribut yang diminta berikutnya.
|
|
379
423
|
console.log('');
|
|
380
424
|
console.log(' Available DB_TYPE: postgresql, mysql, oracle, sqlite');
|
|
@@ -387,7 +431,8 @@ async function collectConfig(args, ask, fileCfg = {}) {
|
|
|
387
431
|
console.log('');
|
|
388
432
|
// fileCfg.DB_NAME: file legacy hasil `restforge init` menyimpan path
|
|
389
433
|
// sqlite di DB_NAME, bukan DB_FILE (lihat init.js & server.js runtime).
|
|
390
|
-
|
|
434
|
+
const dbFileRaw = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE);
|
|
435
|
+
cfg.DB_FILE = normalizeDbFilePath(dbFileRaw);
|
|
391
436
|
cfg.DB_NAME = cfg.DB_FILE;
|
|
392
437
|
} else {
|
|
393
438
|
const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
|
|
@@ -414,6 +459,8 @@ function defaultCfgFromFile(args, fileCfg = {}) {
|
|
|
414
459
|
cfg.SERVER_ADDRESS = fileCfg.SERVER_ADDRESS || DEFAULTS.SERVER_ADDRESS;
|
|
415
460
|
cfg.SERVER_PORT = fileCfg.SERVER_PORT || DEFAULTS.SERVER_PORT;
|
|
416
461
|
cfg.WEB_SERVER_PORT = DEFAULTS.WEB_SERVER_PORT;
|
|
462
|
+
cfg.CORS_ENABLED = fileCfg.CORS_ENABLED || DEFAULTS.CORS_ENABLED;
|
|
463
|
+
cfg.CORS_ORIGINS = fileCfg.CORS_ORIGINS || defaultCorsOrigins(cfg);
|
|
417
464
|
cfg.DB_TYPE = (fileCfg.DB_TYPE || DEFAULTS.DB_TYPE).toLowerCase();
|
|
418
465
|
|
|
419
466
|
if (cfg.DB_TYPE === 'sqlite') {
|
|
@@ -447,6 +494,8 @@ function printExistingConfigSummary({ project, schemaFlag, configName, cfg, over
|
|
|
447
494
|
console.log(` License : ${maskLicense(cfg.LICENSE)}`);
|
|
448
495
|
console.log(` REST API : ${cfg.SERVER_ADDRESS}:${cfg.SERVER_PORT}`);
|
|
449
496
|
console.log(` Web server : ${cfg.SERVER_ADDRESS}:${cfg.WEB_SERVER_PORT}`);
|
|
497
|
+
console.log(` CORS : ${cfg.CORS_ENABLED}`);
|
|
498
|
+
console.log(` CORS Origin: ${cfg.CORS_ORIGINS}`);
|
|
450
499
|
console.log(` Mode : ${overwrite ? 'overwrite' : 'sync'}`);
|
|
451
500
|
console.log('');
|
|
452
501
|
console.log(' Database Configuration');
|
|
@@ -909,6 +958,8 @@ function envValuesFromCfg(cfg) {
|
|
|
909
958
|
SERVER_PORT: cfg.SERVER_PORT,
|
|
910
959
|
DB_TYPE: cfg.DB_TYPE
|
|
911
960
|
};
|
|
961
|
+
if (cfg.CORS_ENABLED !== undefined) v.CORS_ENABLED = cfg.CORS_ENABLED;
|
|
962
|
+
if (cfg.CORS_ORIGINS !== undefined) v.CORS_ORIGINS = cfg.CORS_ORIGINS;
|
|
912
963
|
if (cfg.DB_TYPE === 'sqlite') {
|
|
913
964
|
v.DB_FILE = cfg.DB_FILE;
|
|
914
965
|
v.DB_NAME = cfg.DB_NAME;
|
|
@@ -1262,16 +1313,25 @@ function runFrontendPipeline(ctx) {
|
|
|
1262
1313
|
* biasa membiarkan NODE_ENV kosong sehingga logger memakai pino-pretty (rapi).
|
|
1263
1314
|
*/
|
|
1264
1315
|
function writeServerStartScript(ctx) {
|
|
1265
|
-
const serveCmd = `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`;
|
|
1266
1316
|
const isWin = process.platform === 'win32';
|
|
1267
1317
|
const file = path.join(ctx.cwd, isWin ? 'server-start.bat' : 'server-start.sh');
|
|
1268
1318
|
let content;
|
|
1269
|
-
if (isWin) {
|
|
1319
|
+
if (isWin && ctx.runMode === 'pm2') {
|
|
1320
|
+
const appName = pm2Name(ctx.project, 'server');
|
|
1321
|
+
content = [
|
|
1322
|
+
'@echo off',
|
|
1323
|
+
'REM Start RESTForge REST API via pm2. Generated by fast-track.',
|
|
1324
|
+
'cd /d "%~dp0"',
|
|
1325
|
+
`call pm2 delete ${appName} >nul 2>&1`,
|
|
1326
|
+
`call pm2 start ecosystem.config.js --only ${appName}`,
|
|
1327
|
+
''
|
|
1328
|
+
].join('\r\n');
|
|
1329
|
+
} else if (isWin) {
|
|
1270
1330
|
content = [
|
|
1271
1331
|
'@echo off',
|
|
1272
1332
|
'REM Start RESTForge REST API (runtime server). Generated by fast-track.',
|
|
1273
1333
|
'cd /d "%~dp0"',
|
|
1274
|
-
`call
|
|
1334
|
+
`call npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`,
|
|
1275
1335
|
''
|
|
1276
1336
|
].join('\r\n');
|
|
1277
1337
|
} else {
|
|
@@ -1280,7 +1340,7 @@ function writeServerStartScript(ctx) {
|
|
|
1280
1340
|
'# Start RESTForge REST API (runtime server). Generated by fast-track.',
|
|
1281
1341
|
'set -e',
|
|
1282
1342
|
'cd "$(dirname "$0")"',
|
|
1283
|
-
|
|
1343
|
+
`npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`,
|
|
1284
1344
|
''
|
|
1285
1345
|
].join('\n');
|
|
1286
1346
|
}
|
|
@@ -1297,17 +1357,26 @@ function writeServerStartScript(ctx) {
|
|
|
1297
1357
|
* writeServerStartScript, dipakai juga sebagai target `pm2 start` di non-Windows.
|
|
1298
1358
|
*/
|
|
1299
1359
|
function writeFrontendStartScript(ctx) {
|
|
1300
|
-
const serveCmd = `npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`;
|
|
1301
1360
|
const isWin = process.platform === 'win32';
|
|
1302
1361
|
const appDir = path.join(ctx.cwd, 'frontend', 'apps', ctx.project);
|
|
1303
1362
|
const file = path.join(appDir, isWin ? 'frontend-start.bat' : 'frontend-start.sh');
|
|
1304
1363
|
let content;
|
|
1305
|
-
if (isWin) {
|
|
1364
|
+
if (isWin && ctx.runMode === 'pm2') {
|
|
1365
|
+
const appName = pm2Name(ctx.project, 'frontend');
|
|
1366
|
+
content = [
|
|
1367
|
+
'@echo off',
|
|
1368
|
+
'REM Start RESTForge frontend app via pm2. Generated by fast-track.',
|
|
1369
|
+
'cd /d "%~dp0"',
|
|
1370
|
+
`call pm2 delete ${appName} >nul 2>&1`,
|
|
1371
|
+
`call pm2 start ecosystem.config.js --only ${appName}`,
|
|
1372
|
+
''
|
|
1373
|
+
].join('\r\n');
|
|
1374
|
+
} else if (isWin) {
|
|
1306
1375
|
content = [
|
|
1307
1376
|
'@echo off',
|
|
1308
1377
|
'REM Start RESTForge frontend app. Generated by fast-track.',
|
|
1309
1378
|
'cd /d "%~dp0"',
|
|
1310
|
-
`call ${
|
|
1379
|
+
`call npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`,
|
|
1311
1380
|
''
|
|
1312
1381
|
].join('\r\n');
|
|
1313
1382
|
} else {
|
|
@@ -1316,7 +1385,7 @@ function writeFrontendStartScript(ctx) {
|
|
|
1316
1385
|
'# Start RESTForge frontend app. Generated by fast-track.',
|
|
1317
1386
|
'set -e',
|
|
1318
1387
|
'cd "$(dirname "$0")"',
|
|
1319
|
-
|
|
1388
|
+
`npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`,
|
|
1320
1389
|
''
|
|
1321
1390
|
].join('\n');
|
|
1322
1391
|
}
|
|
@@ -1342,18 +1411,102 @@ function pm2Name(project, kind) {
|
|
|
1342
1411
|
* (silent, tolerant bila belum ada) supaya re-run fast-track tidak gagal
|
|
1343
1412
|
* dengan "already launched". Return false bila pm2 tidak tersedia/gagal.
|
|
1344
1413
|
*/
|
|
1345
|
-
|
|
1414
|
+
/**
|
|
1415
|
+
* Start (ulang) satu app dari ecosystem.config.js via pm2. Idempotent: hapus
|
|
1416
|
+
* instance lama dulu (silent) supaya re-run fast-track tidak gagal "already
|
|
1417
|
+
* launched". Menggunakan `--only <name>` agar hanya app yang dimaksud yang
|
|
1418
|
+
* di-start dari file ecosystem yang bisa berisi beberapa app.
|
|
1419
|
+
*
|
|
1420
|
+
* Pendekatan ecosystem + `interpreter: none` (dideklarasikan di
|
|
1421
|
+
* writeEcosystemConfig) menghindari masalah pm2 default ke Node.js sebagai
|
|
1422
|
+
* interpreter saat diberikan file .bat / .sh secara langsung.
|
|
1423
|
+
*/
|
|
1424
|
+
function pm2StartScript(name, ecosystemPath) {
|
|
1346
1425
|
spawnSync(`pm2 delete ${name} --silent`, { shell: true, stdio: 'ignore' });
|
|
1347
|
-
// Windows menjalankan .bat via cmd.exe secara otomatis; --interpreter bash
|
|
1348
|
-
// hanya diperlukan untuk .sh di Linux/macOS.
|
|
1349
|
-
const interpreterFlag = process.platform !== 'win32' ? ' --interpreter bash' : '';
|
|
1350
1426
|
const r = spawnSync(
|
|
1351
|
-
`pm2 start "${
|
|
1427
|
+
`pm2 start "${ecosystemPath}" --only ${name}`,
|
|
1352
1428
|
{ shell: true, stdio: 'inherit' }
|
|
1353
1429
|
);
|
|
1354
1430
|
return !r.error && r.status === 0;
|
|
1355
1431
|
}
|
|
1356
1432
|
|
|
1433
|
+
/**
|
|
1434
|
+
* Generate ecosystem.config.js di root project. pm2 membaca file ini untuk
|
|
1435
|
+
* mengetahui cara menjalankan tiap app tanpa bergantung pada file launcher
|
|
1436
|
+
* (.bat / .sh) yang rentan salah interpreter.
|
|
1437
|
+
*
|
|
1438
|
+
* `script: "npx"` + `interpreter: "none"` + `args: "..."` adalah pola yang
|
|
1439
|
+
* bekerja seragam di Windows dan Linux: pm2 memanggil npx langsung sebagai
|
|
1440
|
+
* executable OS, bukan membungkusnya dengan Node.
|
|
1441
|
+
*/
|
|
1442
|
+
/**
|
|
1443
|
+
* Tulis file launcher Node.js kecil untuk satu proses pm2.
|
|
1444
|
+
*
|
|
1445
|
+
* pm2 menjalankan file .js dengan `node` secara default — tidak perlu
|
|
1446
|
+
* interpreter khusus. Launcher ini menggunakan spawn({ shell: true }) agar
|
|
1447
|
+
* `npx` resolves dengan benar di Windows (npx.cmd) maupun Linux, tanpa
|
|
1448
|
+
* membuka window terminal baru.
|
|
1449
|
+
*/
|
|
1450
|
+
function writeEcosystemConfig(ctx) {
|
|
1451
|
+
const apps = [];
|
|
1452
|
+
|
|
1453
|
+
if (ctx.scope.backend) {
|
|
1454
|
+
apps.push({
|
|
1455
|
+
name: pm2Name(ctx.project, 'server'),
|
|
1456
|
+
script: 'node_modules/@restforgejs/platform/server.js',
|
|
1457
|
+
args: `serve --project=${ctx.project} --config=${ctx.configFlag} --watch`,
|
|
1458
|
+
cwd: ctx.cwd,
|
|
1459
|
+
instances: 1,
|
|
1460
|
+
exec_mode: 'fork',
|
|
1461
|
+
autorestart: true,
|
|
1462
|
+
watch: false,
|
|
1463
|
+
max_memory_restart: '1G',
|
|
1464
|
+
node_args: '--max-old-space-size=4096',
|
|
1465
|
+
kill_timeout: 3000,
|
|
1466
|
+
windowsHide: true,
|
|
1467
|
+
env: {
|
|
1468
|
+
NODE_ENV: 'production',
|
|
1469
|
+
PM2_USAGE_METRICS: 'false'
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
if (ctx.scope.frontend) {
|
|
1475
|
+
// `serve` harus terinstall di node_modules project agar pm2 bisa
|
|
1476
|
+
// menjalankannya langsung sebagai file JS (tanpa shell/npx).
|
|
1477
|
+
const serveMain = path.join(ctx.cwd, 'node_modules', 'serve', 'build', 'main.js');
|
|
1478
|
+
if (!fs.existsSync(serveMain)) {
|
|
1479
|
+
console.log('\n Installing serve (static file server for frontend)...');
|
|
1480
|
+
const r = spawnSync('npm install serve --save-dev', { cwd: ctx.cwd, shell: true, stdio: 'inherit' });
|
|
1481
|
+
if (r.error || r.status !== 0) {
|
|
1482
|
+
console.log(' [WARN] serve install failed — frontend pm2 entry mungkin tidak jalan.');
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
apps.push({
|
|
1486
|
+
name: pm2Name(ctx.project, 'frontend'),
|
|
1487
|
+
script: 'node_modules/serve/build/main.js',
|
|
1488
|
+
args: `./frontend/apps/${ctx.project} -l ${ctx.cfg.WEB_SERVER_PORT}`,
|
|
1489
|
+
cwd: ctx.cwd,
|
|
1490
|
+
instances: 1,
|
|
1491
|
+
exec_mode: 'fork',
|
|
1492
|
+
autorestart: true,
|
|
1493
|
+
watch: false,
|
|
1494
|
+
max_memory_restart: '512M',
|
|
1495
|
+
kill_timeout: 3000,
|
|
1496
|
+
windowsHide: true,
|
|
1497
|
+
env: {
|
|
1498
|
+
NODE_ENV: 'production',
|
|
1499
|
+
PM2_USAGE_METRICS: 'false'
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const content = 'module.exports = ' + JSON.stringify({ apps }, null, 2) + ';\n';
|
|
1505
|
+
const filePath = path.join(ctx.cwd, 'ecosystem.config.js');
|
|
1506
|
+
fs.writeFileSync(filePath, content);
|
|
1507
|
+
return filePath;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1357
1510
|
// ---------------------------------------------------------------------------
|
|
1358
1511
|
// Pemilihan mode run service (Windows only): terminal baru atau pm2
|
|
1359
1512
|
// ---------------------------------------------------------------------------
|
|
@@ -1443,7 +1596,7 @@ async function startServerNow(ctx, runMode = 'terminal') {
|
|
|
1443
1596
|
if (runMode === 'pm2') {
|
|
1444
1597
|
console.log(`\n Starting via pm2: "${title}"`);
|
|
1445
1598
|
const name = pm2Name(ctx.project, 'server');
|
|
1446
|
-
const ok = pm2StartScript(name, ctx.
|
|
1599
|
+
const ok = pm2StartScript(name, ctx.ecosystemPath);
|
|
1447
1600
|
if (!ok) {
|
|
1448
1601
|
console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
|
|
1449
1602
|
return false;
|
|
@@ -1465,7 +1618,7 @@ async function startServerNow(ctx, runMode = 'terminal') {
|
|
|
1465
1618
|
// pakai pm2 supaya proses bisa dikelola normal (pm2 list/logs/reload/stop).
|
|
1466
1619
|
console.log(`\n Starting via pm2: "${title}"`);
|
|
1467
1620
|
const name = pm2Name(ctx.project, 'server');
|
|
1468
|
-
const ok = pm2StartScript(name, ctx.
|
|
1621
|
+
const ok = pm2StartScript(name, ctx.ecosystemPath);
|
|
1469
1622
|
if (!ok) {
|
|
1470
1623
|
console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
|
|
1471
1624
|
return false;
|
|
@@ -1490,7 +1643,7 @@ async function startServerNow(ctx, runMode = 'terminal') {
|
|
|
1490
1643
|
}
|
|
1491
1644
|
|
|
1492
1645
|
/** Konfirmasi lalu jalankan runtime server. Dipakai scope REST API Only. */
|
|
1493
|
-
async function maybeRunServer(ctx, ask
|
|
1646
|
+
async function maybeRunServer(ctx, ask) {
|
|
1494
1647
|
const serveCmd = `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`;
|
|
1495
1648
|
console.log('');
|
|
1496
1649
|
const answer = (await ask(' Run Runtime Server now? (Y/n): ')).trim().toLowerCase();
|
|
@@ -1498,8 +1651,7 @@ async function maybeRunServer(ctx, ask, prompter) {
|
|
|
1498
1651
|
console.log(` Skipped. Start later: ${serveCmd}`);
|
|
1499
1652
|
return;
|
|
1500
1653
|
}
|
|
1501
|
-
|
|
1502
|
-
await startServerNow(ctx, runMode);
|
|
1654
|
+
await startServerNow(ctx, ctx.runMode);
|
|
1503
1655
|
}
|
|
1504
1656
|
|
|
1505
1657
|
/**
|
|
@@ -1526,7 +1678,7 @@ async function startFrontendNow(ctx, runMode = 'terminal') {
|
|
|
1526
1678
|
if (runMode === 'pm2') {
|
|
1527
1679
|
console.log(`\n Starting via pm2: "${title}"`);
|
|
1528
1680
|
const name = pm2Name(ctx.project, 'frontend');
|
|
1529
|
-
const ok = pm2StartScript(name, ctx.
|
|
1681
|
+
const ok = pm2StartScript(name, ctx.ecosystemPath);
|
|
1530
1682
|
if (!ok) {
|
|
1531
1683
|
console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
|
|
1532
1684
|
return false;
|
|
@@ -1546,7 +1698,7 @@ async function startFrontendNow(ctx, runMode = 'terminal') {
|
|
|
1546
1698
|
} else {
|
|
1547
1699
|
console.log(`\n Starting via pm2: "${title}"`);
|
|
1548
1700
|
const name = pm2Name(ctx.project, 'frontend');
|
|
1549
|
-
const ok = pm2StartScript(name, ctx.
|
|
1701
|
+
const ok = pm2StartScript(name, ctx.ecosystemPath);
|
|
1550
1702
|
if (!ok) {
|
|
1551
1703
|
console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
|
|
1552
1704
|
return false;
|
|
@@ -1587,7 +1739,7 @@ async function startFrontendNow(ctx, runMode = 'terminal') {
|
|
|
1587
1739
|
* butuh API sudah hidup), baru lanjut frontend setelah server window
|
|
1588
1740
|
* terbuka + health check selesai.
|
|
1589
1741
|
*/
|
|
1590
|
-
async function maybeRunServerAndFrontend(ctx, ask
|
|
1742
|
+
async function maybeRunServerAndFrontend(ctx, ask) {
|
|
1591
1743
|
const serveCmd = `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`;
|
|
1592
1744
|
const frontendCmd = `npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`;
|
|
1593
1745
|
console.log('');
|
|
@@ -1597,9 +1749,8 @@ async function maybeRunServerAndFrontend(ctx, ask, prompter) {
|
|
|
1597
1749
|
console.log(` Skipped. Start later (in frontend/apps/${ctx.project}): ${frontendCmd}`);
|
|
1598
1750
|
return;
|
|
1599
1751
|
}
|
|
1600
|
-
|
|
1601
|
-
await
|
|
1602
|
-
await startFrontendNow(ctx, runMode);
|
|
1752
|
+
await startServerNow(ctx, ctx.runMode);
|
|
1753
|
+
await startFrontendNow(ctx, ctx.runMode);
|
|
1603
1754
|
}
|
|
1604
1755
|
|
|
1605
1756
|
// ---------------------------------------------------------------------------
|
|
@@ -1731,30 +1882,27 @@ module.exports = {
|
|
|
1731
1882
|
await confirmDefaultMode(ctx, prompter.ask);
|
|
1732
1883
|
}
|
|
1733
1884
|
|
|
1734
|
-
// 6) Eksekusi
|
|
1735
|
-
if (ctx.scope.backend)
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1885
|
+
// 6) Eksekusi pipeline.
|
|
1886
|
+
if (ctx.scope.backend) runBackendPipeline(ctx);
|
|
1887
|
+
if (ctx.scope.frontend) runFrontendPipeline(ctx);
|
|
1888
|
+
|
|
1889
|
+
// ecosystem.config.js dibuat setelah pipeline selesai.
|
|
1890
|
+
ctx.ecosystemPath = writeEcosystemConfig(ctx);
|
|
1891
|
+
|
|
1892
|
+
// Pilih run mode di Windows sebelum menulis bat file agar konten bat
|
|
1893
|
+
// sesuai pilihan (pm2 start ecosystem / npx langsung).
|
|
1894
|
+
ctx.runMode = process.platform === 'win32' ? await selectRunMode(prompter) : 'pm2';
|
|
1895
|
+
|
|
1896
|
+
if (ctx.scope.backend) ctx.serverStartFile = writeServerStartScript(ctx);
|
|
1897
|
+
if (ctx.scope.frontend) ctx.frontendStartFile = writeFrontendStartScript(ctx);
|
|
1746
1898
|
|
|
1747
1899
|
printFinalSummary(ctx);
|
|
1748
1900
|
|
|
1749
|
-
// 7) Tawarkan menjalankan service.
|
|
1750
|
-
// dialog konfirmasi gabungan, tapi eksekusi tetap wajib runtime
|
|
1751
|
-
// server lebih dulu baru frontend (frontend butuh API hidup).
|
|
1752
|
-
// Scope REST API Only -> dialog server saja (tidak ada frontend
|
|
1753
|
-
// yang di-generate, SCOPES tidak punya opsi frontend-only).
|
|
1901
|
+
// 7) Tawarkan menjalankan service.
|
|
1754
1902
|
if (ctx.scope.backend && ctx.scope.frontend) {
|
|
1755
|
-
await maybeRunServerAndFrontend(ctx, prompter.ask
|
|
1903
|
+
await maybeRunServerAndFrontend(ctx, prompter.ask);
|
|
1756
1904
|
} else if (ctx.scope.backend) {
|
|
1757
|
-
await maybeRunServer(ctx, prompter.ask
|
|
1905
|
+
await maybeRunServer(ctx, prompter.ask);
|
|
1758
1906
|
}
|
|
1759
1907
|
} finally {
|
|
1760
1908
|
prompter.close();
|
package/generators/cli/init.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* Dua mode operasi:
|
|
10
10
|
* 1. Interaktif (default, hanya saat stdin TTY dan TANPA --force):
|
|
11
11
|
* dialog konfirmasi gaya fast-track. Meminta input LICENSE, tipe database
|
|
12
|
-
* beserta atributnya, dan nama file config
|
|
13
|
-
* di-custom). Nilai input di-patch ke
|
|
12
|
+
* beserta atributnya, CORS_ORIGINS (default '*'), dan nama file config
|
|
13
|
+
* (default db-connection.env, bisa di-custom). Nilai input di-patch ke
|
|
14
|
+
* template lalu ditulis.
|
|
14
15
|
* 2. Non-interaktif (saat --force diberikan ATAU stdin bukan TTY):
|
|
15
16
|
* menulis template apa adanya ke config/db-connection.env (perilaku legacy).
|
|
16
17
|
* Jalur ini dipakai oleh orkestrator (fast-track) dan playbook yang
|
|
@@ -104,12 +105,35 @@ function dbDefaultsFor(dbType) {
|
|
|
104
105
|
return { ...DB_DEFAULTS, ...override };
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
// Default CORS_ORIGINS: '*' (semua origin). Selaras dengan default template dan
|
|
109
|
+
// perilaku backward-compatible middleware. Pengguna mengganti dengan daftar
|
|
110
|
+
// origin frontend spesifik untuk production saat prompt.
|
|
111
|
+
const DEFAULT_CORS_ORIGINS = '*';
|
|
112
|
+
|
|
107
113
|
/** Representasi database untuk baris review, berdasarkan konfigurasi input. */
|
|
108
114
|
function describeDatabase(cfg) {
|
|
109
115
|
if (cfg.DB_TYPE === 'sqlite') return `sqlite @ ${cfg.DB_FILE}`;
|
|
110
116
|
return `${cfg.DB_TYPE} @ ${cfg.DB_HOST}:${cfg.DB_PORT}/${cfg.DB_NAME}`;
|
|
111
117
|
}
|
|
112
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Normalisasi input DB_FILE SQLite dari user menjadi path yang lengkap.
|
|
121
|
+
*
|
|
122
|
+
* Aturan:
|
|
123
|
+
* - Tanpa komponen direktori (mis. "guestbook" atau "guestbook.db")
|
|
124
|
+
* → dimasukkan ke ./data/ → "./data/guestbook.db"
|
|
125
|
+
* - Dengan direktori custom (mis. "./dataku/guestbook")
|
|
126
|
+
* → path dihormati, hanya ekstensi yang ditambahkan → "./dataku/guestbook.db"
|
|
127
|
+
* - Ekstensi .db selalu ditambahkan jika belum ada.
|
|
128
|
+
*/
|
|
129
|
+
function normalizeDbFilePath(input) {
|
|
130
|
+
let result = input.trim();
|
|
131
|
+
if (!result.endsWith('.db')) result += '.db';
|
|
132
|
+
const hasDir = result.includes('/') || result.includes('\\');
|
|
133
|
+
if (!hasDir) result = './data/' + result;
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
113
137
|
/**
|
|
114
138
|
* Fase input konfigurasi interaktif: LICENSE lalu tipe database beserta
|
|
115
139
|
* atributnya. Mengembalikan objek cfg. `ask` di-inject agar dapat diuji.
|
|
@@ -139,7 +163,8 @@ async function collectConfig(ask) {
|
|
|
139
163
|
console.log(' SQLite mode: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD are ignored.');
|
|
140
164
|
console.log(' The database file path is set in DB_NAME (via DB_FILE).');
|
|
141
165
|
console.log('');
|
|
142
|
-
|
|
166
|
+
const dbFileRaw = await askField('DB_FILE (.db file path)', DB_DEFAULTS.DB_FILE);
|
|
167
|
+
cfg.DB_FILE = normalizeDbFilePath(dbFileRaw);
|
|
143
168
|
} else {
|
|
144
169
|
const d = dbDefaultsFor(cfg.DB_TYPE);
|
|
145
170
|
cfg.DB_HOST = await askField('DB_HOST', d.DB_HOST);
|
|
@@ -149,6 +174,13 @@ async function collectConfig(ask) {
|
|
|
149
174
|
cfg.DB_NAME = await askField('DB_NAME', d.DB_NAME);
|
|
150
175
|
}
|
|
151
176
|
|
|
177
|
+
// 3) CORS — origin frontend yang diizinkan mengakses API dari browser.
|
|
178
|
+
// Default '*' (semua origin); isi daftar origin spesifik untuk production.
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(' CORS_ORIGINS: frontend origin(s) allowed to call the API');
|
|
181
|
+
console.log(" (comma-separated, or '*' for all origins).");
|
|
182
|
+
cfg.CORS_ORIGINS = await askField('CORS_ORIGINS', DEFAULT_CORS_ORIGINS);
|
|
183
|
+
|
|
152
184
|
return cfg;
|
|
153
185
|
}
|
|
154
186
|
|
|
@@ -158,6 +190,7 @@ function envValuesFromCfg(cfg) {
|
|
|
158
190
|
LICENSE: cfg.LICENSE,
|
|
159
191
|
DB_TYPE: cfg.DB_TYPE
|
|
160
192
|
};
|
|
193
|
+
if (cfg.CORS_ORIGINS) v.CORS_ORIGINS = cfg.CORS_ORIGINS;
|
|
161
194
|
if (cfg.DB_TYPE === 'sqlite') {
|
|
162
195
|
// Untuk SQLite, path file disimpan pada DB_NAME (dikonsumsi runtime).
|
|
163
196
|
v.DB_NAME = cfg.DB_FILE;
|
|
@@ -222,6 +255,7 @@ async function confirmWrite(ctx, ask) {
|
|
|
222
255
|
console.log('====================================================');
|
|
223
256
|
console.log(` License : ${maskLicense(ctx.cfg.LICENSE)}`);
|
|
224
257
|
console.log(` Database : ${describeDatabase(ctx.cfg)}`);
|
|
258
|
+
console.log(` CORS : ${ctx.cfg.CORS_ORIGINS}`);
|
|
225
259
|
const existsNote = ctx.exists ? ' (EXISTING — will be overwritten)' : '';
|
|
226
260
|
console.log(` Target : config/${ctx.configFileName}${existsNote}`);
|
|
227
261
|
console.log('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const a0_0x10f767=a0_0x1b52;(function(_0x172be3,_0x57469a){const _0x7ec4a=a0_0x1b52,_0x46386f=_0x172be3();while(!![]){try{const _0x19e174=-parseInt(_0x7ec4a(0x1a4))/0x1+parseInt(_0x7ec4a(0x1ac))/0x2*(-parseInt(_0x7ec4a(0x1c4))/0x3)+parseInt(_0x7ec4a(0x1a7))/0x4+parseInt(_0x7ec4a(0x187))/0x5+parseInt(_0x7ec4a(0x1d3))/0x6+parseInt(_0x7ec4a(0x196))/0x7+parseInt(_0x7ec4a(0x1c2))/0x8*(-parseInt(_0x7ec4a(0x192))/0x9);if(_0x19e174===_0x57469a)break;else _0x46386f['push'](_0x46386f['shift']());}catch(_0x49c208){_0x46386f['push'](_0x46386f['shift']());}}}(a0_0xaa7d,0x39d29));const FORBIDDEN_FRONTEND_FIELDS=[a0_0x10f767(0x1c5),a0_0x10f767(0x199),'title','subtitle','color'],ALLOWED_PARAM_TYPES=[a0_0x10f767(0x1c9),a0_0x10f767(0x19d),'boolean','date'],FRONTEND_CONCERN_REASONS={'widgetType':'Visual\x20variant\x20(donut,\x20bar,\x20pie,\x20area)\x20is\x20a\x20frontend\x20rendering\x20concern\x20(separation\x20of\x20concerns).','layout':'Layout\x20is\x20a\x20frontend\x20rendering\x20concern.','title':a0_0x10f767(0x191),'subtitle':a0_0x10f767(0x191),'color':'Visual\x20color\x20is\x20a\x20frontend\x20rendering\x20concern.'},PAYLOAD_SHAPE={'discriminator':{'field':a0_0x10f767(0x1a9),'presentMeans':'dashboard\x20payload','absentMeans':'Not\x20a\x20dashboard\x20payload\x20(likely\x20CRUD\x20with\x20tableName,\x20or\x20invalid)','conflictsWith':'tableName','conflictRationale':'A\x20payload\x20with\x20both\x20\x27widgets\x27\x20and\x20\x27tableName\x27\x20is\x20rejected\x20by\x20DashboardValidator.\x20Pick\x20one\x20shape.'},'topLevelAllowed':[{'name':a0_0x10f767(0x1a9),'type':'array','required':!![],'minItems':0x1,'description':a0_0x10f767(0x183)},{'name':a0_0x10f767(0x1d1),'type':'object','required':![],'description':a0_0x10f767(0x18a)},{'name':'cache','type':'object','required':![],'description':'Optional\x20cache\x20configuration.\x20See\x20cacheSpec\x20for\x20details.'}],'topLevelForbidden':[{'name':'tableName','category':a0_0x10f767(0x197),'reason':'Reserved\x20for\x20CRUD\x20payloads.\x20A\x20dashboard\x20payload\x20must\x20declare\x20\x27widgets\x27\x20instead.'},...FORBIDDEN_FRONTEND_FIELDS['map'](_0x58eedc=>({'name':_0x58eedc,'category':a0_0x10f767(0x19f),'reason':FRONTEND_CONCERN_REASONS[_0x58eedc]}))]},WIDGET_SPEC={'requiredFields':[{'name':'id','type':a0_0x10f767(0x1c9),'constraint':a0_0x10f767(0x1a5),'description':'Widget\x20identifier;\x20used\x20as\x20the\x20response\x20key\x20in\x20the\x20dashboard\x20envelope.'}],'exclusiveQueryFields':{'rule':'A\x20widget\x20MUST\x20declare\x20exactly\x20one\x20of:\x20\x27query\x27\x20OR\x20\x27queries\x27.\x20Both\x20or\x20neither\x20is\x20rejected.','options':[{'name':'query','type':'string','format':a0_0x10f767(0x1ba),'description':a0_0x10f767(0x1bc),'responseShape':a0_0x10f767(0x194)},{'name':a0_0x10f767(0x19c),'type':'object','format':a0_0x10f767(0x1cb),'minKeys':0x1,'description':a0_0x10f767(0x1a8),'responseShape':a0_0x10f767(0x1c1)}]},'forbiddenFields':FORBIDDEN_FRONTEND_FIELDS},PARAM_SPEC={'container':'top-level\x20\x27params\x27\x20object','keyConvention':a0_0x10f767(0x1b0),'perEntryFields':[{'name':a0_0x10f767(0x1b4),'required':!![],'allowedValues':ALLOWED_PARAM_TYPES,'description':'Param\x20data\x20type.\x20Validates\x20request\x20body\x20and\x20shapes\x20runtime\x20parameter\x20binding.'},{'name':a0_0x10f767(0x1ae),'required':![],'type':'boolean','default':![],'description':'When\x20true,\x20the\x20request\x20body\x20MUST\x20include\x20this\x20param\x20(otherwise\x20400).'},{'name':a0_0x10f767(0x1bf),'required':![],'type':a0_0x10f767(0x19b),'description':a0_0x10f767(0x180)}]},SCALAR_COLLAPSE_RULES=[{'appliesTo':a0_0x10f767(0x1a3),'rule':'Always\x20wrap\x20as\x20{\x20items:\x20[...]\x20}\x20regardless\x20of\x20SQL\x20result\x20shape.','exampleSqlShape':a0_0x10f767(0x1ab),'exampleResponse':'\x22shopping_categories\x22:\x20{\x20\x22items\x22:\x20[{\x20\x22name\x22:\x20\x22Lands\x22\x20},\x20{\x20\x22name\x22:\x20\x22Houses\x22\x20}]\x20}'},{'appliesTo':a0_0x10f767(0x193),'rule':'Collapse\x20to\x20scalar\x20primitive\x20(the\x20value\x20of\x20the\x20single\x20column).','exampleSqlShape':a0_0x10f767(0x1aa),'exampleResponse':a0_0x10f767(0x18d)},{'appliesTo':'widget.queries.<key>\x20with\x20SQL\x20returning\x201\x20row\x20×\x20multiple\x20columns','rule':'Collapse\x20to\x20object\x20whose\x20keys\x20are\x20SQL\x20column\x20names\x20(lowercased).','exampleSqlShape':'1\x20row\x20×\x202\x20cols,\x20output\x20columns\x20\x27direction\x27,\x20\x27pct\x27','exampleResponse':a0_0x10f767(0x1b6)},{'appliesTo':a0_0x10f767(0x1b1),'rule':'Return\x20as\x20array\x20of\x20objects\x20(no\x20collapse).','exampleSqlShape':'N\x20rows\x20×\x20M\x20cols','exampleResponse':a0_0x10f767(0x190)}],COMMON_WIDGET_PATTERNS=[{'id':a0_0x10f767(0x184),'name':'Metric\x20+\x20Donut\x20Breakdown','useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20breakdown\x20across\x20categories.\x20Suitable\x20for\x20widgets\x20like\x20\x27Expected\x20Earnings\x27\x20that\x20show\x20total\x20value,\x20percentage\x20change,\x20and\x20per-category\x20contribution.','payloadShape':{'id':'<widget_id>','queries':{'value':a0_0x10f767(0x188),'trend':a0_0x10f767(0x18e),'items':a0_0x10f767(0x1be)}},'sqlShapesPerKey':[{'key':a0_0x10f767(0x198),'shape':a0_0x10f767(0x1c6),'outputColumns':[a0_0x10f767(0x198)],'collapseRule':'scalar\x20primitive'},{'key':'trend','shape':a0_0x10f767(0x189),'outputColumns':[a0_0x10f767(0x1ce),'pct'],'collapseRule':'object'},{'key':a0_0x10f767(0x18b),'shape':a0_0x10f767(0x182),'outputColumns':['label',a0_0x10f767(0x198)],'collapseRule':a0_0x10f767(0x1bd)}],'responseShape':{'value':'\x2269700\x22','trend':'{\x20\x22direction\x22:\x20\x22up\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}','items':'[{\x20\x22label\x22:\x20\x22Shoes\x22,\x20\x22value\x22:\x20\x227660\x22\x20},\x20{\x20\x22label\x22:\x20\x22Gaming\x22,\x20\x22value\x22:\x20\x222820\x22\x20},\x20{\x20\x22label\x22:\x20\x22Others\x22,\x20\x22value\x22:\x20\x2245257\x22\x20}]'},'referenceWidgetId':a0_0x10f767(0x1b2),'socNotes':a0_0x10f767(0x186)},{'id':'metric_sparkline','name':a0_0x10f767(0x195),'useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20sparkline\x20mini-chart\x20for\x20short\x20windows\x20(7\x20days,\x2012\x20months,\x20etc.).\x20Suitable\x20for\x20widgets\x20like\x20\x27Average\x20Daily\x20Sales\x27.','payloadShape':{'id':a0_0x10f767(0x1c8),'queries':{'value':'file:query/<path>/value.sql','trend':a0_0x10f767(0x18e),'points':'file:query/<path>/points.sql'}},'sqlShapesPerKey':[{'key':a0_0x10f767(0x198),'shape':a0_0x10f767(0x1c6),'outputColumns':[a0_0x10f767(0x198)],'collapseRule':a0_0x10f767(0x1a6)},{'key':a0_0x10f767(0x18f),'shape':a0_0x10f767(0x189),'outputColumns':['direction',a0_0x10f767(0x1d4)],'collapseRule':'object'},{'key':'points','shape':'N\x20rows\x20×\x202\x20columns','outputColumns':['period','value'],'collapseRule':'array\x20of\x20objects'}],'responseShape':{'value':a0_0x10f767(0x1b3),'trend':'{\x20\x22direction\x22:\x20\x22up\x22,\x20\x22pct\x22:\x20\x222.6\x22\x20}','points':'[{\x20\x22period\x22:\x20\x222026-04-24\x22,\x20\x22value\x22:\x20\x221850\x22\x20},\x20...\x20]'},'referenceWidgetId':'avg_daily_sales','socNotes':a0_0x10f767(0x1af)},{'id':'metric_progress_to_goal','name':'Metric\x20+\x20Progress\x20to\x20Goal','useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20progress\x20bar\x20against\x20a\x20period\x20target.\x20Suitable\x20for\x20widgets\x20like\x20\x27Orders\x20This\x20Month\x27.','payloadShape':{'id':a0_0x10f767(0x1c8),'queries':{'value':a0_0x10f767(0x1d7),'trend':'file:query/<path>/trend.sql','target':a0_0x10f767(0x1cf)}},'sqlShapesPerKey':[{'key':'value','shape':a0_0x10f767(0x1c6),'outputColumns':['value\x20(or\x20current)'],'collapseRule':a0_0x10f767(0x1a6)},{'key':a0_0x10f767(0x18f),'shape':'1\x20row\x20×\x202\x20columns','outputColumns':[a0_0x10f767(0x1ce),a0_0x10f767(0x1d4)],'collapseRule':a0_0x10f767(0x1ad)},{'key':'target','shape':'1\x20row\x20×\x201\x20column','outputColumns':[a0_0x10f767(0x1cc)],'collapseRule':'scalar\x20primitive'}],'responseShape':{'value':'\x221836\x22','trend':'{\x20\x22direction\x22:\x20\x22down\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}','target':a0_0x10f767(0x1c7)},'referenceWidgetId':'orders_this_month','socNotes':'Frontend\x20computes\x20to_goal\x20=\x20target\x20-\x20value\x20and\x20pct\x20=\x20round(value\x20/\x20target\x20*\x20100)\x20for\x20the\x20progress\x20bar.\x20Visual\x20width\x20is\x20presentational\x20and\x20must\x20NOT\x20live\x20in\x20the\x20backend\x20payload.\x20If\x20progress\x20involves\x20complex\x20business\x20rules\x20(e.g.\x20exclude\x20weekends,\x20prorated\x20workdays),\x20use\x20a\x20single\x20multi-column\x20query\x20so\x20\x27pct\x27\x20is\x20a\x20stable\x20business\x20fact\x20rather\x20than\x20visual\x20width.'}],NAMING_CONVENTION={'dashboardName':{'constraint':'MUST\x20start\x20with\x20\x27dash-\x27\x20prefix','minLength':0x6,'maxLength':0x32,'regex':a0_0x10f767(0x185),'examples':[a0_0x10f767(0x1c0),'dash-inbound','dash-author-stats'],'rationale':a0_0x10f767(0x181)}},URL_PATTERN={'method':'POST','path':'/api/{project}/{name}/dashboard','exampleFull':a0_0x10f767(0x1bb),'requestBodyShape':{'params':a0_0x10f767(0x1b8),'widgets':a0_0x10f767(0x1d0)},'responseShape':{'envelope':'{\x20success:\x20boolean,\x20data:\x20{\x20<widgetId>:\x20<perWidgetResponse>,\x20...\x20}\x20}','perWidgetResponse':'Determined\x20by\x20scalarCollapseRules.\x20Failed\x20widgets\x20produce\x20{\x20error:\x20\x27...\x27\x20}\x20block\x20with\x20top-level\x20success\x20still\x20true\x20(one\x20widget\x20failure\x20does\x20NOT\x20fail\x20the\x20dashboard).'}},FILE_REFERENCE_CONVENTION={'format':a0_0x10f767(0x1ba),'pathRelativeTo':'payload\x20JSON\x20file\x20location','fileExtensionPolicy':a0_0x10f767(0x1ca),'resolvedAt':'generation\x20time\x20(NOT\x20runtime)','embedStrategy':a0_0x10f767(0x1cd),'implication':'Updating\x20an\x20SQL\x20file\x20requires\x20regenerating\x20the\x20dashboard\x20module\x20(\x27codegen_create_dashboard\x27)\x20for\x20changes\x20to\x20take\x20effect.'},PLACEHOLDER_CONVENTION={'format':a0_0x10f767(0x17f),'regex':a0_0x10f767(0x19a),'regexNotes':'Negative\x20lookbehind\x20prevents\x20matching\x20\x27::\x27\x20(Postgres\x20cast\x20syntax)\x20as\x20a\x20placeholder.','scanScope':a0_0x10f767(0x1d2),'constraint':'Every\x20placeholder\x20used\x20in\x20SQL\x20MUST\x20be\x20declared\x20in\x20\x27params\x27.\x20Validator\x20throws\x20Error\x20with\x20message\x20format:\x20\x22Widget\x20\x27<id>\x27\x20query\x20\x27<label>\x27\x20uses\x20undeclared\x20placeholder\x20\x27:<token>\x27\x20(declare\x20in\x20\x27params\x27)\x22.','exampleSql':'SELECT\x20*\x20FROM\x20stock_inbound\x20WHERE\x20EXTRACT(YEAR\x20FROM\x20inbound_date)\x20=\x20:year','exampleParamDeclaration':'{\x20\x22params\x22:\x20{\x20\x22year\x22:\x20{\x20\x22type\x22:\x20\x22number\x22,\x20\x22required\x22:\x20true\x20}\x20}\x20}'},CACHE_SPEC={'container':'top-level\x20\x27cache\x27\x20object','optional':!![],'rationale':'Dashboard\x20endpoint\x20may\x20opt-in\x20to\x20Redis-based\x20cache.\x20Pattern\x20follows\x20processor\x20cache\x20(see\x20feat-cache.md).\x20Cache\x20scope\x20is\x20the\x20full\x20response\x20envelope;\x20one\x20cache\x20entry\x20per\x20(params\x20+\x20widgets[]\x20subset)\x20combination.','fields':[{'name':'enabled','type':a0_0x10f767(0x1d5),'required':!![],'description':a0_0x10f767(0x1a2)},{'name':a0_0x10f767(0x1d6),'type':'number','required':![],'constraint':'>=\x200\x20(seconds)','default':'inherits\x20CACHE_TTL\x20env','description':a0_0x10f767(0x1b7)},{'name':'invalidates','type':a0_0x10f767(0x18c),'required':![],'default':'[]','description':a0_0x10f767(0x1a1)}],'validation':{'sqlCrossReference':a0_0x10f767(0x1a0),'errorOn':[a0_0x10f767(0x1c3),'Table\x20declared\x20in\x20invalidates,\x20but\x20not\x20detected\x20in\x20any\x20widget\x20SQL\x20(typo\x20or\x20dead\x20entry)'],'warningOn':['Table\x20detected\x20in\x20SQL,\x20but\x20not\x20registered\x20as\x20CRUD\x20endpoint\x20in\x20metadata\x20project\x20(likely\x20a\x20view,\x20CTE\x20alias,\x20or\x20cross-project\x20table\x20—\x20cascade\x20will\x20not\x20fire)']}},DOCUMENTATION_URL=a0_0x10f767(0x19e),DASHBOARD_CATALOG={'schemaVersion':'1.0','source':'dashboard-catalog','summary':{'totalAllowedTopLevelFields':PAYLOAD_SHAPE[a0_0x10f767(0x1b5)][a0_0x10f767(0x1b9)],'totalForbiddenFrontendFields':FORBIDDEN_FRONTEND_FIELDS['length'],'totalParamTypes':ALLOWED_PARAM_TYPES[a0_0x10f767(0x1b9)],'totalScalarCollapseRules':SCALAR_COLLAPSE_RULES[a0_0x10f767(0x1b9)],'totalCommonWidgetPatterns':COMMON_WIDGET_PATTERNS[a0_0x10f767(0x1b9)]},'payloadShape':PAYLOAD_SHAPE,'widgetSpec':WIDGET_SPEC,'paramSpec':PARAM_SPEC,'scalarCollapseRules':SCALAR_COLLAPSE_RULES,'commonWidgetPatterns':COMMON_WIDGET_PATTERNS,'namingConvention':NAMING_CONVENTION,'urlPattern':URL_PATTERN,'fileReferenceConvention':FILE_REFERENCE_CONVENTION,'placeholderConvention':PLACEHOLDER_CONVENTION,'cacheSpec':CACHE_SPEC,'documentationUrl':DOCUMENTATION_URL};module['exports']={'DASHBOARD_CATALOG':DASHBOARD_CATALOG};function a0_0x1b52(_0x47edfb,_0x4232a4){_0x47edfb=_0x47edfb-0x17f;const _0xaa7dba=a0_0xaa7d();let _0x1b5232=_0xaa7dba[_0x47edfb];if(a0_0x1b52['JdJtkH']===undefined){var _0x590e5e=function(_0x41e4a8){const _0x2cf039='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x58eedc='',_0xe8223f='';for(let _0x29c237=0x0,_0xc24a8e,_0x4a0b83,_0x3b14c9=0x0;_0x4a0b83=_0x41e4a8['charAt'](_0x3b14c9++);~_0x4a0b83&&(_0xc24a8e=_0x29c237%0x4?_0xc24a8e*0x40+_0x4a0b83:_0x4a0b83,_0x29c237++%0x4)?_0x58eedc+=String['fromCharCode'](0xff&_0xc24a8e>>(-0x2*_0x29c237&0x6)):0x0){_0x4a0b83=_0x2cf039['indexOf'](_0x4a0b83);}for(let _0x47d892=0x0,_0x35e381=_0x58eedc['length'];_0x47d892<_0x35e381;_0x47d892++){_0xe8223f+='%'+('00'+_0x58eedc['charCodeAt'](_0x47d892)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xe8223f);};a0_0x1b52['udMYia']=_0x590e5e,a0_0x1b52['LAjQYv']={},a0_0x1b52['JdJtkH']=!![];}const _0x41a2a3=_0xaa7dba[0x0],_0x31cceb=_0x47edfb+_0x41a2a3,_0x27b632=a0_0x1b52['LAjQYv'][_0x31cceb];return!_0x27b632?(_0x1b5232=a0_0x1b52['udMYia'](_0x1b5232),a0_0x1b52['LAjQYv'][_0x31cceb]=_0x1b5232):_0x1b5232=_0x27b632,_0x1b5232;}function a0_0xaa7d(){const _0x578d2e=['ue9tvcaVyxbPl21PBMKTAw52zw50B3j5l2rHC2GTAw5IB3vUzc9KyxnOyM9HCMq','u2LUz2XLifnrtcbXDwvYEsbMB3iGDgHLihDPzgDLDc4','yxjYyxKGB2yGB2jQzwn0CW','zMLSztPXDwvYEs88Cgf0Ad4VyNjLywTKB3DUlNnXBa','zgvMyxvSDa','zgfZAc1ZywXLCW','ugvYlwTLEsbIyxnLzcbVBIbZy2fSyxjdB2XSyxbZzvj1BgvZigjLBg93lG','ntznAhn6Evi','vgfIBguGyxbWzwfYCYbPBIbtuuWGqu5eigLUig1LDgfKyxrHihbYB2PLy3qSigj1DcbTAxnZAw5NigzYB20GAw52ywXPzgf0zxmGkgnHy2HLihn0ywXLihjPC2SP','mta1otqXmvz4zM9OAq','D2LKz2v0vhLWzq','msbYB3CGW5CGmsbJB2X1Bw4','iJi4odqI','phDPzgDLDf9Pzd4','C3rYAw5N','zNjLztSGlNnXBcbYzwnVBw1LBMrLzcbMB3iGzwrPDg9YigHPz2HSAwDODa','A2v54OAszMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa','DgfYz2v0','u1fmigzPBguGy29UDgvUDcbPCYbLBwjLzgrLzcbHCYbkyxzHu2nYAxb0ihrLBxbSyxrLigXPDgvYywWGAw5ZAwrLihrOzsbNzw5LCMf0zwqGBw9KDwXLigzPBguUifj1BNrPBwuGCgvYzM9YBxmGEMvYBYbKAxnRieKVtYbWzxiGCMvXDwvZDcdIGjqGywXSifnrtcbPCYbPBIbTzw1VCNKGywz0zxiGBw9KDwXLigXVywqU','zgLYzwn0Aw9U','zMLSztPXDwvYEs88Cgf0Ad4VDgfYz2v0lNnXBa','yxjYyxK8C3rYAw5NpIWGB3b0Aw9UywWG4Ocuihn1yNnLDcbVzIb3AwrNzxqGsurZihrVigv4zwn1DguUie9TAxqGDg8GzxHLy3v0zsbHBgWGzgvJBgfYzwqGD2LKz2v0CY4','CgfYyw1Z','qwXSihDPzgDLDcbtuuWG4OcuigjVDgGGj3f1zxj5jYaOC2LUz3vSyxiPigfUzcbLDMvYEsaNCxvLCMLLCY48A2v5pICU','mtq0mdaWnNv0v2rsqG','Cgn0','yM9VBgvHBG','DhrS','zMLSztPXDwvYEs88Cgf0Ad4Vy3vYCMvUDc5ZCwW','oNbHCMfTtMfTzq','rgvMyxvSDcb2ywX1zsbHChbSAwvKihDOzw4GDgHLihjLCxvLC3qGB21PDhmGDgHPCYbWyxjHBs4GvMfSAwrHDg9YigrVzxmGtK9uihn0CMLJDgX5ihr5CguTy2HLy2SGzgvMyxvSDdSGCNvUDgLTzsbPCYbYzxnWB25ZAwjSzsbMB3iGy29TCgf0AwjPBgL0Es4','vgHLihbYzwzPEcbIzwnVBwvZihbHCNqGB2yGDgHLifvstcbZzwDTzw50lIbuAguGCMvZzxj2zwqGC2nOzw1LigTLzxbZigrHC2HIB2fYzcbLBMrWB2LUDhmGDMLZDwfSBhKGzgLZDgLUy3qGzNjVBsbduLveigvUzhbVAw50CYbPBIb0AguGvvjmihnWywnLigfUzcbHBgXVD3mGzNv0DxjLihjVDxrPBMCGzgLMzMvYzw50Awf0Aw9UlG','tIbYB3DZimoxidiGy29SDw1UCW','tgLZDcbVzIb3AwrNzxqGzgvMAw5PDgLVBNmUie9YzgvYigLZigLUzM9YBwf0Aw9UywWGB25SEsaOCMvZCg9UC2uGA2v5CYbHCMuGyNKGD2LKz2v0igLKlcbUB3qGyxjYyxKGAw5KzxGPlG','Bwv0CMLJx2rVBNv0x2jYzwfRzg93BG','xMrHC2GTw2eTEKeTwJaTov8TxsSK','rNjVBNrLBMqGzgv0zxjTAw5LCYbKB251Dc9WAwuGDMfYAwfUDcWGy29SB3iGCgvYignHDgvNB3j5lcbHBMqGBgfIzwWGB3jKzxiUieLMihbLCI1JyxrLz29YEsbWzxjJzw50ywDLigLZig5LzwrLzcbMB3iGDgHLigrVBNv0igfYyYWGzNjVBNrLBMqGy29TChv0zxmGAxqGzNjVBsbPDgvTC1TPxs52ywX1zsaVihn1BsHPDgvTC1SQxs52ywX1zsKUie5Vig5LzwqGDg8GC2vUzcaNCgn0jYbMCM9TigjHy2TLBMqGDw5SzxnZihrOzsbMAwD1CMuGAxmGysbZDgfIBguGyNvZAw5LC3mGy2fSy3vSyxrPB24GAw5KzxbLBMrLBNqGB2yGDMLZDwfSihjLBMrLCMLUzY4','nty1oti1r0Lbv3fX','zMLSztPXDwvYEs88Cgf0Ad4VDMfSDwuUC3fS','msbYB3CGW5CGmIbJB2X1Bw5Z','ugfYyw1LDgvYignVBNrYywn0igzVCIb0AguGzgfZAgjVyxjKlIbfywnOigTLEsbPCYbHihbHCMfTig5HBwu7ihzHBhvLCYbKzxnJCMLIzsb0ExbLl3jLCxvPCMvKl2rLzMf1BhqUifbSywnLAg9SzgvYCYbPBNnPzguGD2LKz2v0ifnrtcbTDxn0ihjLzMvYzw5JzsbKzwnSyxjLzcbWyxjHBsbUyw1LCY4','AxrLBxm','yxjYyxK8C3rYAw5NpG','iNzHBhvLiJOGiJy5nZaWiG','zMLSztPXDwvYEs88Cgf0Ad4VDhjLBMqUC3fS','DhjLBMq','iML0zw1ZiJOGw3SGiMXHyMvSiJOGiLnOB2vZiIWGiNzHBhvLiJOGiJC2nJaIih0Sic4UlL0','vuKGBgfIzwWGAxmGysbMCM9UDgvUzcbYzw5KzxjPBMCGy29Uy2vYBI4','mZu2nJDIufnMqw8','D2LKz2v0lNf1zxjPzxmUpgTLEt4GD2L0AcbtuuWGCMv0DxjUAw5NideGCM93imoxideGy29SDw1U','qwX3yxLZihSGAxrLBxm6ifSUlI5Dih0GCMvNyxjKBgvZCYbVzIbtuuWGCMvZDwX0ihnOyxbLlG','twv0CMLJicSGu3bHCMTSAw5L','ndmXmtG2zeHYrKTZ','C2HHCguTy29UzMXPy3q','DMfSDwu','Bgf5B3v0','kd88itOPoIHBys16qs1Ax11Bys16qs1Amc05x10Qkq','yw55icHTDxn0igjLignVBxbHDgLIBguGD2L0AcbKzwnSyxjLzcaNDhLWzsCP','CxvLCMLLCW','BNvTyMvY','Ahr0Chm6lY9Yzxn0zM9Yz2uUzgv2l2rVy3mVC2vYDMvYl3f1zxj5lwrHDgeVzgfZAgjVyxjK','zNjVBNrLBMqTy29Uy2vYBG','v2HLBIbJywnOzs5LBMfIBgvKid09psb0CNvLigfUzcbPBNzHBgLKyxrLCYbPCYbUB24Tzw1WDhK6ihzHBgLKyxrVCIbLEhrYywn0CYb0ywjSzsbJyw5KAwrHDgvZigzYB20GD2LKz2v0ifnrtcaOCMvNzxGGrLjpts9kt0LoksWGy3jVC3mTCMvMzxjLBMnLCYb3AxrOig1LDgfKyxrHl3TWCM9Qzwn0Fs5QC29UicHLBMrWB2LUDhnBkL0UDgfIBgvoyw1LihDOzxjLihr5CguGpt09icjTB2r1BguIksWGyw5KigfZC2vYDhmGzxf1ywXPDhKGB2yGzxHWzwn0zwqGDNmGzgvJBgfYzwqGC2v0CY4GtwLZBwf0y2HLCYbHCMuGCMvWB3j0zwqGCgvYignHDgvNB3j5icHTAxnZAw5NlcbLEhrYysWGDw5TyxrJAgvKks4','tgLZDcbVzIbduLveihrHyMXLig5HBwvZihrOyxqSihDOzw4GD3jPDhrLBIWGD2LSBcb0CMLNz2vYigLUDMfSAwrHDgLVBIbVzIb0AgLZigrHC2HIB2fYzcbJywnOzs4','vg9Nz2XLignHy2HLigzLyxr1CMuGzM9YihrOAxmGzgfZAgjVyxjKlG','D2LKz2v0lNf1zxj5icHZAw5NDwXHCIK','ntG5mtjbrgvsrNm','BM9UlwvTChr5lcb1BMLXDwuGywnYB3nZihDPzgDLDhmGAw4GDgHLihnHBwuGCgf5Bg9Hza','C2nHBgfYihbYAw1PDgL2zq','mta0nZm4offMBMzfEG','txvSDgKTu1fmihDPzgDLDc4GrwfJAcbRzxKGyMvJB21LCYbHigTLEsbPBIb0AguGCMvZCg9UC2uGB2jQzwn0lG','D2LKz2v0CW','msbYB3CGW5CGmsbJB2WSig91Dhb1DcbJB2X1Bw4Gj3zHBhvLjW','yw55icGXihjVDYddLYaXignVBcWGtIbYB3DZimoxie0Gy29SCYWGzxrJlIK','mMH0r09YBW','B2jQzwn0','CMvXDwLYzwq','u3bHCMTSAw5LigXPyNjHCMLLCYaOqxbLEenOyxj0CYWGq2HHCNrPC3qSigv0yY4Pihr5CgLJywXSEsbUzwvKigeGCgXHAw4GBNvTyMvYigfYCMf5lIbgCM9UDgvUzcbTyxbZihbVAw50CY5TyxaOCca9pIbWlNzHBhvLks4GvgHLicDWzxjPB2qNigzPzwXKihn0yxLZigzVCIb0B29SDgLWigfUzcbNyxaTCMvZAwXPzw5JzsbHz2fPBNn0ig1PC3nPBMCGzgf5CY4GvxnLigDLBMvYyxrLx3nLCMLLCYbPBIbtuuWGDg8Gzw5ZDxjLignVBNnPC3rLBNqGCM93ignVDw50igv2zw4GzM9YigrHExmGD2L0AcbUBYb0CMfUC2fJDgLVBNmU','ugfYyw0GBMfTzsbTDxn0ig1HDgnOihrOzsbWBgfJzwHVBgrLCIbYzwDLEcbGw2eTEKeTwL9Dw2eTEKeTwJaTov9DkMaGkgfSCgHHBNvTzxjPyYaRihvUzgvYC2nVCMuSig11C3qGC3rHCNqGD2L0AcbSzxr0zxiGB3iGDw5KzxjZy29YzsKU','D2LKz2v0lNf1zxjPzxmUpgTLEt4GD2L0AcbtuuWGCMv0DxjUAw5Nie4GCM93CW','zxHWzwn0zwrFzwfYBMLUz3m','iJi0mJaI','DhLWzq','Dg9Wtgv2zwXbBgXVD2vK','iNrYzw5KiJOGEYaIzgLYzwn0Aw9UiJOGiNvWiIWGiNbJDci6iciYlJiIih0','vgLTzs10BY1SAxzLigLUihnLy29UzhmUidaGzwzMzwn0AxzLBhKGzgLZywjSzxmGy2fJAguGzM9YihrOAxmGzw50CNKU','B2jQzwn0iokaLcb2ywX1zxmGzM9YigrLy2XHCMvKihbHCMfTCYaODMfSAwrHDgvKigfNywLUC3qGCgfYyw1ZignVBNrYywn0oYbTAxnZAw5NihjLCxvPCMvKiokgKIa0mdaSihr5CguGBwLZBwf0y2GG4OAsidqWmcK','BgvUz3rO','zMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa'];a0_0xaa7d=function(){return _0x578d2e;};return a0_0xaa7d();}
|
|
1
|
+
const a0_0x435468=a0_0x1262;(function(_0x3a356b,_0x54a2ba){const _0x5b8e53=a0_0x1262,_0xd3c4e8=_0x3a356b();while(!![]){try{const _0x4ac358=parseInt(_0x5b8e53(0x1fe))/0x1*(parseInt(_0x5b8e53(0x207))/0x2)+parseInt(_0x5b8e53(0x1ef))/0x3+-parseInt(_0x5b8e53(0x1d3))/0x4+-parseInt(_0x5b8e53(0x20e))/0x5*(-parseInt(_0x5b8e53(0x1e1))/0x6)+-parseInt(_0x5b8e53(0x210))/0x7+parseInt(_0x5b8e53(0x20b))/0x8+-parseInt(_0x5b8e53(0x1df))/0x9*(-parseInt(_0x5b8e53(0x206))/0xa);if(_0x4ac358===_0x54a2ba)break;else _0xd3c4e8['push'](_0xd3c4e8['shift']());}catch(_0x295b88){_0xd3c4e8['push'](_0xd3c4e8['shift']());}}}(a0_0x4c37,0x3372f));const FORBIDDEN_FRONTEND_FIELDS=[a0_0x435468(0x1ed),a0_0x435468(0x1d6),'title',a0_0x435468(0x217),a0_0x435468(0x1e4)],ALLOWED_PARAM_TYPES=[a0_0x435468(0x205),'number','boolean','date'],FRONTEND_CONCERN_REASONS={'widgetType':'Visual\x20variant\x20(donut,\x20bar,\x20pie,\x20area)\x20is\x20a\x20frontend\x20rendering\x20concern\x20(separation\x20of\x20concerns).','layout':a0_0x435468(0x1c8),'title':'UI\x20label\x20is\x20a\x20frontend\x20rendering\x20concern.','subtitle':a0_0x435468(0x204),'color':a0_0x435468(0x1c9)},PAYLOAD_SHAPE={'discriminator':{'field':a0_0x435468(0x1e0),'presentMeans':a0_0x435468(0x209),'absentMeans':'Not\x20a\x20dashboard\x20payload\x20(likely\x20CRUD\x20with\x20tableName,\x20or\x20invalid)','conflictsWith':'tableName','conflictRationale':'A\x20payload\x20with\x20both\x20\x27widgets\x27\x20and\x20\x27tableName\x27\x20is\x20rejected\x20by\x20DashboardValidator.\x20Pick\x20one\x20shape.'},'topLevelAllowed':[{'name':'widgets','type':'array','required':!![],'minItems':0x1,'description':'List\x20of\x20widget\x20definitions.\x20Order\x20is\x20informational\x20only\x20(response\x20keys\x20are\x20by\x20widget\x20id,\x20not\x20array\x20index).'},{'name':'params','type':a0_0x435468(0x1f7),'required':![],'description':a0_0x435468(0x1eb)},{'name':'cache','type':a0_0x435468(0x1f7),'required':![],'description':a0_0x435468(0x1fd)}],'topLevelForbidden':[{'name':'tableName','category':'shape-conflict','reason':a0_0x435468(0x1f4)},...FORBIDDEN_FRONTEND_FIELDS['map'](_0x2b6e8d=>({'name':_0x2b6e8d,'category':'frontend-concern','reason':FRONTEND_CONCERN_REASONS[_0x2b6e8d]}))]},WIDGET_SPEC={'requiredFields':[{'name':'id','type':'string','constraint':'non-empty,\x20unique\x20across\x20widgets\x20in\x20the\x20same\x20payload','description':a0_0x435468(0x1f0)}],'exclusiveQueryFields':{'rule':'A\x20widget\x20MUST\x20declare\x20exactly\x20one\x20of:\x20\x27query\x27\x20OR\x20\x27queries\x27.\x20Both\x20or\x20neither\x20is\x20rejected.','options':[{'name':'query','type':a0_0x435468(0x205),'format':a0_0x435468(0x200),'description':a0_0x435468(0x1ca),'responseShape':'Always\x20{\x20items:\x20[...]\x20}\x20regardless\x20of\x20SQL\x20result\x20shape.'},{'name':a0_0x435468(0x1cb),'type':'object','format':'key→file:relative/path/to/query.sql','minKeys':0x1,'description':a0_0x435468(0x1d7),'responseShape':'Per-key\x20based\x20on\x20scalarCollapseRules\x20below.'}]},'forbiddenFields':FORBIDDEN_FRONTEND_FIELDS},PARAM_SPEC={'container':a0_0x435468(0x1fa),'keyConvention':a0_0x435468(0x208),'perEntryFields':[{'name':'type','required':!![],'allowedValues':ALLOWED_PARAM_TYPES,'description':'Param\x20data\x20type.\x20Validates\x20request\x20body\x20and\x20shapes\x20runtime\x20parameter\x20binding.'},{'name':'required','required':![],'type':'boolean','default':![],'description':'When\x20true,\x20the\x20request\x20body\x20MUST\x20include\x20this\x20param\x20(otherwise\x20400).'},{'name':a0_0x435468(0x1ff),'required':![],'type':a0_0x435468(0x1e8),'description':a0_0x435468(0x1d0)}]},SCALAR_COLLAPSE_RULES=[{'appliesTo':a0_0x435468(0x1fb),'rule':a0_0x435468(0x1e3),'exampleSqlShape':a0_0x435468(0x1d8),'exampleResponse':'\x22shopping_categories\x22:\x20{\x20\x22items\x22:\x20[{\x20\x22name\x22:\x20\x22Lands\x22\x20},\x20{\x20\x22name\x22:\x20\x22Houses\x22\x20}]\x20}'},{'appliesTo':a0_0x435468(0x219),'rule':'Collapse\x20to\x20scalar\x20primitive\x20(the\x20value\x20of\x20the\x20single\x20column).','exampleSqlShape':a0_0x435468(0x21e),'exampleResponse':a0_0x435468(0x21f)},{'appliesTo':a0_0x435468(0x20a),'rule':'Collapse\x20to\x20object\x20whose\x20keys\x20are\x20SQL\x20column\x20names\x20(lowercased).','exampleSqlShape':'1\x20row\x20×\x202\x20cols,\x20output\x20columns\x20\x27direction\x27,\x20\x27pct\x27','exampleResponse':'\x22trend\x22:\x20{\x20\x22direction\x22:\x20\x22up\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}'},{'appliesTo':'widget.queries.<key>\x20with\x20SQL\x20returning\x20N\x20rows','rule':a0_0x435468(0x1d1),'exampleSqlShape':'N\x20rows\x20×\x20M\x20cols','exampleResponse':a0_0x435468(0x20c)}],COMMON_WIDGET_PATTERNS=[{'id':'metric_donut_breakdown','name':'Metric\x20+\x20Donut\x20Breakdown','useCase':a0_0x435468(0x202),'payloadShape':{'id':'<widget_id>','queries':{'value':'file:query/<path>/value.sql','trend':a0_0x435468(0x1fc),'items':'file:query/<path>/breakdown.sql'}},'sqlShapesPerKey':[{'key':'value','shape':a0_0x435468(0x201),'outputColumns':[a0_0x435468(0x1d5)],'collapseRule':a0_0x435468(0x1cf)},{'key':'trend','shape':a0_0x435468(0x1d9),'outputColumns':[a0_0x435468(0x1ce),a0_0x435468(0x1e6)],'collapseRule':'object'},{'key':a0_0x435468(0x1dc),'shape':a0_0x435468(0x21b),'outputColumns':[a0_0x435468(0x1ee),'value'],'collapseRule':'array\x20of\x20objects'}],'responseShape':{'value':a0_0x435468(0x213),'trend':a0_0x435468(0x1ec),'items':'[{\x20\x22label\x22:\x20\x22Shoes\x22,\x20\x22value\x22:\x20\x227660\x22\x20},\x20{\x20\x22label\x22:\x20\x22Gaming\x22,\x20\x22value\x22:\x20\x222820\x22\x20},\x20{\x20\x22label\x22:\x20\x22Others\x22,\x20\x22value\x22:\x20\x2245257\x22\x20}]'},'referenceWidgetId':a0_0x435468(0x1e7),'socNotes':'Frontend\x20determines\x20donut/pie\x20variant,\x20color\x20per\x20category,\x20and\x20label\x20order.\x20If\x20per-category\x20percentage\x20is\x20needed\x20for\x20the\x20donut\x20arc,\x20frontend\x20computes\x20it\x20from\x20items[i].value\x20/\x20sum(items[*].value).\x20No\x20need\x20to\x20send\x20\x27pct\x27\x20from\x20backend\x20unless\x20the\x20figure\x20is\x20a\x20stable\x20business\x20calculation\x20independent\x20of\x20visual\x20rendering.'},{'id':'metric_sparkline','name':a0_0x435468(0x1cc),'useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20sparkline\x20mini-chart\x20for\x20short\x20windows\x20(7\x20days,\x2012\x20months,\x20etc.).\x20Suitable\x20for\x20widgets\x20like\x20\x27Average\x20Daily\x20Sales\x27.','payloadShape':{'id':'<widget_id>','queries':{'value':a0_0x435468(0x218),'trend':a0_0x435468(0x1fc),'points':'file:query/<path>/points.sql'}},'sqlShapesPerKey':[{'key':a0_0x435468(0x1d5),'shape':'1\x20row\x20×\x201\x20column','outputColumns':[a0_0x435468(0x1d5)],'collapseRule':a0_0x435468(0x1cf)},{'key':a0_0x435468(0x1dd),'shape':'1\x20row\x20×\x202\x20columns','outputColumns':[a0_0x435468(0x1ce),a0_0x435468(0x1e6)],'collapseRule':'object'},{'key':'points','shape':'N\x20rows\x20×\x202\x20columns','outputColumns':[a0_0x435468(0x212),a0_0x435468(0x1d5)],'collapseRule':a0_0x435468(0x214)}],'responseShape':{'value':'\x222420\x22','trend':'{\x20\x22direction\x22:\x20\x22up\x22,\x20\x22pct\x22:\x20\x222.6\x22\x20}','points':a0_0x435468(0x1f1)},'referenceWidgetId':a0_0x435468(0x1f9),'socNotes':'Sparkline\x20libraries\x20(ApexCharts,\x20Chartist,\x20etc.)\x20typically\x20need\x20a\x20plain\x20number\x20array.\x20Frontend\x20maps\x20points.map(p\x20=>\x20p.value).\x20The\x20\x27period\x27\x20field\x20stays\x20for\x20tooltip\x20and\x20gap-resilience\x20against\x20missing\x20days.\x20Use\x20generate_series\x20in\x20SQL\x20to\x20ensure\x20consistent\x20row\x20count\x20even\x20for\x20days\x20with\x20no\x20transactions.'},{'id':'metric_progress_to_goal','name':'Metric\x20+\x20Progress\x20to\x20Goal','useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20progress\x20bar\x20against\x20a\x20period\x20target.\x20Suitable\x20for\x20widgets\x20like\x20\x27Orders\x20This\x20Month\x27.','payloadShape':{'id':a0_0x435468(0x203),'queries':{'value':a0_0x435468(0x216),'trend':a0_0x435468(0x1fc),'target':'file:query/<path>/target.sql'}},'sqlShapesPerKey':[{'key':a0_0x435468(0x1d5),'shape':a0_0x435468(0x201),'outputColumns':['value\x20(or\x20current)'],'collapseRule':'scalar\x20primitive'},{'key':'trend','shape':'1\x20row\x20×\x202\x20columns','outputColumns':['direction','pct'],'collapseRule':'object'},{'key':a0_0x435468(0x1d2),'shape':a0_0x435468(0x201),'outputColumns':['target'],'collapseRule':'scalar\x20primitive'}],'responseShape':{'value':a0_0x435468(0x1e5),'trend':'{\x20\x22direction\x22:\x20\x22down\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}','target':a0_0x435468(0x20f)},'referenceWidgetId':'orders_this_month','socNotes':a0_0x435468(0x215)}],NAMING_CONVENTION={'dashboardName':{'constraint':'MUST\x20start\x20with\x20\x27dash-\x27\x20prefix','minLength':0x6,'maxLength':0x32,'regex':'^dash-[a-zA-Z0-9_-]+$','examples':['dash-sales','dash-inbound',a0_0x435468(0x21a)],'rationale':a0_0x435468(0x1e2)}},URL_PATTERN={'method':'POST','path':'/api/{project}/{name}/dashboard','exampleFull':a0_0x435468(0x1f6),'requestBodyShape':{'params':a0_0x435468(0x1ea),'widgets':a0_0x435468(0x1de)},'responseShape':{'envelope':a0_0x435468(0x1f2),'perWidgetResponse':'Determined\x20by\x20scalarCollapseRules.\x20Failed\x20widgets\x20produce\x20{\x20error:\x20\x27...\x27\x20}\x20block\x20with\x20top-level\x20success\x20still\x20true\x20(one\x20widget\x20failure\x20does\x20NOT\x20fail\x20the\x20dashboard).'}},FILE_REFERENCE_CONVENTION={'format':a0_0x435468(0x200),'pathRelativeTo':'payload\x20JSON\x20file\x20location','fileExtensionPolicy':'free;\x20.sql\x20recommended\x20for\x20editor\x20highlight','resolvedAt':'generation\x20time\x20(NOT\x20runtime)','embedStrategy':'SQL\x20file\x20content\x20is\x20embedded\x20as\x20JavaScript\x20template\x20literal\x20inside\x20the\x20generated\x20module\x20file.\x20Runtime\x20performs\x20zero\x20disk\x20I/O\x20per\x20request\x20—\x20all\x20SQL\x20is\x20in\x20memory\x20after\x20module\x20load.','implication':'Updating\x20an\x20SQL\x20file\x20requires\x20regenerating\x20the\x20dashboard\x20module\x20(\x27codegen_create_dashboard\x27)\x20for\x20changes\x20to\x20take\x20effect.'},PLACEHOLDER_CONVENTION={'format':':paramName','regex':'(?<!:):([a-zA-Z_][a-zA-Z0-9_]*)','regexNotes':'Negative\x20lookbehind\x20prevents\x20matching\x20\x27::\x27\x20(Postgres\x20cast\x20syntax)\x20as\x20a\x20placeholder.','scanScope':a0_0x435468(0x1e9),'constraint':'Every\x20placeholder\x20used\x20in\x20SQL\x20MUST\x20be\x20declared\x20in\x20\x27params\x27.\x20Validator\x20throws\x20Error\x20with\x20message\x20format:\x20\x22Widget\x20\x27<id>\x27\x20query\x20\x27<label>\x27\x20uses\x20undeclared\x20placeholder\x20\x27:<token>\x27\x20(declare\x20in\x20\x27params\x27)\x22.','exampleSql':a0_0x435468(0x1cd),'exampleParamDeclaration':'{\x20\x22params\x22:\x20{\x20\x22year\x22:\x20{\x20\x22type\x22:\x20\x22number\x22,\x20\x22required\x22:\x20true\x20}\x20}\x20}'},CACHE_SPEC={'container':'top-level\x20\x27cache\x27\x20object','optional':!![],'rationale':'Dashboard\x20endpoint\x20may\x20opt-in\x20to\x20Redis-based\x20cache.\x20Pattern\x20follows\x20processor\x20cache\x20(see\x20feat-cache.md).\x20Cache\x20scope\x20is\x20the\x20full\x20response\x20envelope;\x20one\x20cache\x20entry\x20per\x20(params\x20+\x20widgets[]\x20subset)\x20combination.','fields':[{'name':'enabled','type':a0_0x435468(0x1f3),'required':!![],'description':a0_0x435468(0x1d4)},{'name':a0_0x435468(0x20d),'type':a0_0x435468(0x220),'required':![],'constraint':a0_0x435468(0x1da),'default':a0_0x435468(0x221),'description':'Time-to-live\x20in\x20seconds.\x200\x20effectively\x20disables\x20cache\x20for\x20this\x20entry.'},{'name':'invalidates','type':a0_0x435468(0x1f8),'required':![],'default':'[]','description':a0_0x435468(0x21d)}],'validation':{'sqlCrossReference':'When\x20cache.enabled\x20===\x20true\x20and\x20invalidates\x20is\x20non-empty:\x20validator\x20extracts\x20table\x20candidates\x20from\x20widget\x20SQL\x20(regex\x20FROM/JOIN),\x20cross-references\x20with\x20metadata/{project}.json\x20(endpoints[*].tableName\x20where\x20type\x20===\x20\x22module\x22),\x20and\x20asserts\x20equality\x20of\x20expected\x20vs\x20declared\x20sets.\x20Mismatches\x20are\x20reported\x20per\x20category\x20(missing,\x20extra,\x20unmatched).','errorOn':['Table\x20appears\x20in\x20SQL\x20AND\x20in\x20metadata\x20project,\x20but\x20missing\x20from\x20invalidates\x20(cache\x20stale\x20risk)','Table\x20declared\x20in\x20invalidates,\x20but\x20not\x20detected\x20in\x20any\x20widget\x20SQL\x20(typo\x20or\x20dead\x20entry)'],'warningOn':[a0_0x435468(0x1f5)]}},DOCUMENTATION_URL='https://restforge.dev/docs/server/query-data/dashboard',DASHBOARD_CATALOG={'schemaVersion':a0_0x435468(0x1db),'source':'dashboard-catalog','summary':{'totalAllowedTopLevelFields':PAYLOAD_SHAPE[a0_0x435468(0x21c)]['length'],'totalForbiddenFrontendFields':FORBIDDEN_FRONTEND_FIELDS[a0_0x435468(0x211)],'totalParamTypes':ALLOWED_PARAM_TYPES['length'],'totalScalarCollapseRules':SCALAR_COLLAPSE_RULES[a0_0x435468(0x211)],'totalCommonWidgetPatterns':COMMON_WIDGET_PATTERNS['length']},'payloadShape':PAYLOAD_SHAPE,'widgetSpec':WIDGET_SPEC,'paramSpec':PARAM_SPEC,'scalarCollapseRules':SCALAR_COLLAPSE_RULES,'commonWidgetPatterns':COMMON_WIDGET_PATTERNS,'namingConvention':NAMING_CONVENTION,'urlPattern':URL_PATTERN,'fileReferenceConvention':FILE_REFERENCE_CONVENTION,'placeholderConvention':PLACEHOLDER_CONVENTION,'cacheSpec':CACHE_SPEC,'documentationUrl':DOCUMENTATION_URL};function a0_0x1262(_0xc7c11a,_0x587ff1){_0xc7c11a=_0xc7c11a-0x1c8;const _0x4c37c2=a0_0x4c37();let _0x1262ba=_0x4c37c2[_0xc7c11a];if(a0_0x1262['UTnOPF']===undefined){var _0x48a128=function(_0x332361){const _0x34038d='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x2b6e8d='',_0xbbe074='';for(let _0x37985d=0x0,_0x570946,_0x79a81a,_0x4129e3=0x0;_0x79a81a=_0x332361['charAt'](_0x4129e3++);~_0x79a81a&&(_0x570946=_0x37985d%0x4?_0x570946*0x40+_0x79a81a:_0x79a81a,_0x37985d++%0x4)?_0x2b6e8d+=String['fromCharCode'](0xff&_0x570946>>(-0x2*_0x37985d&0x6)):0x0){_0x79a81a=_0x34038d['indexOf'](_0x79a81a);}for(let _0x38ae8=0x0,_0x3fd361=_0x2b6e8d['length'];_0x38ae8<_0x3fd361;_0x38ae8++){_0xbbe074+='%'+('00'+_0x2b6e8d['charCodeAt'](_0x38ae8)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xbbe074);};a0_0x1262['IXAWax']=_0x48a128,a0_0x1262['jLkexN']={},a0_0x1262['UTnOPF']=!![];}const _0x35c74c=_0x4c37c2[0x0],_0x57410f=_0xc7c11a+_0x35c74c,_0x49cbf7=a0_0x1262['jLkexN'][_0x57410f];return!_0x49cbf7?(_0x1262ba=a0_0x1262['IXAWax'](_0x1262ba),a0_0x1262['jLkexN'][_0x57410f]=_0x1262ba):_0x1262ba=_0x49cbf7,_0x1262ba;}function a0_0x4c37(){const _0x4d6423=['yxzNx2rHAwX5x3nHBgvZ','Dg9WlwXLDMvSicDWyxjHBxmNig9IAMvJDa','D2LKz2v0lNf1zxj5icHZAw5NDwXHCIK','zMLSztPXDwvYEs88Cgf0Ad4VDhjLBMqUC3fS','t3b0Aw9UywWGy2fJAguGy29UzMLNDxjHDgLVBI4Gu2vLignHy2HLu3bLyYbMB3iGzgv0ywLSCY4','nJK4otnND1rvreG','zgvMyxvSDa','zMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa','msbYB3CGW5CGmsbJB2X1Bw4','sgvHzgXPBMuGBwv0CMLJihDPDgGGDhjLBMqGy2HPCcbHBMqGyNjLywTKB3DUigfJCM9ZCYbJyxrLz29YAwvZlIbtDwL0ywjSzsbMB3iGD2LKz2v0CYbSAwTLicDfEhbLy3rLzcbfyxjUAw5NCYCGDgHHDcbZAg93ihrVDgfSihzHBhvLlcbWzxjJzw50ywDLignOyw5NzsWGyw5KihbLCI1JyxrLz29YEsbJB250CMLIDxrPB24U','phDPzgDLDf9Pzd4','vuKGBgfIzwWGAxmGysbMCM9UDgvUzcbYzw5KzxjPBMCGy29Uy2vYBI4','C3rYAw5N','mtb6uwXVDge','mNzUz0LLyq','ugfYyw0GBMfTzsbTDxn0ig1HDgnOihrOzsbWBgfJzwHVBgrLCIbYzwDLEcbGw2eTEKeTwL9Dw2eTEKeTwJaTov9DkMaGkgfSCgHHBNvTzxjPyYaRihvUzgvYC2nVCMuSig11C3qGC3rHCNqGD2L0AcbSzxr0zxiGB3iGDw5KzxjZy29YzsKU','zgfZAgjVyxjKihbHEwXVywq','D2LKz2v0lNf1zxjPzxmUpgTLEt4GD2L0AcbtuuWGCMv0DxjUAw5NideGCM93imoxig11BhrPCgXLignVBhvTBNm','mte0mte5mLnNwhDxra','iML0zw1ZiJOGw3SGiMXHyMvSiJOGiLnOB2vZiIWGiNzHBhvLiJOGiJC2nJaIih0Sic4UlL0','DhrS','mZe1B2PMAfnu','iJi4odqI','mJKZnZe4nML2BgXwAG','BgvUz3rO','CgvYAw9K','iJy5nZaWiG','yxjYyxKGB2yGB2jQzwn0CW','rNjVBNrLBMqGy29TChv0zxmGDg9Fz29HBca9ihrHCMDLDcaTihzHBhvLigfUzcbWy3qGpsbYB3vUzcH2ywX1zsaVihrHCMDLDcaQideWmcKGzM9YihrOzsbWCM9NCMvZCYbIyxiUifzPC3vHBcb3Awr0AcbPCYbWCMvZzw50yxrPB25HBcbHBMqGBxvZDcbot1qGBgL2zsbPBIb0AguGyMfJA2vUzcbWyxLSB2fKlIbjzIbWCM9NCMvZCYbPBNzVBhzLCYbJB21WBgv4igj1C2LUzxnZihj1BgvZicHLlMCUigv4y2X1zguGD2vLA2vUzhmSihbYB3jHDgvKihDVCMTKyxLZksWGDxnLigeGC2LUz2XLig11BhrPlwnVBhvTBIbXDwvYEsbZBYaNCgn0jYbPCYbHihn0ywjSzsbIDxnPBMvZCYbMywn0ihjHDgHLCIb0AgfUihzPC3vHBcb3Awr0Ac4','zMLSztPXDwvYEs88Cgf0Ad4Vy3vYCMvUDc5ZCwW','C3vIDgL0Bgu','zMLSztPXDwvYEs88Cgf0Ad4VDMfSDwuUC3fS','D2LKz2v0lNf1zxjPzxmUpgTLEt4GD2L0AcbtuuWGCMv0DxjUAw5NideGCM93imoxideGy29SDw1U','zgfZAc1HDxrOB3iTC3rHDhm','tIbYB3DZimoxidiGy29SDw1UCW','Dg9Wtgv2zwXbBgXVD2vK','tgLZDcbVzIbduLveihrHyMXLig5HBwvZihrOyxqSihDOzw4GD3jPDhrLBIWGD2LSBcb0CMLNz2vYigLUDMfSAwrHDgLVBIbVzIb0AgLZigrHC2HIB2fYzcbJywnOzs4','msbYB3CGW5CGmsbJB2WSig91Dhb1DcbJB2X1Bw4Gj3zHBhvLjW','iNzHBhvLiJOGiJy5nZaWiG','BNvTyMvY','Aw5OzxjPDhmGq0fdsevFvfrmigvUDG','tgf5B3v0igLZigeGzNjVBNrLBMqGCMvUzgvYAw5NignVBMnLCM4U','vMLZDwfSignVBg9YigLZigeGzNjVBNrLBMqGCMvUzgvYAw5NignVBMnLCM4U','u2LUz2XLifnrtcbXDwvYEsbMB3iGDgHLihDPzgDLDc4','CxvLCMLLCW','twv0CMLJicSGu3bHCMTSAw5L','u0vmrunuicOGrLjptsbZDg9JA19PBMjVDw5KifDirvjfievyvfjbq1qOwuvbuIbguK9nigLUyM91BMrFzgf0zsKGpsa6EwvHCG','zgLYzwn0Aw9U','C2nHBgfYihbYAw1PDgL2zq','rgvMyxvSDcb2ywX1zsbHChbSAwvKihDOzw4GDgHLihjLCxvLC3qGB21PDhmGDgHPCYbWyxjHBs4GvMfSAwrHDg9YigrVzxmGtK9uihn0CMLJDgX5ihr5CguTy2HLy2SGzgvMyxvSDdSGCNvUDgLTzsbPCYbYzxnWB25ZAwjSzsbMB3iGy29TCgf0AwjPBgL0Es4','uMv0DxjUigfZigfYCMf5ig9Mig9IAMvJDhmGkg5VignVBgXHChnLks4','DgfYz2v0','mtqXnZm2ngrit0v6EG','vg9Nz2XLignHy2HLigzLyxr1CMuGzM9YihrOAxmGzgfZAgjVyxjKlG','DMfSDwu','Bgf5B3v0','txvSDgKTu1fmihDPzgDLDc4GrwfJAcbRzxKGyMvJB21LCYbHigTLEsbPBIb0AguGCMvZCg9UC2uGB2jQzwn0lG','yw55icGXihjVDYddLYaXignVBcWGtIbYB3DZimoxie0Gy29SCYWGzxrJlIK','msbYB3CGW5CGmIbJB2X1Bw5Z','pJ0GmcaOC2vJB25KCYK','ms4W','AxrLBxm','DhjLBMq','yxjYyxK8C3rYAw5NpIWGB3b0Aw9UywWG4Ocuihn1yNnLDcbVzIb3AwrNzxqGsurZihrVigv4zwn1DguUie9TAxqGDg8GzxHLy3v0zsbHBgWGzgvJBgfYzwqGD2LKz2v0CY4','mJqYodq3oxHvBgLora','D2LKz2v0CW','mtq3mtjeqMHczLu','vgHLihbYzwzPEcbIzwnVBwvZihbHCNqGB2yGDgHLifvstcbZzwDTzw50lIbuAguGCMvZzxj2zwqGC2nOzw1LigTLzxbZigrHC2HIB2fYzcbLBMrWB2LUDhmGDMLZDwfSBhKGzgLZDgLUy3qGzNjVBsbduLveigvUzhbVAw50CYbPBIb0AguGvvjmihnWywnLigfUzcbHBgXVD3mGzNv0DxjLihjVDxrPBMCGzgLMzMvYzw50Awf0Aw9UlG','qwX3yxLZihDYyxaGyxmGEYbPDgvTCZOGwY4UlL0GFsbYzwDHCMrSzxnZig9MifnrtcbYzxn1BhqGC2HHCguU','y29SB3i','iJe4mZyI','Cgn0','zxHWzwn0zwrFzwfYBMLUz3m','yw55icHTDxn0igjLignVBxbHDgLIBguGD2L0AcbKzwnSyxjLzcaNDhLWzsCP','qwXSihDPzgDLDcbtuuWG4OcuigjVDgGGj3f1zxj5jYaOC2LUz3vSyxiPigfUzcbLDMvYEsaNCxvLCMLLCY48A2v5pICU','B2jQzwn0iokaLcb2ywX1zxmGzM9YigrLy2XHCMvKihbHCMfTCYaODMfSAwrHDgvKigfNywLUC3qGCgfYyw1ZignVBNrYywn0oYbTAxnZAw5NihjLCxvPCMvKiokgKIa0mdaSihr5CguGBwLZBwf0y2GG4OAsidqWmcK','ugfYyw1LDgvYignVBNrYywn0igzVCIb0AguGzgfZAgjVyxjKlIbfywnOigTLEsbPCYbHihbHCMfTig5HBwu7ihzHBhvLCYbKzxnJCMLIzsb0ExbLl3jLCxvPCMvKl2rLzMf1BhqUifbSywnLAg9SzgvYCYbPBNnPzguGD2LKz2v0ifnrtcbTDxn0ihjLzMvYzw5JzsbKzwnSyxjLzcbWyxjHBsbUyw1LCY4','EYaIzgLYzwn0Aw9UiJOGiNvWiIWGiNbJDci6iciYlJiIih0','D2LKz2v0vhLWzq','BgfIzwW','mta0mZq3nwD3wuD4za','v2LKz2v0igLKzw50AwzPzxi7ihvZzwqGyxmGDgHLihjLC3bVBNnLigTLEsbPBIb0AguGzgfZAgjVyxjKigvUDMvSB3bLlG','w3SGiNbLCMLVzci6iciYmdi2lta0lti0iIWGiNzHBhvLiJOGiJe4ntaIih0Sic4UlIbD','EYbZDwnJzxnZoIbIB29SzwfUlcbKyxrHoIb7idX3AwrNzxrjzd46idXWzxjxAwrNzxrszxnWB25Zzt4Sic4UlIb9ih0','yM9VBgvHBG','uMvZzxj2zwqGzM9YiensvuqGCgf5Bg9HzhmUieeGzgfZAgjVyxjKihbHEwXVywqGBxvZDcbKzwnSyxjLicD3AwrNzxrZjYbPBNn0zwfKlG','vgfIBguGzgv0zwn0zwqGAw4Gu1fmlcbIDxqGBM90ihjLz2LZDgvYzwqGyxmGq1jvrcbLBMrWB2LUDcbPBIbTzxrHzgf0ysbWCM9Qzwn0icHSAwTLBhKGysb2Awv3lcbdveuGywXPyxmSig9YignYB3nZlxbYB2PLy3qGDgfIBguG4OcuignHC2nHzguGD2LSBcbUB3qGzMLYzsK','ue9tvcaVyxbPl21PBMKTAw52zw50B3j5l2rHC2GTAw5IB3vUzc9KyxnOyM9HCMq','B2jQzwn0','yxjYyxK8C3rYAw5NpG'];a0_0x4c37=function(){return _0x4d6423;};return a0_0x4c37();}module['exports']={'DASHBOARD_CATALOG':DASHBOARD_CATALOG};
|