@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.
Files changed (171) hide show
  1. package/build-info.json +2 -2
  2. package/cli/consumer-deploy.js +1 -1
  3. package/cli/consumer.js +1 -1
  4. package/cli/designer.js +1 -1
  5. package/generators/cli/fast-track.js +193 -45
  6. package/generators/cli/init.js +37 -3
  7. package/generators/lib/templates/dashboard-catalog.js +1 -1
  8. package/generators/lib/templates/db-connection-env.js +1 -1
  9. package/generators/lib/templates/dbschema-catalog.js +1 -1
  10. package/generators/lib/templates/field-validation-catalog.js +1 -1
  11. package/generators/lib/templates/mysql-template.js +1 -1
  12. package/generators/lib/templates/oracle-template.js +1 -1
  13. package/generators/lib/templates/postgres-template.js +1 -1
  14. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  15. package/generators/lib/templates/sqlite-template.js +1 -1
  16. package/integrity-manifest.json +18 -18
  17. package/package.json +1 -1
  18. package/scripts/verify-integrity.js +1 -1
  19. package/server.js +1 -1
  20. package/src/components/handlers/adjust_handler.js +1 -1
  21. package/src/components/handlers/audit_handler.js +1 -1
  22. package/src/components/handlers/delete_handler.js +1 -1
  23. package/src/components/handlers/export_handler.js +1 -1
  24. package/src/components/handlers/import_handler.js +1 -1
  25. package/src/components/handlers/insert_handler.js +1 -1
  26. package/src/components/handlers/update_handler.js +1 -1
  27. package/src/components/handlers/upload_handler.js +1 -1
  28. package/src/components/handlers/workflow_handler.js +1 -1
  29. package/src/components/integrations/webhook.js +1 -1
  30. package/src/consumers/baseConsumer.js +1 -1
  31. package/src/consumers/declarativeMapper.js +1 -1
  32. package/src/consumers/handlers/apiHandler.js +1 -1
  33. package/src/consumers/handlers/consoleHandler.js +1 -1
  34. package/src/consumers/handlers/databaseHandler.js +1 -1
  35. package/src/consumers/handlers/index.js +1 -1
  36. package/src/consumers/handlers/kafkaHandler.js +1 -1
  37. package/src/consumers/index.js +1 -1
  38. package/src/consumers/messageTransformer.js +1 -1
  39. package/src/consumers/validator.js +1 -1
  40. package/src/core/db/dialect/base-dialect.js +1 -1
  41. package/src/core/db/dialect/index.js +1 -1
  42. package/src/core/db/dialect/mysql-dialect.js +1 -1
  43. package/src/core/db/dialect/oracle-dialect.js +1 -1
  44. package/src/core/db/dialect/postgres-dialect.js +1 -1
  45. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  46. package/src/core/db/flatten-helper.js +1 -1
  47. package/src/core/db/query-builder-error.js +1 -1
  48. package/src/core/db/query-builder.js +1 -1
  49. package/src/core/db/relation-helper.js +1 -1
  50. package/src/core/handlers/delete_handler.js +1 -1
  51. package/src/core/handlers/insert_handler.js +1 -1
  52. package/src/core/handlers/update_handler.js +1 -1
  53. package/src/core/models/base-model.js +1 -1
  54. package/src/core/utils/cache-manager.js +1 -1
  55. package/src/core/utils/component-engine.js +1 -1
  56. package/src/core/utils/context-builder.js +1 -1
  57. package/src/core/utils/datetime-formatter.js +1 -1
  58. package/src/core/utils/datetime-parser.js +1 -1
  59. package/src/core/utils/db.js +1 -1
  60. package/src/core/utils/logger.js +1 -1
  61. package/src/core/utils/payload-loader.js +1 -1
  62. package/src/core/utils/security-checks.js +1 -1
  63. package/src/middleware/body-options.js +1 -1
  64. package/src/middleware/cors.js +1 -1
  65. package/src/middleware/idempotency.js +1 -1
  66. package/src/middleware/rate-limiter.js +1 -1
  67. package/src/middleware/request-logger.js +1 -1
  68. package/src/middleware/security-headers.js +1 -1
  69. package/src/models/base-model-mysql.js +1 -1
  70. package/src/models/base-model-oracle.js +1 -1
  71. package/src/models/base-model-sqlite.js +1 -1
  72. package/src/models/base-model.js +1 -1
  73. package/src/pro/caching/redis-client.js +1 -1
  74. package/src/pro/caching/redis-helper.js +1 -1
  75. package/src/pro/consumers/baseConsumer.js +1 -1
  76. package/src/pro/consumers/declarativeMapper.js +1 -1
  77. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  78. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  79. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  80. package/src/pro/consumers/handlers/index.js +1 -1
  81. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  82. package/src/pro/consumers/index.js +1 -1
  83. package/src/pro/consumers/messageTransformer.js +1 -1
  84. package/src/pro/consumers/validator.js +1 -1
  85. package/src/pro/database/base-model-mysql.js +1 -1
  86. package/src/pro/database/base-model-oracle.js +1 -1
  87. package/src/pro/database/base-model-sqlite.js +1 -1
  88. package/src/pro/database/db-mysql.js +1 -1
  89. package/src/pro/database/db-oracle.js +1 -1
  90. package/src/pro/database/db-sqlite.js +1 -1
  91. package/src/pro/excel/excel-generator.js +1 -1
  92. package/src/pro/excel/excel-parser.js +1 -1
  93. package/src/pro/excel/export-service.js +1 -1
  94. package/src/pro/excel/export_handler.js +1 -1
  95. package/src/pro/excel/import-service.js +1 -1
  96. package/src/pro/excel/import-validator.js +1 -1
  97. package/src/pro/excel/import_handler.js +1 -1
  98. package/src/pro/excel/upsert-builder.js +1 -1
  99. package/src/pro/idgen/idgen-routes.js +1 -1
  100. package/src/pro/integrations/lookup-resolver.js +1 -1
  101. package/src/pro/integrations/upload-handler-v2.js +1 -1
  102. package/src/pro/integrations/upload-handler.js +1 -1
  103. package/src/pro/integrations/webhook.js +1 -1
  104. package/src/pro/locking/lock-routes.js +1 -1
  105. package/src/pro/locking/resource-lock-manager.js +1 -1
  106. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  107. package/src/pro/messaging/kafkaService.js +1 -1
  108. package/src/pro/messaging/messagehubService.js +1 -1
  109. package/src/pro/messaging/rabbitmqService.js +1 -1
  110. package/src/pro/scheduler/job-manager.js +1 -1
  111. package/src/pro/scheduler/job-routes.js +1 -1
  112. package/src/pro/scheduler/job-validator.js +1 -1
  113. package/src/pro/storage/base-storage-provider.js +1 -1
  114. package/src/pro/storage/file-metadata-helper.js +1 -1
  115. package/src/pro/storage/index.js +1 -1
  116. package/src/pro/storage/local-storage-provider.js +1 -1
  117. package/src/pro/storage/s3-storage-provider.js +1 -1
  118. package/src/pro/storage/upload-cleanup-job.js +1 -1
  119. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  120. package/src/pro/storage/upload-pending-tracker.js +1 -1
  121. package/src/pro/websocket/broadcast-helper.js +1 -1
  122. package/src/pro/websocket/index.js +1 -1
  123. package/src/pro/websocket/livesync-server.js +1 -1
  124. package/src/pro/websocket/ws-broadcaster.js +1 -1
  125. package/src/services/export-service.js +1 -1
  126. package/src/services/import-service.js +1 -1
  127. package/src/services/kafkaConsumerService.js +1 -1
  128. package/src/services/kafkaService.js +1 -1
  129. package/src/services/messagehubService.js +1 -1
  130. package/src/services/rabbitmqService.js +1 -1
  131. package/src/utils/cache-invalidation-registry.js +1 -1
  132. package/src/utils/cache-manager.js +1 -1
  133. package/src/utils/component-engine.js +1 -1
  134. package/src/utils/config-extractor.js +1 -1
  135. package/src/utils/consumerLogger.js +1 -1
  136. package/src/utils/context-builder.js +1 -1
  137. package/src/utils/dashboard-helpers.js +1 -1
  138. package/src/utils/dateHelper.js +1 -1
  139. package/src/utils/datetime-formatter.js +1 -1
  140. package/src/utils/datetime-parser.js +1 -1
  141. package/src/utils/db-bootstrap.js +1 -1
  142. package/src/utils/db-mysql.js +1 -1
  143. package/src/utils/db-oracle.js +1 -1
  144. package/src/utils/db-sqlite.js +1 -1
  145. package/src/utils/db.js +1 -1
  146. package/src/utils/demo-generator.js +1 -1
  147. package/src/utils/excel-generator.js +1 -1
  148. package/src/utils/excel-parser.js +1 -1
  149. package/src/utils/file-watcher.js +1 -1
  150. package/src/utils/id-generator.js +1 -1
  151. package/src/utils/idempotency-manager.js +1 -1
  152. package/src/utils/import-validator.js +1 -1
  153. package/src/utils/license-client.js +1 -1
  154. package/src/utils/lock-manager.js +1 -1
  155. package/src/utils/logger.js +1 -1
  156. package/src/utils/lookup-resolver.js +1 -1
  157. package/src/utils/payload-loader.js +1 -1
  158. package/src/utils/processor-response.js +1 -1
  159. package/src/utils/rabbitmq.js +1 -1
  160. package/src/utils/redis-client.js +1 -1
  161. package/src/utils/redis-helper.js +1 -1
  162. package/src/utils/request-scope.js +1 -1
  163. package/src/utils/security-checks.js +1 -1
  164. package/src/utils/service-resolver.js +1 -1
  165. package/src/utils/shutdown-coordinator.js +1 -1
  166. package/src/utils/soft-delete-dashboard-guard.js +1 -1
  167. package/src/utils/sql-table-extractor.js +1 -1
  168. package/src/utils/trusted-keys.js +1 -1
  169. package/src/utils/upload-handler.js +1 -1
  170. package/src/utils/upsert-builder.js +1 -1
  171. 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 a0_0x5c9a7d=a0_0x236a;(function(_0xd30869,_0x3fbe52){const _0x564c38=a0_0x236a,_0x103c81=_0xd30869();while(!![]){try{const _0x7b0e0=parseInt(_0x564c38(0x174))/0x1+parseInt(_0x564c38(0x15f))/0x2*(parseInt(_0x564c38(0x16f))/0x3)+parseInt(_0x564c38(0x164))/0x4*(-parseInt(_0x564c38(0x167))/0x5)+parseInt(_0x564c38(0x16c))/0x6+parseInt(_0x564c38(0x15c))/0x7+parseInt(_0x564c38(0x161))/0x8+-parseInt(_0x564c38(0x16e))/0x9;if(_0x7b0e0===_0x3fbe52)break;else _0x103c81['push'](_0x103c81['shift']());}catch(_0x5e04bc){_0x103c81['push'](_0x103c81['shift']());}}}(a0_0x21dd,0x72bb3));const os=require('os'),path=require(a0_0x5c9a7d(0x173)),fs=require('fs'),{spawn}=require('child_process');function resolveBinaryPath(){const _0x40e5af=a0_0x5c9a7d,_0x360cb6={'obiKl':function(_0xc94ff9,_0x14e727){return _0xc94ff9===_0x14e727;}},_0x3435e4=os['platform'](),_0x186c55=path[_0x40e5af(0x168)](__dirname,'..','bin');if(_0x360cb6['obiKl'](_0x3435e4,'win32'))return path[_0x40e5af(0x160)](_0x186c55,_0x40e5af(0x15b));if(_0x360cb6[_0x40e5af(0x16a)](_0x3435e4,_0x40e5af(0x165)))return path['join'](_0x186c55,_0x40e5af(0x172));return null;}const binaryPath=resolveBinaryPath();function a0_0x21dd(){const _0xf2ee89=['mtuXnZmXtM9QAu9y','C2XPy2u','CMvZDgzVCMDLlwrLC2LNBMvYlMv4zq','mtC4ntC0ou9vwu9TAG','Aw5OzxjPDa','zxjYB3i','nty3mJy4DxL1DeHZ','AM9PBG','nty3nZeYz0HiyNz4','zxHPC3rZu3LUyW','sw5ZDgfSBcb1BgfUzYbaCMvZDgzVCMDLANmVCgXHDgzVCM0GDw50DwSGBwvUzgfWyxrRyw4GyMLUyxj5ihLHBMCGC2vZDwfPlG','ofrxqwDsCG','BgLUDxG','y2HTB2rtEw5J','mJi3ndm2nuHszvvgAW','CMvZB2X2zq','rxjYB3i6ihjLC3rMB3jNzs1KzxnPz25LCIb0AwrHAYbKAwr1A3vUzYbKAsbWBgf0zM9YBsbPBMKGka','B2jPs2W','A2LSBa','mZCXmZe5mhrVthrryq','CgLK','ntu0nenmsen4CW','m0fIwKzkCq','BwvZC2fNzq','zxHPDa','CMvZDgzVCMDLlwrLC2LNBMvYlwXPBNv4','Cgf0Aa'];a0_0x21dd=function(){return _0xf2ee89;};return a0_0x21dd();}function a0_0x236a(_0x569c58,_0x5b0331){_0x569c58=_0x569c58-0x15a;const _0x21dd50=a0_0x21dd();let _0x236a0b=_0x21dd50[_0x569c58];if(a0_0x236a['wTJqNa']===undefined){var _0xcd6aa0=function(_0x5c2f71){const _0x3e09e3='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x4ab395='',_0x2132ac='';for(let _0x2538c7=0x0,_0x39be92,_0x41a3c7,_0x405ded=0x0;_0x41a3c7=_0x5c2f71['charAt'](_0x405ded++);~_0x41a3c7&&(_0x39be92=_0x2538c7%0x4?_0x39be92*0x40+_0x41a3c7:_0x41a3c7,_0x2538c7++%0x4)?_0x4ab395+=String['fromCharCode'](0xff&_0x39be92>>(-0x2*_0x2538c7&0x6)):0x0){_0x41a3c7=_0x3e09e3['indexOf'](_0x41a3c7);}for(let _0xa2141=0x0,_0x59b3ce=_0x4ab395['length'];_0xa2141<_0x59b3ce;_0xa2141++){_0x2132ac+='%'+('00'+_0x4ab395['charCodeAt'](_0xa2141)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x2132ac);};a0_0x236a['wpZREI']=_0xcd6aa0,a0_0x236a['zVeeZR']={},a0_0x236a['wTJqNa']=!![];}const _0x1d6c11=_0x21dd50[0x0],_0x233368=_0x569c58+_0x1d6c11,_0x3b4ad9=a0_0x236a['zVeeZR'][_0x233368];return!_0x3b4ad9?(_0x236a0b=a0_0x236a['wpZREI'](_0x236a0b),a0_0x236a['zVeeZR'][_0x233368]=_0x236a0b):_0x236a0b=_0x3b4ad9,_0x236a0b;}!binaryPath&&(console[a0_0x5c9a7d(0x15e)](a0_0x5c9a7d(0x169)+os['platform']()+').'),process['exit'](0x1));!fs[a0_0x5c9a7d(0x162)](binaryPath)&&(console[a0_0x5c9a7d(0x15e)]('Error:\x20binary\x20restforge-designer\x20tidak\x20ditemukan\x20di\x20'+binaryPath+'.'),console['error'](a0_0x5c9a7d(0x163)),process[a0_0x5c9a7d(0x171)](0x1));if(os['platform']()!=='win32')try{fs[a0_0x5c9a7d(0x166)](binaryPath,0x1ed);}catch{}const child=spawn(binaryPath,process['argv'][a0_0x5c9a7d(0x15a)](0x2),{'stdio':a0_0x5c9a7d(0x15d)});child['on']('error',_0x1fb584=>{const _0xc2b3e8=a0_0x5c9a7d;console['error']('Error\x20menjalankan\x20restforge-designer:\x20'+_0x1fb584[_0xc2b3e8(0x170)]),process['exit'](0x1);}),child['on'](a0_0x5c9a7d(0x171),(_0x35f0bb,_0x216a00)=>{const _0x4404df=a0_0x5c9a7d,_0x5bfe6d={'ntiIm':function(_0x3b72fb,_0x1ce886){return _0x3b72fb??_0x1ce886;}};if(_0x216a00)process[_0x4404df(0x16b)](process[_0x4404df(0x16d)],_0x216a00);else process[_0x4404df(0x171)](_0x5bfe6d['ntiIm'](_0x35f0bb,0x0));});
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
- cfg.DB_FILE = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE);
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 ${serveCmd}`,
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
- serveCmd,
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 ${serveCmd}`,
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
- serveCmd,
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
- function pm2StartScript(name, scriptPath, cwd) {
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 "${scriptPath}" --name ${name} --cwd "${cwd}"${interpreterFlag}`,
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.serverStartFile, ctx.cwd);
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.serverStartFile, ctx.cwd);
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, prompter) {
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
- const runMode = process.platform === 'win32' ? await selectRunMode(prompter) : 'pm2';
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.frontendStartFile, appDir);
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.frontendStartFile, appDir);
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, prompter) {
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
- const runMode = process.platform === 'win32' ? await selectRunMode(prompter) : 'pm2';
1601
- await startServerNow(ctx, runMode);
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 sesuai scope.
1735
- if (ctx.scope.backend) {
1736
- runBackendPipeline(ctx);
1737
- // Launcher start REST API mandiri (sesuai OS), di folder kerja.
1738
- ctx.serverStartFile = writeServerStartScript(ctx);
1739
- }
1740
- if (ctx.scope.frontend) {
1741
- runFrontendPipeline(ctx);
1742
- // Launcher start frontend mandiri (sesuai OS), dipakai juga sebagai
1743
- // target `pm2 start` di non-Windows.
1744
- ctx.frontendStartFile = writeFrontendStartScript(ctx);
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. Scope REST API + Frontend -> SATU
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, prompter);
1903
+ await maybeRunServerAndFrontend(ctx, prompter.ask);
1756
1904
  } else if (ctx.scope.backend) {
1757
- await maybeRunServer(ctx, prompter.ask, prompter);
1905
+ await maybeRunServer(ctx, prompter.ask);
1758
1906
  }
1759
1907
  } finally {
1760
1908
  prompter.close();
@@ -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 (default db-connection.env, bisa
13
- * di-custom). Nilai input di-patch ke template lalu ditulis.
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
- cfg.DB_FILE = await askField('DB_FILE (.db file path)', DB_DEFAULTS.DB_FILE);
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};