@restforgejs/platform 5.0.0 → 5.0.3

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 (175) 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/generators/cli/init.js +4 -104
  5. package/generators/cli/payload/migrate.js +1 -1
  6. package/generators/cli/schema/list.js +82 -18
  7. package/generators/cli/schema/migrate.js +23 -3
  8. package/generators/lib/dbschema-kit/diff-engine.js +715 -715
  9. package/generators/lib/migrate/field-type-resolver.js +9 -3
  10. package/generators/lib/migrate/migrate-runner.js +244 -38
  11. package/generators/lib/migrate/naming.js +9 -0
  12. package/generators/lib/payload/payload-runner.js +20 -0
  13. package/generators/lib/templates/dashboard-catalog.js +1 -1
  14. package/generators/lib/templates/db-connection-env.js +1 -1
  15. package/generators/lib/templates/dbschema-catalog.js +1 -1
  16. package/generators/lib/templates/field-validation-catalog.js +1 -1
  17. package/generators/lib/templates/mysql-template.js +1 -1
  18. package/generators/lib/templates/oracle-template.js +1 -1
  19. package/generators/lib/templates/postgres-template.js +1 -1
  20. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  21. package/generators/lib/templates/sqlite-template.js +1 -1
  22. package/integrity-manifest.json +18 -18
  23. package/package.json +1 -1
  24. package/scripts/verify-integrity.js +1 -1
  25. package/server.js +1 -1
  26. package/src/components/handlers/adjust_handler.js +1 -1
  27. package/src/components/handlers/audit_handler.js +1 -1
  28. package/src/components/handlers/delete_handler.js +1 -1
  29. package/src/components/handlers/export_handler.js +1 -1
  30. package/src/components/handlers/import_handler.js +1 -1
  31. package/src/components/handlers/insert_handler.js +1 -1
  32. package/src/components/handlers/update_handler.js +1 -1
  33. package/src/components/handlers/upload_handler.js +1 -1
  34. package/src/components/handlers/workflow_handler.js +1 -1
  35. package/src/components/integrations/webhook.js +1 -1
  36. package/src/consumers/baseConsumer.js +1 -1
  37. package/src/consumers/declarativeMapper.js +1 -1
  38. package/src/consumers/handlers/apiHandler.js +1 -1
  39. package/src/consumers/handlers/consoleHandler.js +1 -1
  40. package/src/consumers/handlers/databaseHandler.js +1 -1
  41. package/src/consumers/handlers/index.js +1 -1
  42. package/src/consumers/handlers/kafkaHandler.js +1 -1
  43. package/src/consumers/index.js +1 -1
  44. package/src/consumers/messageTransformer.js +1 -1
  45. package/src/consumers/validator.js +1 -1
  46. package/src/core/db/dialect/base-dialect.js +1 -1
  47. package/src/core/db/dialect/index.js +1 -1
  48. package/src/core/db/dialect/mysql-dialect.js +1 -1
  49. package/src/core/db/dialect/oracle-dialect.js +1 -1
  50. package/src/core/db/dialect/postgres-dialect.js +1 -1
  51. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  52. package/src/core/db/flatten-helper.js +1 -1
  53. package/src/core/db/query-builder-error.js +1 -1
  54. package/src/core/db/query-builder.js +1 -1
  55. package/src/core/db/relation-helper.js +1 -1
  56. package/src/core/handlers/delete_handler.js +1 -1
  57. package/src/core/handlers/insert_handler.js +1 -1
  58. package/src/core/handlers/update_handler.js +1 -1
  59. package/src/core/models/base-model.js +1 -1
  60. package/src/core/utils/cache-manager.js +1 -1
  61. package/src/core/utils/component-engine.js +1 -1
  62. package/src/core/utils/context-builder.js +1 -1
  63. package/src/core/utils/datetime-formatter.js +1 -1
  64. package/src/core/utils/datetime-parser.js +1 -1
  65. package/src/core/utils/db.js +1 -1
  66. package/src/core/utils/logger.js +1 -1
  67. package/src/core/utils/payload-loader.js +1 -1
  68. package/src/core/utils/security-checks.js +1 -1
  69. package/src/middleware/body-options.js +1 -1
  70. package/src/middleware/cors.js +1 -1
  71. package/src/middleware/idempotency.js +1 -1
  72. package/src/middleware/rate-limiter.js +1 -1
  73. package/src/middleware/request-logger.js +1 -1
  74. package/src/middleware/security-headers.js +1 -1
  75. package/src/models/base-model-mysql.js +1 -1
  76. package/src/models/base-model-oracle.js +1 -1
  77. package/src/models/base-model-sqlite.js +1 -1
  78. package/src/models/base-model.js +1 -1
  79. package/src/pro/caching/redis-client.js +1 -1
  80. package/src/pro/caching/redis-helper.js +1 -1
  81. package/src/pro/consumers/baseConsumer.js +1 -1
  82. package/src/pro/consumers/declarativeMapper.js +1 -1
  83. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  84. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  85. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  86. package/src/pro/consumers/handlers/index.js +1 -1
  87. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  88. package/src/pro/consumers/index.js +1 -1
  89. package/src/pro/consumers/messageTransformer.js +1 -1
  90. package/src/pro/consumers/validator.js +1 -1
  91. package/src/pro/database/base-model-mysql.js +1 -1
  92. package/src/pro/database/base-model-oracle.js +1 -1
  93. package/src/pro/database/base-model-sqlite.js +1 -1
  94. package/src/pro/database/db-mysql.js +1 -1
  95. package/src/pro/database/db-oracle.js +1 -1
  96. package/src/pro/database/db-sqlite.js +1 -1
  97. package/src/pro/excel/excel-generator.js +1 -1
  98. package/src/pro/excel/excel-parser.js +1 -1
  99. package/src/pro/excel/export-service.js +1 -1
  100. package/src/pro/excel/export_handler.js +1 -1
  101. package/src/pro/excel/import-service.js +1 -1
  102. package/src/pro/excel/import-validator.js +1 -1
  103. package/src/pro/excel/import_handler.js +1 -1
  104. package/src/pro/excel/upsert-builder.js +1 -1
  105. package/src/pro/idgen/idgen-routes.js +1 -1
  106. package/src/pro/integrations/lookup-resolver.js +1 -1
  107. package/src/pro/integrations/upload-handler-v2.js +1 -1
  108. package/src/pro/integrations/upload-handler.js +1 -1
  109. package/src/pro/integrations/webhook.js +1 -1
  110. package/src/pro/locking/lock-routes.js +1 -1
  111. package/src/pro/locking/resource-lock-manager.js +1 -1
  112. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  113. package/src/pro/messaging/kafkaService.js +1 -1
  114. package/src/pro/messaging/messagehubService.js +1 -1
  115. package/src/pro/messaging/rabbitmqService.js +1 -1
  116. package/src/pro/scheduler/job-manager.js +1 -1
  117. package/src/pro/scheduler/job-routes.js +1 -1
  118. package/src/pro/scheduler/job-validator.js +1 -1
  119. package/src/pro/storage/base-storage-provider.js +1 -1
  120. package/src/pro/storage/file-metadata-helper.js +1 -1
  121. package/src/pro/storage/index.js +1 -1
  122. package/src/pro/storage/local-storage-provider.js +1 -1
  123. package/src/pro/storage/s3-storage-provider.js +1 -1
  124. package/src/pro/storage/upload-cleanup-job.js +1 -1
  125. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  126. package/src/pro/storage/upload-pending-tracker.js +1 -1
  127. package/src/pro/websocket/broadcast-helper.js +1 -1
  128. package/src/pro/websocket/index.js +1 -1
  129. package/src/pro/websocket/livesync-server.js +1 -1
  130. package/src/pro/websocket/ws-broadcaster.js +1 -1
  131. package/src/services/export-service.js +1 -1
  132. package/src/services/import-service.js +1 -1
  133. package/src/services/kafkaConsumerService.js +1 -1
  134. package/src/services/kafkaService.js +1 -1
  135. package/src/services/messagehubService.js +1 -1
  136. package/src/services/rabbitmqService.js +1 -1
  137. package/src/utils/cache-invalidation-registry.js +1 -1
  138. package/src/utils/cache-manager.js +1 -1
  139. package/src/utils/component-engine.js +1 -1
  140. package/src/utils/config-extractor.js +1 -1
  141. package/src/utils/consumerLogger.js +1 -1
  142. package/src/utils/context-builder.js +1 -1
  143. package/src/utils/dashboard-helpers.js +1 -1
  144. package/src/utils/dateHelper.js +1 -1
  145. package/src/utils/datetime-formatter.js +1 -1
  146. package/src/utils/datetime-parser.js +1 -1
  147. package/src/utils/db-bootstrap.js +1 -1
  148. package/src/utils/db-mysql.js +1 -1
  149. package/src/utils/db-oracle.js +1 -1
  150. package/src/utils/db-sqlite.js +1 -1
  151. package/src/utils/db.js +1 -1
  152. package/src/utils/demo-generator.js +1 -1
  153. package/src/utils/excel-generator.js +1 -1
  154. package/src/utils/excel-parser.js +1 -1
  155. package/src/utils/file-watcher.js +1 -1
  156. package/src/utils/id-generator.js +1 -1
  157. package/src/utils/idempotency-manager.js +1 -1
  158. package/src/utils/import-validator.js +1 -1
  159. package/src/utils/license-client.js +1 -1
  160. package/src/utils/lock-manager.js +1 -1
  161. package/src/utils/logger.js +1 -1
  162. package/src/utils/lookup-resolver.js +1 -1
  163. package/src/utils/payload-loader.js +1 -1
  164. package/src/utils/processor-response.js +1 -1
  165. package/src/utils/rabbitmq.js +1 -1
  166. package/src/utils/redis-client.js +1 -1
  167. package/src/utils/redis-helper.js +1 -1
  168. package/src/utils/request-scope.js +1 -1
  169. package/src/utils/security-checks.js +1 -1
  170. package/src/utils/service-resolver.js +1 -1
  171. package/src/utils/shutdown-coordinator.js +1 -1
  172. package/src/utils/trusted-keys.js +1 -1
  173. package/src/utils/upload-handler.js +1 -1
  174. package/src/utils/upsert-builder.js +1 -1
  175. package/src/utils/workflow-hook-executor.js +1 -1
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  const { generateLabel } = require('./label-generator');
21
- const { snakeToTitle } = require('./naming');
21
+ const { snakeToTitle, snakeToKebab } = require('./naming');
22
22
 
23
23
  const AUDIT_FIELDS = ['created_at', 'created_by', 'updated_at', 'updated_by'];
24
24
  const TEXTAREA_FIELDS = ['address', 'description', 'notes'];
@@ -175,7 +175,10 @@ class FieldTypeResolver {
175
175
  // Rule 3: FK with JOIN → select (API)
176
176
  if (isFk) {
177
177
  const join = this.joinMap.get(fieldName);
178
- const displayCol = guessDisplayCol(join);
178
+ // Display column diambil dari kolom JOIN aktual yang dipilih di SELECT
179
+ // (mis. category_name), bukan tebakan `<table>_name`, sehingga `select`
180
+ // dan `tableField` konsisten.
181
+ const displayCol = this.joinDisplayFields.get(fieldName) || guessDisplayCol(join);
179
182
  return {
180
183
  name: fieldName,
181
184
  label,
@@ -189,7 +192,10 @@ class FieldTypeResolver {
189
192
  extra: {
190
193
  dataSource: {
191
194
  type: 'api',
192
- resource: join.tableName,
195
+ // resource = kebab-case agar cocok dengan apiPath endpoint
196
+ // (POST <apiBaseUrl>/<resource>/lookup). tableName SQL bersifat
197
+ // snake_case (visitor_categories) sehingga harus dikonversi.
198
+ resource: snakeToKebab(join.tableName),
193
199
  select: [join.remoteColumn, displayCol]
194
200
  }
195
201
  }
@@ -8,10 +8,15 @@
8
8
  * 2. Baca SERVER_ADDRESS dan SERVER_PORT dari config → konstruksi apiBaseUrl
9
9
  * dengan format `http://{host}:{port}/api/{project}`
10
10
  * 3. Load file payload backend (RDF) dari --name (relative ke cwd/payload/
11
- * atau absolute)
12
- * 4. Jalankan migrasi RDF UDF via BackendPayloadMigrator
13
- * 5. Tulis output JSON ke directory --output (file name = basename tanpa ext +
14
- * suffix `-app.json`)
11
+ * atau absolute) dan resolve referensi `file:` pada datatablesQuery/viewQuery
12
+ * 4. Discovery RDF terkait via JOIN di datatablesQuery: setiap tabel yang
13
+ * di-JOIN dimuat RDF-nya dari folder yang sama lalu dikonversi menjadi page
14
+ * tersendiri (1 RDF utama → multi page)
15
+ * 5. Jalankan migrasi RDF → UDF via BackendPayloadMigrator
16
+ * 6. Tulis output dalam format SPLIT multi-file:
17
+ * <output>/app-config.json → { appConfig }
18
+ * <output>/pages/<pageId>.json → { pages: [ <page> ] }
19
+ * <output>/<appCode>.json → aggregator (extends/homepage/include/navigation)
15
20
  */
16
21
 
17
22
  const fs = require('fs');
@@ -20,6 +25,8 @@ const path = require('path');
20
25
  const { resolveConfig, printDefaultConfigWarning } = require('../utils/config-resolver');
21
26
  const { readEnvFile } = require('../utils/env-manager');
22
27
  const { migrate } = require('./backend-payload-migrator');
28
+ const { parseDatatablesQuery } = require('./sql-parser');
29
+ const { kebabToTitle, snakeToKebab } = require('./naming');
23
30
 
24
31
  const DEFAULT_APP_NAME = 'My Application';
25
32
  const DEFAULT_APP_CODE_FALLBACK = 'my-app';
@@ -28,6 +35,8 @@ const DEFAULT_HOST = '127.0.0.1';
28
35
  const DEFAULT_BACKEND_PORT = 3000;
29
36
  const DEFAULT_FRONTEND_PORT = 8000;
30
37
 
38
+ const QUERY_REF_FIELDS = ['datatablesQuery', 'viewQuery'];
39
+
31
40
  function resolveInputPath(nameArg, cwd) {
32
41
  if (path.isAbsolute(nameArg)) return nameArg;
33
42
  const direct = path.resolve(cwd, nameArg);
@@ -38,21 +47,15 @@ function resolveInputPath(nameArg, cwd) {
38
47
  return direct;
39
48
  }
40
49
 
41
- function buildOutputPath(outputArg, inputBaseName, cwd) {
42
- const targetFileName = inputBaseName;
43
-
50
+ function buildOutputDir(outputArg, cwd) {
44
51
  if (!outputArg) {
45
- const defaultDir = path.resolve(cwd, 'frontend', 'payload');
46
- return path.join(defaultDir, targetFileName);
52
+ return path.resolve(cwd, 'frontend', 'payload');
47
53
  }
48
-
49
54
  const abs = path.isAbsolute(outputArg) ? outputArg : path.resolve(cwd, outputArg);
50
-
51
- // Treat as file path bila berakhiran .json (case insensitive)
52
- if (/\.json$/i.test(abs)) return abs;
53
-
54
- // Else treat as directory
55
- return path.join(abs, targetFileName);
55
+ // Toleransi bila --output diakhiri .json: pakai direktori induknya sebagai
56
+ // root output split (output bukan file tunggal lagi).
57
+ if (/\.json$/i.test(abs)) return path.dirname(abs);
58
+ return abs;
56
59
  }
57
60
 
58
61
  function readServerConfig(envFilePath) {
@@ -73,6 +76,116 @@ function buildApiBaseUrl(host, port, project) {
73
76
  return `http://${host}:${port}/api${projectSegment}`;
74
77
  }
75
78
 
79
+ /**
80
+ * Gabungkan aggregator `<project>.json` yang sudah ada dengan hasil run saat ini.
81
+ * Karena nama file aggregator = nama project, run berikutnya untuk project yang
82
+ * sama akan menambahkan (akumulasi) page baru, bukan menimpa.
83
+ *
84
+ * Aturan merge:
85
+ * - `pages[]` : union berdasarkan path `include` (entry existing didahulukan,
86
+ * page baru di-append). Re-migrate page yang sama = idempoten.
87
+ * - `navigation.items`: union berdasarkan `pageRef` (type=page). Item existing
88
+ * dipertahankan sehingga kustomisasi manual (mis. icon/label)
89
+ * pada page lama tidak hilang.
90
+ * - `homepage` : dipertahankan dari file existing (run pertama yang menetapkan).
91
+ * - `extends` : dipertahankan.
92
+ */
93
+ function mergeAggregator(existing, fresh) {
94
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
95
+ return fresh;
96
+ }
97
+
98
+ const seenInclude = new Set();
99
+ const mergedPages = [];
100
+ const pushPages = (list) => {
101
+ if (!Array.isArray(list)) return;
102
+ for (const entry of list) {
103
+ const key = (entry && typeof entry.include === 'string')
104
+ ? entry.include
105
+ : JSON.stringify(entry);
106
+ if (seenInclude.has(key)) continue;
107
+ seenInclude.add(key);
108
+ mergedPages.push(entry);
109
+ }
110
+ };
111
+ pushPages(existing.pages);
112
+ pushPages(fresh.pages);
113
+
114
+ const seenNav = new Set();
115
+ const mergedNav = [];
116
+ const pushNav = (items) => {
117
+ if (!Array.isArray(items)) return;
118
+ for (const item of items) {
119
+ const key = (item && item.type === 'page' && item.pageRef)
120
+ ? `page:${item.pageRef}`
121
+ : JSON.stringify(item);
122
+ if (seenNav.has(key)) continue;
123
+ seenNav.add(key);
124
+ mergedNav.push(item);
125
+ }
126
+ };
127
+ const existingNav = (existing.navigation && Array.isArray(existing.navigation.items))
128
+ ? existing.navigation.items : [];
129
+ const freshNav = (fresh.navigation && Array.isArray(fresh.navigation.items))
130
+ ? fresh.navigation.items : [];
131
+ pushNav(existingNav);
132
+ pushNav(freshNav);
133
+
134
+ return {
135
+ extends: existing.extends || fresh.extends,
136
+ homepage: (typeof existing.homepage === 'string' && existing.homepage)
137
+ ? existing.homepage
138
+ : fresh.homepage,
139
+ pages: mergedPages,
140
+ navigation: { items: mergedNav }
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Resolve referensi `file:` pada query fields (datatablesQuery, viewQuery)
146
+ * menjadi isi SQL inline. Dibaca langsung dari disk relatif terhadap lokasi
147
+ * file RDF, independen dari mode pkg (file SQL ada di project user, bukan
148
+ * di-bundle ke binary). Bila file tidak ditemukan, query di-kosongkan dan
149
+ * sebuah warning dikumpulkan agar parsing JOIN tidak crash.
150
+ */
151
+ function resolveQueryRefs(payload, payloadPath, warnings) {
152
+ const dir = path.dirname(payloadPath);
153
+ const out = Object.assign({}, payload);
154
+ for (const key of QUERY_REF_FIELDS) {
155
+ const val = out[key];
156
+ if (typeof val === 'string' && val.startsWith('file:')) {
157
+ const rel = val.slice(5);
158
+ const full = path.isAbsolute(rel) ? rel : path.resolve(dir, rel);
159
+ if (fs.existsSync(full)) {
160
+ out[key] = fs.readFileSync(full, 'utf8');
161
+ } else {
162
+ out[key] = '';
163
+ warnings.push(`${key} reference not found: ${full} (referenced by ${path.basename(payloadPath)})`);
164
+ }
165
+ }
166
+ }
167
+ return out;
168
+ }
169
+
170
+ function loadRdf(filePath) {
171
+ const content = fs.readFileSync(filePath, 'utf8');
172
+ return JSON.parse(content);
173
+ }
174
+
175
+ /**
176
+ * Petakan nama tabel JOIN (snake_case, mis. visitor_categories) ke file RDF
177
+ * sibling di folder payload. Coba bentuk kebab dulu (visitor-categories.json),
178
+ * lalu bentuk snake (visitor_categories.json).
179
+ */
180
+ function findRelatedRdfPath(tableName, payloadDir) {
181
+ const candidates = [`${snakeToKebab(tableName)}.json`, `${tableName}.json`];
182
+ for (const c of candidates) {
183
+ const p = path.join(payloadDir, c);
184
+ if (fs.existsSync(p)) return { path: p, candidates };
185
+ }
186
+ return { path: null, candidates };
187
+ }
188
+
76
189
  async function run(args) {
77
190
  const cwd = process.cwd();
78
191
 
@@ -110,7 +223,7 @@ async function run(args) {
110
223
  ? args.port
111
224
  : DEFAULT_FRONTEND_PORT;
112
225
 
113
- const appName = args.appName || DEFAULT_APP_NAME;
226
+ const appName = args.appName || kebabToTitle(project) || DEFAULT_APP_NAME;
114
227
  const appCode = args.appCode || project || DEFAULT_APP_CODE_FALLBACK;
115
228
  const plugin = args.plugin || DEFAULT_PLUGIN;
116
229
 
@@ -120,26 +233,49 @@ async function run(args) {
120
233
  err.exitCode = 3;
121
234
  throw err;
122
235
  }
123
- let backendPayload;
236
+
237
+ const warnings = [];
238
+
239
+ let mainPayloadRaw;
124
240
  try {
125
- const content = fs.readFileSync(inputPath, 'utf8');
126
- backendPayload = JSON.parse(content);
241
+ mainPayloadRaw = loadRdf(inputPath);
127
242
  } catch (e) {
128
243
  const err = new Error(`Failed to parse JSON from ${inputPath}: ${e.message}`);
129
244
  err.exitCode = 3;
130
245
  throw err;
131
246
  }
132
247
 
133
- const inputBaseName = path.basename(inputPath);
134
- const outputPath = buildOutputPath(args.output, inputBaseName, cwd);
248
+ // Resolve file: refs pada RDF utama agar JOIN dapat di-parse
249
+ const mainPayload = resolveQueryRefs(mainPayloadRaw, inputPath, warnings);
250
+ const mainTableClean = String(mainPayload.tableName || '').split('.').pop() || '';
135
251
 
136
- if (fs.existsSync(outputPath) && !args.overwrite) {
137
- const err = new Error(`Output file already exists: ${outputPath}. Use --overwrite to replace.`);
138
- err.exitCode = 1;
139
- throw err;
252
+ // Discovery tabel terkait via JOIN (1 level dari RDF utama)
253
+ const payloadDir = path.dirname(inputPath);
254
+ const parsedMain = parseDatatablesQuery(mainPayload.datatablesQuery || '');
255
+ const relatedPayloads = [];
256
+ const seenTables = new Set([mainTableClean]);
257
+ for (const join of parsedMain.joins) {
258
+ const t = String(join.tableName || '').split('.').pop() || '';
259
+ if (!t || seenTables.has(t)) continue;
260
+ seenTables.add(t);
261
+
262
+ const { path: relPath, candidates } = findRelatedRdfPath(t, payloadDir);
263
+ if (!relPath) {
264
+ warnings.push(`Related table '${t}' referenced by JOIN but no RDF file found in ${payloadDir} (tried: ${candidates.join(', ')}); page not generated, only the select reference is kept`);
265
+ continue;
266
+ }
267
+ try {
268
+ const relRaw = loadRdf(relPath);
269
+ relatedPayloads.push(resolveQueryRefs(relRaw, relPath, warnings));
270
+ } catch (e) {
271
+ warnings.push(`Failed to parse related RDF ${relPath}: ${e.message}`);
272
+ }
140
273
  }
141
274
 
142
- const result = migrate([backendPayload], appName, appCode, plugin, apiBaseUrl, frontendPortArg);
275
+ // Urutan page: tabel terkait (master) lebih dulu, RDF utama (detail) terakhir
276
+ const orderedPayloads = relatedPayloads.concat([mainPayload]);
277
+
278
+ const result = migrate(orderedPayloads, appName, appCode, plugin, apiBaseUrl, frontendPortArg);
143
279
  if (!result.success) {
144
280
  const msg = (result.errors && result.errors.length > 0)
145
281
  ? result.errors.join('; ')
@@ -148,30 +284,97 @@ async function run(args) {
148
284
  err.exitCode = 1;
149
285
  throw err;
150
286
  }
287
+ for (const w of (result.warnings || [])) warnings.push(w);
288
+
289
+ const pages = result.payload.pages;
290
+ const appConfig = result.payload.appConfig;
291
+ // homepage = page dari RDF utama (entry terakhir karena di-append paling akhir)
292
+ const mainPageId = pages.length > 0 ? pages[pages.length - 1].pageId : '';
293
+
294
+ const outputDir = buildOutputDir(args.output, cwd);
151
295
 
152
- const outputDir = path.dirname(outputPath);
153
- if (!fs.existsSync(outputDir)) {
154
- fs.mkdirSync(outputDir, { recursive: true });
296
+ // Susun semua file target untuk format split
297
+ const aggregatorPath = path.join(outputDir, `${appCode}.json`);
298
+
299
+ // File app-config + per-page: tunduk pada gate overwrite.
300
+ const pageTargets = [];
301
+ pageTargets.push({
302
+ path: path.join(outputDir, 'app-config.json'),
303
+ content: { appConfig }
304
+ });
305
+ for (const page of pages) {
306
+ pageTargets.push({
307
+ path: path.join(outputDir, 'pages', `${page.pageId}.json`),
308
+ content: { pages: [page] }
309
+ });
310
+ }
311
+
312
+ // Gate overwrite hanya untuk app-config + pages. Aggregator TIDAK dihitung
313
+ // sebagai konflik karena ia di-merge (akumulasi), bukan ditimpa.
314
+ if (!args.overwrite) {
315
+ const existing = pageTargets.filter(t => fs.existsSync(t.path)).map(t => t.path);
316
+ if (existing.length > 0) {
317
+ const err = new Error(`Output file(s) already exist:\n ${existing.join('\n ')}\nUse --overwrite to replace.`);
318
+ err.exitCode = 1;
319
+ throw err;
320
+ }
321
+ }
322
+
323
+ // Aggregator: gabungkan dengan file existing bila project sama (nama file sama)
324
+ const freshAggregator = {
325
+ extends: 'app-config.json',
326
+ homepage: mainPageId,
327
+ pages: pages.map(p => ({ include: `pages/${p.pageId}.json` })),
328
+ navigation: {
329
+ items: pages.map(p => ({ type: 'page', pageRef: p.pageId, label: p.pageTitle }))
330
+ }
331
+ };
332
+ let aggregatorMerged = false;
333
+ let aggregatorContent = freshAggregator;
334
+ if (fs.existsSync(aggregatorPath)) {
335
+ try {
336
+ const existingAgg = JSON.parse(fs.readFileSync(aggregatorPath, 'utf8'));
337
+ aggregatorContent = mergeAggregator(existingAgg, freshAggregator);
338
+ aggregatorMerged = true;
339
+ } catch (e) {
340
+ warnings.push(`Existing aggregator ${path.basename(aggregatorPath)} could not be parsed (${e.message}); it will be replaced`);
341
+ }
342
+ }
343
+
344
+ const targets = pageTargets.concat([{ path: aggregatorPath, content: aggregatorContent }]);
345
+ for (const t of targets) {
346
+ const dir = path.dirname(t.path);
347
+ if (!fs.existsSync(dir)) {
348
+ fs.mkdirSync(dir, { recursive: true });
349
+ }
350
+ fs.writeFileSync(t.path, JSON.stringify(t.content, null, 2), 'utf8');
155
351
  }
156
- fs.writeFileSync(outputPath, JSON.stringify(result.payload, null, 2), 'utf8');
157
352
 
158
353
  const stdout = process.stdout;
159
354
  stdout.write('============================================================\n');
160
- stdout.write('PAYLOAD MIGRATE - RDF (backend) -> UDF (frontend)\n');
355
+ stdout.write('PAYLOAD MIGRATE - RDF (backend) -> UDF (frontend, split)\n');
161
356
  stdout.write('============================================================\n\n');
162
357
  stdout.write(` Input : ${inputPath}\n`);
163
- stdout.write(` Output : ${outputPath}\n`);
358
+ stdout.write(` Output dir : ${outputDir}\n`);
164
359
  stdout.write(` Project : ${project}\n`);
165
360
  stdout.write(` apiBaseUrl : ${apiBaseUrl}\n`);
166
361
  stdout.write(` Backend port : ${backendPort}\n`);
167
362
  stdout.write(` Frontend port: ${frontendPortArg}\n`);
168
- stdout.write(` Pages : ${result.pageResults.length}\n\n`);
363
+ stdout.write(` Homepage : ${aggregatorContent.homepage}\n`);
364
+ stdout.write(` Pages (run) : ${result.pageResults.length}\n`);
365
+ stdout.write(` Pages (app) : ${aggregatorContent.pages.length}${aggregatorMerged ? ' (merged with existing)' : ''}\n\n`);
169
366
  for (const pr of result.pageResults) {
170
367
  stdout.write(` [OK] ${pr.pageId}: ${pr.fieldCount} field(s), ${pr.tableColCount} table column(s)\n`);
171
368
  }
172
- if (result.warnings && result.warnings.length > 0) {
369
+ stdout.write('\n Files written:\n');
370
+ for (const t of targets) {
371
+ const rel = path.relative(outputDir, t.path) || path.basename(t.path);
372
+ const tag = (t.path === aggregatorPath && aggregatorMerged) ? ' (merged)' : '';
373
+ stdout.write(` - ${rel}${tag}\n`);
374
+ }
375
+ if (warnings.length > 0) {
173
376
  stdout.write('\n Warnings:\n');
174
- for (const w of result.warnings) {
377
+ for (const w of warnings) {
175
378
  stdout.write(` - ${w}\n`);
176
379
  }
177
380
  }
@@ -181,7 +384,10 @@ async function run(args) {
181
384
  module.exports = {
182
385
  run,
183
386
  resolveInputPath,
184
- buildOutputPath,
387
+ buildOutputDir,
388
+ resolveQueryRefs,
389
+ findRelatedRdfPath,
185
390
  readServerConfig,
186
- buildApiBaseUrl
391
+ buildApiBaseUrl,
392
+ mergeAggregator
187
393
  };
@@ -29,6 +29,14 @@ function snakeToTitle(name) {
29
29
  return String(name || '').split('_').map(capitalize).join(' ');
30
30
  }
31
31
 
32
+ function kebabToTitle(name) {
33
+ return String(name || '')
34
+ .split(/[-_]+/)
35
+ .filter(Boolean)
36
+ .map(capitalize)
37
+ .join(' ');
38
+ }
39
+
32
40
  function toClassName(appCode) {
33
41
  return String(appCode || '').replace(/_/g, '-').split('-').map(capitalize).join('');
34
42
  }
@@ -39,5 +47,6 @@ module.exports = {
39
47
  snakeToCamel,
40
48
  snakeToPascal,
41
49
  snakeToTitle,
50
+ kebabToTitle,
42
51
  toClassName
43
52
  };
@@ -700,6 +700,18 @@ class PayloadGenerator {
700
700
  if (fieldValidation.length > 0) {
701
701
  payloadData.fieldValidation = fieldValidation;
702
702
  }
703
+
704
+ // uniqueConstraints (Issue #13): map nama constraint UNIQUE -> [fields] dari
705
+ // introspeksi DB (variabel `constraints` di atas). Dikonsumsi template
706
+ // MySQL/Oracle saat codegen (Phase 03) agar response 409 menyertakan field
707
+ // penyebab konflik. Cakupan = SEMUA UNIQUE (single + composite); nama & kolom
708
+ // disimpan apa adanya dari introspector (tanpa normalisasi case di sisi RDF).
709
+ // Selalu array eksplisit ([] bila tidak ada UNIQUE) agar bentuk konsisten.
710
+ // Hanya ditulis di path normal (di dalam guard this.db.pool, introspeksi
711
+ // tersedia); path tanpa koneksi DB tidak menyentuh field ini.
712
+ payloadData.uniqueConstraints = (constraints || [])
713
+ .filter((c) => c && c.type === 'UNIQUE')
714
+ .map((c) => ({ name: c.name, fields: c.columns }));
703
715
  }
704
716
 
705
717
  // Save payload
@@ -1225,6 +1237,14 @@ class SchemaValidator {
1225
1237
  delete updatedPayload.fieldValidation;
1226
1238
  }
1227
1239
 
1240
+ // uniqueConstraints (Issue #13): regenerasi map nama UNIQUE -> [fields] dari
1241
+ // introspeksi terbaru (variabel `constraints` di atas). Posisi & bentuk
1242
+ // konsisten dengan path generate (setelah fieldValidation). Selalu array
1243
+ // eksplisit ([] bila tidak ada UNIQUE) agar diff RDF antar versi minimal.
1244
+ updatedPayload.uniqueConstraints = (constraints || [])
1245
+ .filter((c) => c && c.type === 'UNIQUE')
1246
+ .map((c) => ({ name: c.name, fields: c.columns }));
1247
+
1228
1248
  // Audit columns resolution (Phase 03):
1229
1249
  // Pastikan field `auditColumns` di payload aligned dengan struktur tabel
1230
1250
  // database. Logic ini mencegah inkonsistensi antara `payload sync` (yang
@@ -1 +1 @@
1
- const a0_0x2f22d8=a0_0x59f4;(function(_0x4d57a8,_0x2c85dd){const _0x327969=a0_0x59f4,_0x12ed23=_0x4d57a8();while(!![]){try{const _0x398936=-parseInt(_0x327969(0x1be))/0x1+-parseInt(_0x327969(0x1cf))/0x2+parseInt(_0x327969(0x1b6))/0x3*(-parseInt(_0x327969(0x19f))/0x4)+-parseInt(_0x327969(0x18f))/0x5*(-parseInt(_0x327969(0x1b7))/0x6)+parseInt(_0x327969(0x1c8))/0x7*(-parseInt(_0x327969(0x1d8))/0x8)+-parseInt(_0x327969(0x1b3))/0x9*(parseInt(_0x327969(0x1cb))/0xa)+parseInt(_0x327969(0x18c))/0xb*(parseInt(_0x327969(0x18d))/0xc);if(_0x398936===_0x2c85dd)break;else _0x12ed23['push'](_0x12ed23['shift']());}catch(_0xde8b61){_0x12ed23['push'](_0x12ed23['shift']());}}}(a0_0x50b8,0xa259f));function a0_0x50b8(){const _0x17f5a3=['phDPzgDLDf9Pzd4','zMLSztPXDwvYEs88Cgf0Ad4VDhjLBMqUC3fS','zMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa','iJy5nZaWiG','zgfZAc1PBMjVDw5K','Cg9PBNrZ','iJi4odqI','iNrYzw5KiJOGEYaIzgLYzwn0Aw9UiJOGiNvWiIWGiNbJDci6iciYlJiIih0','DMfSDwuGkg9Yign1CNjLBNqP','q29SBgfWC2uGDg8GC2nHBgfYihbYAw1PDgL2zsaODgHLihzHBhvLig9MihrOzsbZAw5NBguGy29SDw1Uks4','u0vmrunuicOGrLjptsbZDg9JA19PBMjVDw5KifDirvjfievyvfjbq1qOwuvbuIbguK9nigLUyM91BMrFzgf0zsKGpsa6EwvHCG','DMfSDwu','AxrLBxm','zgfZAc1ZywXLCW','Bgf5B3v0','vMLZDwfSignVBg9YigLZigeGzNjVBNrLBMqGCMvUzgvYAw5NignVBMnLCM4U','sgvHzgXPBMuGBwv0CMLJihDPDgGGDhjLBMqGy2HPCcbHBMqGyNjLywTKB3DUigfJCM9ZCYbJyxrLz29YAwvZlIbtDwL0ywjSzsbMB3iGD2LKz2v0CYbSAwTLicDfEhbLy3rLzcbfyxjUAw5NCYCGDgHHDcbZAg93ihrVDgfSihzHBhvLlcbWzxjJzw50ywDLignOyw5NzsWGyw5KihbLCI1JyxrLz29YEsbJB250CMLIDxrPB24U','zMLSztPXDwvYEs88Cgf0Ad4VyNjLywTKB3DUlNnXBa','zNjLztSGlNnXBcbYzwnVBw1LBMrLzcbMB3iGzwrPDg9YigHPz2HSAwDODa','D2LKz2v0CW','twv0CMLJicSGuhjVz3jLC3mGDg8Gr29HBa','vgHLihbYzwzPEcbIzwnVBwvZihbHCNqGB2yGDgHLifvstcbZzwDTzw50lIbuAguGCMvZzxj2zwqGC2nOzw1LigTLzxbZigrHC2HIB2fYzcbLBMrWB2LUDhmGDMLZDwfSBhKGzgLZDgLUy3qGzNjVBsbduLveigvUzhbVAw50CYbPBIb0AguGvvjmihnWywnLigfUzcbHBgXVD3mGzNv0DxjLihjVDxrPBMCGzgLMzMvYzw50Awf0Aw9UlG','mtm2mdDtqxfVzwi','mJq3odb0wgvwsNm','v2LKz2v0igLKzw50AwzPzxi7ihvZzwqGyxmGDgHLihjLC3bVBNnLigTLEsbPBIb0AguGzgfZAgjVyxjKigvUDMvSB3bLlG','mtiWv05ZD21g','uMvZzxj2zwqGzM9YiensvuqGCgf5Bg9HzhmUieeGzgfZAgjVyxjKihbHEwXVywqGBxvZDcbKzwnSyxjLicD3AwrNzxrZjYbPBNn0zwfKlG','zNjVBNrLBMqTy29Uy2vYBG','yxzNx2rHAwX5x3nHBgvZ','DhjLBMq','EYaIzgLYzwn0Aw9UiJOGiNvWiIWGiNbJDci6iciYlJiIih0','DgfIBgvoyw1L','zMLSztPXDwvYEs88Cgf0Ad4Vy3vYCMvUDc5ZCwW','rNjVBNrLBMqGzgv0zxjTAw5LCYbKB251Dc9WAwuGDMfYAwfUDcWGy29SB3iGCgvYignHDgvNB3j5lcbHBMqGBgfIzwWGB3jKzxiUieLMihbLCI1JyxrLz29YEsbWzxjJzw50ywDLigLZig5LzwrLzcbMB3iGDgHLigrVBNv0igfYyYWGzNjVBNrLBMqGy29TChv0zxmGAxqGzNjVBsbPDgvTC1TPxs52ywX1zsaVihn1BsHPDgvTC1SQxs52ywX1zsKUie5Vig5LzwqGDg8GC2vUzcaNCgn0jYbMCM9TigjHy2TLBMqGDw5SzxnZihrOzsbMAwD1CMuGAxmGysbZDgfIBguGyNvZAw5LC3mGy2fSy3vSyxrPB24GAw5KzxbLBMrLBNqGB2yGDMLZDwfSihjLBMrLCMLUzY4','xMrHC2GTw2eTEKeTwJaTov8TxsSK','twv0CMLJicSGu3bHCMTSAw5L','tIbYB3DZimoxidiGy29SDw1UCW','vgfIBguGyxbWzwfYCYbPBIbtuuWGqu5eigLUig1LDgfKyxrHihbYB2PLy3qSigj1DcbTAxnZAw5NigzYB20GAw52ywXPzgf0zxmGkgnHy2HLihn0ywXLihjPC2SP','rgv0zxjTAw5LzcbIEsbZy2fSyxjdB2XSyxbZzvj1BgvZlIbgywLSzwqGD2LKz2v0CYbWCM9KDwnLihSGzxjYB3i6icCUlI4Nih0GyMXVy2SGD2L0Acb0B3aTBgv2zwWGC3vJy2vZCYbZDgLSBcb0CNvLicHVBMuGD2LKz2v0igzHAwX1CMuGzg9LCYbot1qGzMfPBcb0AguGzgfZAgjVyxjKks4','y2fJAgu','iJi0mJaI','mZi5oti2mfLcv09Jyq','Dg9WlwXLDMvSicDWyxjHBxmNig9IAMvJDa','msbYB3CGW5CGmIbJB2XZlcbVDxrWDxqGy29SDw1UCYaNzgLYzwn0Aw9UjYWGj3bJDcC','iNzHBhvLiJOGiJy5nZaWiG','ugfYyw0Gzgf0ysb0ExbLlIbwywXPzgf0zxmGCMvXDwvZDcbIB2r5igfUzcbZAgfWzxmGCNvUDgLTzsbWyxjHBwv0zxiGyMLUzgLUzY4','Bwv0CMLJx2rVBNv0x2jYzwfRzg93BG','yw55icGXihjVDYddLYaXignVBcWGtIbYB3DZimoxie0Gy29SCYWGzxrJlIK','zMLSztPXDwvYEs88Cgf0Ad4VDMfSDwuUC3fS','EYbZDwnJzxnZoIbIB29SzwfUlcbKyxrHoIb7idX3AwrNzxrjzd46idXWzxjxAwrNzxrszxnWB25Zzt4Sic4UlIb9ih0','iNnOB3bWAw5Nx2nHDgvNB3jPzxmIoIb7icjPDgvTCYi6ifT7icjUyw1LiJOGiKXHBMrZiIb9lcb7icjUyw1LiJOGiKHVDxnLCYiGFv0GFq','yxjYyxKGB2yGB2jQzwn0CW','BgvUz3rO','yxjYyxK8C3rYAw5NpIWGB3b0Aw9UywWG4Ocuihn1yNnLDcbVzIb3AwrNzxqGsurZihrVigv4zwn1DguUie9TAxqGDg8GzxHLy3v0zsbHBgWGzgvJBgfYzwqGD2LKz2v0CY4','zgf0zq','D2LKz2v0lNf1zxj5icHZAw5NDwXHCIK','qwX3yxLZihDYyxaGyxmGEYbPDgvTCZOGwY4UlL0GFsbYzwDHCMrSzxnZig9MifnrtcbYzxn1BhqGC2HHCguU','tgf5B3v0igLZigeGzNjVBNrLBMqGCMvUzgvYAw5NignVBMnLCM4U','u2LUz2XLifnrtcbXDwvYEsbMB3iGDgHLihDPzgDLDc4','yM9VBgvHBG','oNbHCMfTtMfTzq','mJi4odDtuevwreS','tvvtvcbZDgfYDcb3AxrOicDKyxnOlsCGChjLzML4','CgfYyw1Z','m2TqsLPvAq','mJG2mta0CKfez3PV','kd88itOPoIHBys16qs1Ax11Bys16qs1Amc05x10Qkq','C3rYAw5N','vxbKyxrPBMCGyw4Gu1fmigzPBguGCMvXDwLYzxmGCMvNzw5LCMf0Aw5NihrOzsbKyxnOyM9HCMqGBw9KDwXLicGNy29KzwDLBL9JCMvHDgvFzgfZAgjVyxjKjYKGzM9YignOyw5NzxmGDg8GDgfRzsbLzMzLy3qU','yw55icHTDxn0igjLignVBxbHDgLIBguGD2L0AcbKzwnSyxjLzcaNDhLWzsCP','BNvTyMvY','qwXSihDPzgDLDcbtuuWG4OcuigjVDgGGj3f1zxj5jYaOC2LUz3vSyxiPigfUzcbLDMvYEsaNCxvLCMLLCY48A2v5pICU','mteZmJC4nK1tuMDmsq','DhrS','B2jQzwn0iokaLcb2ywX1zxmGzM9YigrLy2XHCMvKihbHCMfTCYaODMfSAwrHDgvKigfNywLUC3qGCgfYyw1ZignVBNrYywn0oYbTAxnZAw5NihjLCxvPCMvKiokgKIa0mdaSihr5CguGBwLZBwf0y2GG4OAsidqWmcK','iJe4mZyI','Cgn0','msbYB3CGW5CGmIbJB2X1Bw5Z','D2LKz2v0vhLWzq','vgLTzs10BY1SAxzLigLUihnLy29UzhmUidaGzwzMzwn0AxzLBhKGzgLZywjSzxmGy2fJAguGzM9YihrOAxmGzw50CNKU','C2nHBgfYihbYAw1PDgL2zq','u3bHCMTSAw5LigXPyNjHCMLLCYaOqxbLEenOyxj0CYWGq2HHCNrPC3qSigv0yY4Pihr5CgLJywXSEsbUzwvKigeGCgXHAw4GBNvTyMvYigfYCMf5lIbgCM9UDgvUzcbTyxbZihbVAw50CY5TyxaOCca9pIbWlNzHBhvLks4GvgHLicDWzxjPB2qNigzPzwXKihn0yxLZigzVCIb0B29SDgLWigfUzcbNyxaTCMvZAwXPzw5JzsbHz2fPBNn0ig1PC3nPBMCGzgf5CY4GvxnLigDLBMvYyxrLx3nLCMLLCYbPBIbtuuWGDg8Gzw5ZDxjLignVBNnPC3rLBNqGCM93ignVDw50igv2zw4GzM9YigrHExmGD2L0AcbUBYb0CMfUC2fJDgLVBNmU','mtC1n2jPy0Dvrq','zgfZAgjVyxjKlwnHDgfSB2C','D2LKz2v0lNf1zxjPzxmUpgTLEt4GD2L0AcbtuuWGCMv0DxjUAw5Nie4GCM93CW','mZuWAevdzuLf','zw5HyMXLza','rgfZAgjVyxjKigvUzhbVAw50ig1HEsbVChqTAw4GDg8GuMvKAxmTyMfZzwqGy2fJAguUifbHDhrLCM4GzM9SBg93CYbWCM9JzxnZB3iGy2fJAguGkhnLzsbMzwf0lwnHy2HLlM1Kks4Gq2fJAguGC2nVCguGAxmGDgHLigz1BgWGCMvZCg9UC2uGzw52zwXVCgu7ig9UzsbJywnOzsbLBNrYEsbWzxiGkhbHCMfTCYaRihDPzgDLDhnBxsbZDwjZzxqPignVBwjPBMf0Aw9UlG','CMvXDwLYzwq','odG5mti0qvzhyNz5','B2jQzwn0','Ahr0Chm6lY9Yzxn0zM9Yz2uUzgv2l2rVy3mVC2vYDMvYl3f1zxj5lwrHDgeVzgfZAgjVyxjK','zgfZAc1HDxrOB3iTC3rHDhm','vuKGBgfIzwWGAxmGysbMCM9UDgvUzcbYzw5KzxjPBMCGy29Uy2vYBI4','rNjVBNrLBMqGy29TChv0zxmGDg9Fz29HBca9ihrHCMDLDcaTihzHBhvLigfUzcbWy3qGpsbYB3vUzcH2ywX1zsaVihrHCMDLDcaQideWmcKGzM9YihrOzsbWCM9NCMvZCYbIyxiUifzPC3vHBcb3Awr0AcbPCYbWCMvZzw50yxrPB25HBcbHBMqGBxvZDcbot1qGBgL2zsbPBIb0AguGyMfJA2vUzcbWyxLSB2fKlIbjzIbWCM9NCMvZCYbPBNzVBhzLCYbJB21WBgv4igj1C2LUzxnZihj1BgvZicHLlMCUigv4y2X1zguGD2vLA2vUzhmSihbYB3jHDgvKihDVCMTKyxLZksWGDxnLigeGC2LUz2XLig11BhrPlwnVBhvTBIbXDwvYEsbZBYaNCgn0jYbPCYbHihn0ywjSzsbIDxnPBMvZCYbMywn0ihjHDgHLCIb0AgfUihzPC3vHBcb3Awr0Ac4','ue9tvcaVyxbPl21PBMKTAw52zw50B3j5l2rHC2GTAw5IB3vUzc9KyxnOyM9HCMq','C3vIDgL0Bgu','vgfIBguGzgvJBgfYzwqGAw4GAw52ywXPzgf0zxmSigj1DcbUB3qGzgv0zwn0zwqGAw4Gyw55ihDPzgDLDcbtuuWGkhr5Cg8GB3iGzgvHzcbLBNrYEsK','mtCYotzPsgnfDLy','yxjYyxK','Bwv0CMLJx3nWyxjRBgLUzq','msbYB3CGW5CGmsbJB2X1Bw4','z2vUzxjHDgLVBIb0Aw1LicHot1qGCNvUDgLTzsK','v2HLBIbJywnOzs5LBMfIBgvKid09psb0CNvLigfUzcbPBNzHBgLKyxrLCYbPCYbUB24Tzw1WDhK6ihzHBgLKyxrVCIbLEhrYywn0CYb0ywjSzsbJyw5KAwrHDgvZigzYB20GD2LKz2v0ifnrtcaOCMvNzxGGrLjpts9kt0LoksWGy3jVC3mTCMvMzxjLBMnLCYb3AxrOig1LDgfKyxrHl3TWCM9Qzwn0Fs5QC29UicHLBMrWB2LUDhnBkL0UDgfIBgvoyw1LihDOzxjLihr5CguGpt09icjTB2r1BguIksWGyw5KigfZC2vYDhmGzxf1ywXPDhKGB2yGzxHWzwn0zwqGDNmGzgvJBgfYzwqGC2v0CY4GtwLZBwf0y2HLCYbHCMuGCMvWB3j0zwqGCgvYignHDgvNB3j5icHTAxnZAw5NlcbLEhrYysWGDw5TyxrJAgvKks4'];a0_0x50b8=function(){return _0x17f5a3;};return a0_0x50b8();}function a0_0x59f4(_0x53f52a,_0x263d8a){_0x53f52a=_0x53f52a-0x185;const _0x50b8c9=a0_0x50b8();let _0x59f4eb=_0x50b8c9[_0x53f52a];if(a0_0x59f4['sCXFKU']===undefined){var _0x964f=function(_0x501110){const _0x5403b7='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x256805='',_0x3bdf46='';for(let _0x71432d=0x0,_0x1fc052,_0x121e93,_0x58b709=0x0;_0x121e93=_0x501110['charAt'](_0x58b709++);~_0x121e93&&(_0x1fc052=_0x71432d%0x4?_0x1fc052*0x40+_0x121e93:_0x121e93,_0x71432d++%0x4)?_0x256805+=String['fromCharCode'](0xff&_0x1fc052>>(-0x2*_0x71432d&0x6)):0x0){_0x121e93=_0x5403b7['indexOf'](_0x121e93);}for(let _0x1c3c03=0x0,_0x2c7f6a=_0x256805['length'];_0x1c3c03<_0x2c7f6a;_0x1c3c03++){_0x3bdf46+='%'+('00'+_0x256805['charCodeAt'](_0x1c3c03)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x3bdf46);};a0_0x59f4['HoGBqg']=_0x964f,a0_0x59f4['JLnrWj']={},a0_0x59f4['sCXFKU']=!![];}const _0x1ca468=_0x50b8c9[0x0],_0x415bc5=_0x53f52a+_0x1ca468,_0x3205f7=a0_0x59f4['JLnrWj'][_0x415bc5];return!_0x3205f7?(_0x59f4eb=a0_0x59f4['HoGBqg'](_0x59f4eb),a0_0x59f4['JLnrWj'][_0x415bc5]=_0x59f4eb):_0x59f4eb=_0x3205f7,_0x59f4eb;}const FORBIDDEN_FRONTEND_FIELDS=[a0_0x2f22d8(0x1c4),a0_0x2f22d8(0x1ec),'title',a0_0x2f22d8(0x1d6),'color'],ALLOWED_PARAM_TYPES=['string','number',a0_0x2f22d8(0x1b1),a0_0x2f22d8(0x1ac)],FRONTEND_CONCERN_REASONS={'widgetType':'Visual\x20variant\x20(donut,\x20bar,\x20pie,\x20area)\x20is\x20a\x20frontend\x20rendering\x20concern\x20(separation\x20of\x20concerns).','layout':a0_0x2f22d8(0x1af),'title':a0_0x2f22d8(0x1d3),'subtitle':'UI\x20label\x20is\x20a\x20frontend\x20rendering\x20concern.','color':a0_0x2f22d8(0x185)},PAYLOAD_SHAPE={'discriminator':{'field':'widgets','presentMeans':'dashboard\x20payload','absentMeans':'Not\x20a\x20dashboard\x20payload\x20(likely\x20CRUD\x20with\x20tableName,\x20or\x20invalid)','conflictsWith':a0_0x2f22d8(0x195),'conflictRationale':'A\x20payload\x20with\x20both\x20\x27widgets\x27\x20and\x20\x27tableName\x27\x20is\x20rejected\x20by\x20DashboardValidator.\x20Pick\x20one\x20shape.'},'topLevelAllowed':[{'name':a0_0x2f22d8(0x189),'type':a0_0x2f22d8(0x1d9),'required':!![],'minItems':0x1,'description':'List\x20of\x20widget\x20definitions.\x20Order\x20is\x20informational\x20only\x20(response\x20keys\x20are\x20by\x20widget\x20id,\x20not\x20array\x20index).'},{'name':a0_0x2f22d8(0x1b5),'type':a0_0x2f22d8(0x1d0),'required':![],'description':'Parameter\x20contract\x20for\x20the\x20dashboard.\x20Each\x20key\x20is\x20a\x20param\x20name;\x20values\x20describe\x20type/required/default.\x20Placeholders\x20inside\x20widget\x20SQL\x20must\x20reference\x20declared\x20param\x20names.'},{'name':a0_0x2f22d8(0x19d),'type':'object','required':![],'description':'Optional\x20cache\x20configuration.\x20See\x20cacheSpec\x20for\x20details.'}],'topLevelForbidden':[{'name':'tableName','category':'shape-conflict','reason':a0_0x2f22d8(0x190)},...FORBIDDEN_FRONTEND_FIELDS['map'](_0x256805=>({'name':_0x256805,'category':a0_0x2f22d8(0x191),'reason':FRONTEND_CONCERN_REASONS[_0x256805]}))]},WIDGET_SPEC={'requiredFields':[{'name':'id','type':a0_0x2f22d8(0x1b9),'constraint':'non-empty,\x20unique\x20across\x20widgets\x20in\x20the\x20same\x20payload','description':a0_0x2f22d8(0x18e)}],'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_0x2f22d8(0x1e0),'description':a0_0x2f22d8(0x1b0),'responseShape':'Always\x20{\x20items:\x20[...]\x20}\x20regardless\x20of\x20SQL\x20result\x20shape.'},{'name':'queries','type':'object','format':'key→file:relative/path/to/query.sql','minKeys':0x1,'description':'Multi-SQL\x20widget.\x20Each\x20key\x20becomes\x20a\x20key\x20in\x20the\x20response\x20object.','responseShape':'Per-key\x20based\x20on\x20scalarCollapseRules\x20below.'}]},'forbiddenFields':FORBIDDEN_FRONTEND_FIELDS},PARAM_SPEC={'container':a0_0x2f22d8(0x1a0),'keyConvention':'Param\x20name\x20must\x20match\x20the\x20placeholder\x20regex\x20`[a-zA-Z_][a-zA-Z0-9_]*`\x20(alphanumeric\x20+\x20underscore,\x20must\x20start\x20with\x20letter\x20or\x20underscore).','perEntryFields':[{'name':'type','required':!![],'allowedValues':ALLOWED_PARAM_TYPES,'description':a0_0x2f22d8(0x1a3)},{'name':a0_0x2f22d8(0x1ce),'required':![],'type':'boolean','default':![],'description':'When\x20true,\x20the\x20request\x20body\x20MUST\x20include\x20this\x20param\x20(otherwise\x20400).'},{'name':'default','required':![],'type':a0_0x2f22d8(0x1bb),'description':'Default\x20value\x20applied\x20when\x20the\x20request\x20omits\x20this\x20param.\x20Validator\x20does\x20NOT\x20strictly\x20type-check\x20default;\x20runtime\x20is\x20responsible\x20for\x20compatibility.'}]},SCALAR_COLLAPSE_RULES=[{'appliesTo':a0_0x2f22d8(0x1ad),'rule':a0_0x2f22d8(0x1ae),'exampleSqlShape':a0_0x2f22d8(0x1a5),'exampleResponse':a0_0x2f22d8(0x1a8)},{'appliesTo':'widget.queries.<key>\x20with\x20SQL\x20returning\x201\x20row\x20×\x201\x20column','rule':a0_0x2f22d8(0x1e7),'exampleSqlShape':'1\x20row\x20×\x201\x20col,\x20output\x20column\x20\x27value\x27','exampleResponse':a0_0x2f22d8(0x1a2)},{'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':a0_0x2f22d8(0x1a1),'exampleResponse':a0_0x2f22d8(0x1e5)},{'appliesTo':a0_0x2f22d8(0x1ca),'rule':'Return\x20as\x20array\x20of\x20objects\x20(no\x20collapse).','exampleSqlShape':'N\x20rows\x20×\x20M\x20cols','exampleResponse':'\x22items\x22:\x20[{\x20\x22label\x22:\x20\x22Shoes\x22,\x20\x22value\x22:\x20\x227660\x22\x20},\x20...]'}],COMMON_WIDGET_PATTERNS=[{'id':a0_0x2f22d8(0x1a4),'name':'Metric\x20+\x20Donut\x20Breakdown','useCase':a0_0x2f22d8(0x186),'payloadShape':{'id':a0_0x2f22d8(0x1de),'queries':{'value':a0_0x2f22d8(0x1a6),'trend':'file:query/<path>/trend.sql','items':a0_0x2f22d8(0x187)}},'sqlShapesPerKey':[{'key':a0_0x2f22d8(0x1e9),'shape':a0_0x2f22d8(0x1db),'outputColumns':['value'],'collapseRule':a0_0x2f22d8(0x1c6)},{'key':'trend','shape':'1\x20row\x20×\x202\x20columns','outputColumns':['direction','pct'],'collapseRule':'object'},{'key':a0_0x2f22d8(0x1ea),'shape':a0_0x2f22d8(0x19a),'outputColumns':['label','value'],'collapseRule':a0_0x2f22d8(0x1a9)}],'responseShape':{'value':a0_0x2f22d8(0x1e1),'trend':a0_0x2f22d8(0x194),'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':'expected_earnings','socNotes':a0_0x2f22d8(0x197)},{'id':a0_0x2f22d8(0x1da),'name':a0_0x2f22d8(0x199),'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_0x2f22d8(0x1de),'queries':{'value':'file:query/<path>/value.sql','trend':'file:query/<path>/trend.sql','points':'file:query/<path>/points.sql'}},'sqlShapesPerKey':[{'key':a0_0x2f22d8(0x1e9),'shape':a0_0x2f22d8(0x1db),'outputColumns':['value'],'collapseRule':'scalar\x20primitive'},{'key':a0_0x2f22d8(0x193),'shape':a0_0x2f22d8(0x1c3),'outputColumns':['direction','pct'],'collapseRule':a0_0x2f22d8(0x1d0)},{'key':a0_0x2f22d8(0x1e3),'shape':'N\x20rows\x20×\x202\x20columns','outputColumns':['period','value'],'collapseRule':a0_0x2f22d8(0x1a9)}],'responseShape':{'value':a0_0x2f22d8(0x19e),'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':a0_0x2f22d8(0x192),'socNotes':a0_0x2f22d8(0x1c7)},{'id':'metric_progress_to_goal','name':a0_0x2f22d8(0x18a),'useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20progress\x20bar\x20against\x20a\x20period\x20target.\x20Suitable\x20for\x20widgets\x20like\x20\x27Orders\x20This\x20Month\x27.','payloadShape':{'id':'<widget_id>','queries':{'value':a0_0x2f22d8(0x196),'trend':a0_0x2f22d8(0x1df),'target':'file:query/<path>/target.sql'}},'sqlShapesPerKey':[{'key':a0_0x2f22d8(0x1e9),'shape':a0_0x2f22d8(0x1db),'outputColumns':[a0_0x2f22d8(0x1e6)],'collapseRule':a0_0x2f22d8(0x1c6)},{'key':a0_0x2f22d8(0x193),'shape':a0_0x2f22d8(0x1c3),'outputColumns':['direction',a0_0x2f22d8(0x1c2)],'collapseRule':'object'},{'key':'target','shape':'1\x20row\x20×\x201\x20column','outputColumns':['target'],'collapseRule':a0_0x2f22d8(0x1c6)}],'responseShape':{'value':a0_0x2f22d8(0x1c1),'trend':'{\x20\x22direction\x22:\x20\x22down\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}','target':a0_0x2f22d8(0x1e4)},'referenceWidgetId':'orders_this_month','socNotes':a0_0x2f22d8(0x1d4)}],NAMING_CONVENTION={'dashboardName':{'constraint':a0_0x2f22d8(0x1b4),'minLength':0x6,'maxLength':0x32,'regex':a0_0x2f22d8(0x198),'examples':[a0_0x2f22d8(0x1eb),a0_0x2f22d8(0x1e2),a0_0x2f22d8(0x1d2)],'rationale':a0_0x2f22d8(0x18b)}},URL_PATTERN={'method':'POST','path':'/api/{project}/{name}/dashboard','exampleFull':a0_0x2f22d8(0x1d5),'requestBodyShape':{'params':a0_0x2f22d8(0x1c0),'widgets':a0_0x2f22d8(0x1ab)},'responseShape':{'envelope':a0_0x2f22d8(0x1a7),'perWidgetResponse':a0_0x2f22d8(0x19c)}},FILE_REFERENCE_CONVENTION={'format':'file:relative/path/to/query.sql','pathRelativeTo':'payload\x20JSON\x20file\x20location','fileExtensionPolicy':a0_0x2f22d8(0x188),'resolvedAt':a0_0x2f22d8(0x1dc),'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':a0_0x2f22d8(0x1ba)},PLACEHOLDER_CONVENTION={'format':a0_0x2f22d8(0x1b2),'regex':a0_0x2f22d8(0x1b8),'regexNotes':'Negative\x20lookbehind\x20prevents\x20matching\x20\x27::\x27\x20(Postgres\x20cast\x20syntax)\x20as\x20a\x20placeholder.','scanScope':a0_0x2f22d8(0x1bd),'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_0x2f22d8(0x1e8),'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':a0_0x2f22d8(0x1cd),'fields':[{'name':a0_0x2f22d8(0x1cc),'type':'boolean','required':!![],'description':'Toggle\x20cache\x20feature\x20for\x20this\x20dashboard.'},{'name':a0_0x2f22d8(0x1bf),'type':a0_0x2f22d8(0x1bc),'required':![],'constraint':'>=\x200\x20(seconds)','default':'inherits\x20CACHE_TTL\x20env','description':a0_0x2f22d8(0x1c5)},{'name':'invalidates','type':'array<string>','required':![],'default':'[]','description':'List\x20of\x20CRUD\x20table\x20names\x20that,\x20when\x20written,\x20will\x20trigger\x20invalidation\x20of\x20this\x20dashboard\x20cache.'}],'validation':{'sqlCrossReference':a0_0x2f22d8(0x1dd),'errorOn':[a0_0x2f22d8(0x19b),a0_0x2f22d8(0x1d7)],'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_0x2f22d8(0x1d1),DASHBOARD_CATALOG={'schemaVersion':'1.0','source':a0_0x2f22d8(0x1c9),'summary':{'totalAllowedTopLevelFields':PAYLOAD_SHAPE['topLevelAllowed']['length'],'totalForbiddenFrontendFields':FORBIDDEN_FRONTEND_FIELDS[a0_0x2f22d8(0x1aa)],'totalParamTypes':ALLOWED_PARAM_TYPES[a0_0x2f22d8(0x1aa)],'totalScalarCollapseRules':SCALAR_COLLAPSE_RULES[a0_0x2f22d8(0x1aa)],'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};module['exports']={'DASHBOARD_CATALOG':DASHBOARD_CATALOG};
1
+ function a0_0x55aa(_0x48a0fb,_0x357c6a){_0x48a0fb=_0x48a0fb-0x175;const _0x285fb0=a0_0x285f();let _0x55aa03=_0x285fb0[_0x48a0fb];if(a0_0x55aa['MTFens']===undefined){var _0x7cb3e2=function(_0x1cabb2){const _0x420d9d='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x45f712='',_0x4b30bf='';for(let _0xc15954=0x0,_0x2400fa,_0x4277ed,_0x18e1f9=0x0;_0x4277ed=_0x1cabb2['charAt'](_0x18e1f9++);~_0x4277ed&&(_0x2400fa=_0xc15954%0x4?_0x2400fa*0x40+_0x4277ed:_0x4277ed,_0xc15954++%0x4)?_0x45f712+=String['fromCharCode'](0xff&_0x2400fa>>(-0x2*_0xc15954&0x6)):0x0){_0x4277ed=_0x420d9d['indexOf'](_0x4277ed);}for(let _0x32dcc7=0x0,_0x31d7e9=_0x45f712['length'];_0x32dcc7<_0x31d7e9;_0x32dcc7++){_0x4b30bf+='%'+('00'+_0x45f712['charCodeAt'](_0x32dcc7)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x4b30bf);};a0_0x55aa['fggdAy']=_0x7cb3e2,a0_0x55aa['qhcvLj']={},a0_0x55aa['MTFens']=!![];}const _0x38ff3f=_0x285fb0[0x0],_0x32d968=_0x48a0fb+_0x38ff3f,_0x7b4475=a0_0x55aa['qhcvLj'][_0x32d968];return!_0x7b4475?(_0x55aa03=a0_0x55aa['fggdAy'](_0x55aa03),a0_0x55aa['qhcvLj'][_0x32d968]=_0x55aa03):_0x55aa03=_0x7b4475,_0x55aa03;}const a0_0xfa6499=a0_0x55aa;(function(_0x267dca,_0x42bce3){const _0x33d074=a0_0x55aa,_0x10587d=_0x267dca();while(!![]){try{const _0x42232a=-parseInt(_0x33d074(0x1c0))/0x1+-parseInt(_0x33d074(0x19b))/0x2*(parseInt(_0x33d074(0x1c3))/0x3)+parseInt(_0x33d074(0x1c8))/0x4*(parseInt(_0x33d074(0x184))/0x5)+-parseInt(_0x33d074(0x176))/0x6+parseInt(_0x33d074(0x192))/0x7*(parseInt(_0x33d074(0x17b))/0x8)+parseInt(_0x33d074(0x1bb))/0x9+-parseInt(_0x33d074(0x182))/0xa*(-parseInt(_0x33d074(0x1a9))/0xb);if(_0x42232a===_0x42bce3)break;else _0x10587d['push'](_0x10587d['shift']());}catch(_0xd872ce){_0x10587d['push'](_0x10587d['shift']());}}}(a0_0x285f,0xc5a5f));const FORBIDDEN_FRONTEND_FIELDS=['widgetType','layout',a0_0xfa6499(0x197),a0_0xfa6499(0x180),'color'],ALLOWED_PARAM_TYPES=[a0_0xfa6499(0x191),'number','boolean',a0_0xfa6499(0x17c)],FRONTEND_CONCERN_REASONS={'widgetType':a0_0xfa6499(0x1bd),'layout':'Layout\x20is\x20a\x20frontend\x20rendering\x20concern.','title':'UI\x20label\x20is\x20a\x20frontend\x20rendering\x20concern.','subtitle':a0_0xfa6499(0x1a0),'color':a0_0xfa6499(0x1c4)},PAYLOAD_SHAPE={'discriminator':{'field':'widgets','presentMeans':'dashboard\x20payload','absentMeans':'Not\x20a\x20dashboard\x20payload\x20(likely\x20CRUD\x20with\x20tableName,\x20or\x20invalid)','conflictsWith':a0_0xfa6499(0x1b5),'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_0xfa6499(0x17f),'required':![],'description':a0_0xfa6499(0x18d)},{'name':'cache','type':a0_0xfa6499(0x17f),'required':![],'description':'Optional\x20cache\x20configuration.\x20See\x20cacheSpec\x20for\x20details.'}],'topLevelForbidden':[{'name':'tableName','category':'shape-conflict','reason':'Reserved\x20for\x20CRUD\x20payloads.\x20A\x20dashboard\x20payload\x20must\x20declare\x20\x27widgets\x27\x20instead.'},...FORBIDDEN_FRONTEND_FIELDS[a0_0xfa6499(0x1b9)](_0x45f712=>({'name':_0x45f712,'category':'frontend-concern','reason':FRONTEND_CONCERN_REASONS[_0x45f712]}))]},WIDGET_SPEC={'requiredFields':[{'name':'id','type':a0_0xfa6499(0x191),'constraint':a0_0xfa6499(0x193),'description':'Widget\x20identifier;\x20used\x20as\x20the\x20response\x20key\x20in\x20the\x20dashboard\x20envelope.'}],'exclusiveQueryFields':{'rule':a0_0xfa6499(0x1a1),'options':[{'name':'query','type':'string','format':a0_0xfa6499(0x1ad),'description':'Single\x20SQL\x20query\x20for\x20the\x20widget.','responseShape':a0_0xfa6499(0x19a)},{'name':a0_0xfa6499(0x1bf),'type':'object','format':'key→file:relative/path/to/query.sql','minKeys':0x1,'description':'Multi-SQL\x20widget.\x20Each\x20key\x20becomes\x20a\x20key\x20in\x20the\x20response\x20object.','responseShape':'Per-key\x20based\x20on\x20scalarCollapseRules\x20below.'}]},'forbiddenFields':FORBIDDEN_FRONTEND_FIELDS},PARAM_SPEC={'container':a0_0xfa6499(0x1c1),'keyConvention':'Param\x20name\x20must\x20match\x20the\x20placeholder\x20regex\x20`[a-zA-Z_][a-zA-Z0-9_]*`\x20(alphanumeric\x20+\x20underscore,\x20must\x20start\x20with\x20letter\x20or\x20underscore).','perEntryFields':[{'name':a0_0xfa6499(0x199),'required':!![],'allowedValues':ALLOWED_PARAM_TYPES,'description':'Param\x20data\x20type.\x20Validates\x20request\x20body\x20and\x20shapes\x20runtime\x20parameter\x20binding.'},{'name':'required','required':![],'type':a0_0xfa6499(0x1b0),'default':![],'description':a0_0xfa6499(0x1c6)},{'name':'default','required':![],'type':a0_0xfa6499(0x1c5),'description':a0_0xfa6499(0x179)}]},SCALAR_COLLAPSE_RULES=[{'appliesTo':'widget.query\x20(singular)','rule':'Always\x20wrap\x20as\x20{\x20items:\x20[...]\x20}\x20regardless\x20of\x20SQL\x20result\x20shape.','exampleSqlShape':'any\x20(1\x20row\x20×\x201\x20col,\x20N\x20rows\x20×\x20M\x20cols,\x20etc.)','exampleResponse':a0_0xfa6499(0x186)},{'appliesTo':'widget.queries.<key>\x20with\x20SQL\x20returning\x201\x20row\x20×\x201\x20column','rule':'Collapse\x20to\x20scalar\x20primitive\x20(the\x20value\x20of\x20the\x20single\x20column).','exampleSqlShape':a0_0xfa6499(0x1a8),'exampleResponse':a0_0xfa6499(0x17a)},{'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':'\x22trend\x22:\x20{\x20\x22direction\x22:\x20\x22up\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}'},{'appliesTo':a0_0xfa6499(0x1bc),'rule':'Return\x20as\x20array\x20of\x20objects\x20(no\x20collapse).','exampleSqlShape':a0_0xfa6499(0x178),'exampleResponse':'\x22items\x22:\x20[{\x20\x22label\x22:\x20\x22Shoes\x22,\x20\x22value\x22:\x20\x227660\x22\x20},\x20...]'}],COMMON_WIDGET_PATTERNS=[{'id':'metric_donut_breakdown','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':a0_0xfa6499(0x1aa),'queries':{'value':a0_0xfa6499(0x1ca),'trend':'file:query/<path>/trend.sql','items':'file:query/<path>/breakdown.sql'}},'sqlShapesPerKey':[{'key':'value','shape':'1\x20row\x20×\x201\x20column','outputColumns':['value'],'collapseRule':'scalar\x20primitive'},{'key':'trend','shape':'1\x20row\x20×\x202\x20columns','outputColumns':['direction',a0_0xfa6499(0x1cb)],'collapseRule':'object'},{'key':a0_0xfa6499(0x1b1),'shape':'N\x20rows\x20×\x202\x20columns','outputColumns':['label','value'],'collapseRule':a0_0xfa6499(0x196)}],'responseShape':{'value':a0_0xfa6499(0x1a7),'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':'expected_earnings','socNotes':a0_0xfa6499(0x194)},{'id':a0_0xfa6499(0x1ae),'name':a0_0xfa6499(0x1c2),'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_0xfa6499(0x1ca),'trend':'file:query/<path>/trend.sql','points':a0_0xfa6499(0x18f)}},'sqlShapesPerKey':[{'key':'value','shape':a0_0xfa6499(0x19d),'outputColumns':['value'],'collapseRule':'scalar\x20primitive'},{'key':'trend','shape':a0_0xfa6499(0x1ba),'outputColumns':['direction','pct'],'collapseRule':a0_0xfa6499(0x17f)},{'key':a0_0xfa6499(0x19e),'shape':a0_0xfa6499(0x1a6),'outputColumns':['period','value'],'collapseRule':a0_0xfa6499(0x196)}],'responseShape':{'value':'\x222420\x22','trend':a0_0xfa6499(0x1af),'points':a0_0xfa6499(0x181)},'referenceWidgetId':'avg_daily_sales','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':a0_0xfa6499(0x175),'name':a0_0xfa6499(0x19c),'useCase':'Headline\x20metric\x20with\x20trend\x20chip\x20and\x20progress\x20bar\x20against\x20a\x20period\x20target.\x20Suitable\x20for\x20widgets\x20like\x20\x27Orders\x20This\x20Month\x27.','payloadShape':{'id':'<widget_id>','queries':{'value':'file:query/<path>/current.sql','trend':'file:query/<path>/trend.sql','target':'file:query/<path>/target.sql'}},'sqlShapesPerKey':[{'key':a0_0xfa6499(0x1c9),'shape':a0_0xfa6499(0x19d),'outputColumns':['value\x20(or\x20current)'],'collapseRule':a0_0xfa6499(0x1a3)},{'key':a0_0xfa6499(0x185),'shape':'1\x20row\x20×\x202\x20columns','outputColumns':[a0_0xfa6499(0x18a),a0_0xfa6499(0x1cb)],'collapseRule':'object'},{'key':'target','shape':'1\x20row\x20×\x201\x20column','outputColumns':['target'],'collapseRule':a0_0xfa6499(0x1a3)}],'responseShape':{'value':a0_0xfa6499(0x177),'trend':'{\x20\x22direction\x22:\x20\x22down\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}','target':a0_0xfa6499(0x1b7)},'referenceWidgetId':'orders_this_month','socNotes':a0_0xfa6499(0x189)}],NAMING_CONVENTION={'dashboardName':{'constraint':'MUST\x20start\x20with\x20\x27dash-\x27\x20prefix','minLength':0x6,'maxLength':0x32,'regex':'^dash-[a-zA-Z0-9_-]+$','examples':['dash-sales',a0_0xfa6499(0x18e),a0_0xfa6499(0x188)],'rationale':'The\x20prefix\x20becomes\x20part\x20of\x20the\x20URL\x20segment.\x20The\x20reserved\x20scheme\x20keeps\x20dashboard\x20endpoints\x20visually\x20distinct\x20from\x20CRUD\x20endpoints\x20in\x20the\x20URL\x20space\x20and\x20allows\x20future\x20routing\x20differentiation.'}},URL_PATTERN={'method':a0_0xfa6499(0x1a2),'path':'/api/{project}/{name}/dashboard','exampleFull':'POST\x20/api/mini-inventory/dash-inbound/dashboard','requestBodyShape':{'params':'object\x20—\x20values\x20for\x20declared\x20params\x20(validated\x20against\x20params\x20contract;\x20missing\x20required\x20→\x20400,\x20type\x20mismatch\x20→\x20400)','widgets':a0_0xfa6499(0x1b4)},'responseShape':{'envelope':'{\x20success:\x20boolean,\x20data:\x20{\x20<widgetId>:\x20<perWidgetResponse>,\x20...\x20}\x20}','perWidgetResponse':a0_0xfa6499(0x1b2)}},FILE_REFERENCE_CONVENTION={'format':a0_0xfa6499(0x1ad),'pathRelativeTo':a0_0xfa6499(0x195),'fileExtensionPolicy':a0_0xfa6499(0x1ac),'resolvedAt':a0_0xfa6499(0x17d),'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':a0_0xfa6499(0x18b),'regex':a0_0xfa6499(0x17e),'regexNotes':'Negative\x20lookbehind\x20prevents\x20matching\x20\x27::\x27\x20(Postgres\x20cast\x20syntax)\x20as\x20a\x20placeholder.','scanScope':'All\x20widget\x20SQL\x20—\x20both\x20\x27query\x27\x20(singular)\x20and\x20every\x20\x27queries.<key>\x27.','constraint':a0_0xfa6499(0x1a4),'exampleSql':a0_0xfa6499(0x187),'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':a0_0xfa6499(0x183),'fields':[{'name':'enabled','type':a0_0xfa6499(0x1b0),'required':!![],'description':a0_0xfa6499(0x1b8)},{'name':'ttl','type':'number','required':![],'constraint':a0_0xfa6499(0x18c),'default':'inherits\x20CACHE_TTL\x20env','description':a0_0xfa6499(0x1a5)},{'name':a0_0xfa6499(0x1c7),'type':'array<string>','required':![],'default':'[]','description':'List\x20of\x20CRUD\x20table\x20names\x20that,\x20when\x20written,\x20will\x20trigger\x20invalidation\x20of\x20this\x20dashboard\x20cache.'}],'validation':{'sqlCrossReference':a0_0xfa6499(0x19f),'errorOn':[a0_0xfa6499(0x1b6),'Table\x20declared\x20in\x20invalidates,\x20but\x20not\x20detected\x20in\x20any\x20widget\x20SQL\x20(typo\x20or\x20dead\x20entry)'],'warningOn':[a0_0xfa6499(0x1be)]}},DOCUMENTATION_URL=a0_0xfa6499(0x1ab),DASHBOARD_CATALOG={'schemaVersion':a0_0xfa6499(0x198),'source':'dashboard-catalog','summary':{'totalAllowedTopLevelFields':PAYLOAD_SHAPE['topLevelAllowed'][a0_0xfa6499(0x190)],'totalForbiddenFrontendFields':FORBIDDEN_FRONTEND_FIELDS[a0_0xfa6499(0x190)],'totalParamTypes':ALLOWED_PARAM_TYPES[a0_0xfa6499(0x190)],'totalScalarCollapseRules':SCALAR_COLLAPSE_RULES['length'],'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_0x285f(){const _0x421ac2=['Bwv0CMLJx3nWyxjRBgLUzq','EYaIzgLYzwn0Aw9UiJOGiNvWiIWGiNbJDci6iciYlJyIih0','yM9VBgvHBG','AxrLBxm','rgv0zxjTAw5LzcbIEsbZy2fSyxjdB2XSyxbZzvj1BgvZlIbgywLSzwqGD2LKz2v0CYbWCM9KDwnLihSGzxjYB3i6icCUlI4Nih0GyMXVy2SGD2L0Acb0B3aTBgv2zwWGC3vJy2vZCYbZDgLSBcb0CNvLicHVBMuGD2LKz2v0igzHAwX1CMuGzg9LCYbot1qGzMfPBcb0AguGzgfZAgjVyxjKks4','zxHWB3j0CW','yxjYyxK8C3rYAw5NpIWGB3b0Aw9UywWG4Ocuihn1yNnLDcbVzIb3AwrNzxqGsurZihrVigv4zwn1DguUie9TAxqGDg8GzxHLy3v0zsbHBgWGzgvJBgfYzwqGD2LKz2v0CY4','DgfIBgvoyw1L','vgfIBguGyxbWzwfYCYbPBIbtuuWGqu5eigLUig1LDgfKyxrHihbYB2PLy3qSigj1DcbTAxnZAw5NigzYB20GAw52ywXPzgf0zxmGkgnHy2HLihn0ywXLihjPC2SP','iJi4odqI','vg9Nz2XLignHy2HLigzLyxr1CMuGzM9YihrOAxmGzgfZAgjVyxjKlG','BwfW','msbYB3CGW5CGmIbJB2X1Bw5Z','nJu4nJeXrfPsve9N','D2LKz2v0lNf1zxjPzxmUpgTLEt4GD2L0AcbtuuWGCMv0DxjUAw5Nie4GCM93CW','vMLZDwfSihzHCMLHBNqGkgrVBNv0lcbIyxiSihbPzsWGyxjLysKGAxmGysbMCM9UDgvUzcbYzw5KzxjPBMCGy29Uy2vYBIaOC2vWyxjHDgLVBIbVzIbJB25JzxjUCYKU','vgfIBguGzgv0zwn0zwqGAw4Gu1fmlcbIDxqGBM90ihjLz2LZDgvYzwqGyxmGq1jvrcbLBMrWB2LUDcbPBIbTzxrHzgf0ysbWCM9Qzwn0icHSAwTLBhKGysb2Awv3lcbdveuGywXPyxmSig9YignYB3nZlxbYB2PLy3qGDgfIBguG4OcuignHC2nHzguGD2LSBcbUB3qGzMLYzsK','CxvLCMLLCW','mtiWota4nNL1D3zAyW','Dg9WlwXLDMvSicDWyxjHBxmNig9IAMvJDa','twv0CMLJicSGu3bHCMTSAw5L','mtmYmJy3BNzuzgPz','vMLZDwfSignVBg9YigLZigeGzNjVBNrLBMqGCMvUzgvYAw5NignVBMnLCM4U','yw55icHTDxn0igjLignVBxbHDgLIBguGD2L0AcbKzwnSyxjLzcaNDhLWzsCP','v2HLBIb0CNvLlcb0AguGCMvXDwvZDcbIB2r5ie1vu1qGAw5JBhvKzsb0AgLZihbHCMfTicHVDgHLCNDPC2uGndaWks4','Aw52ywXPzgf0zxm','ndi1mty4AMrSq21Z','DMfSDwu','zMLSztPXDwvYEs88Cgf0Ad4VDMfSDwuUC3fS','Cgn0','Bwv0CMLJx3bYB2DYzxnZx3rVx2DVywW','ndy0nJC4neHPBxHHqq','iJe4mZyI','tIbYB3DZimoxie0Gy29SCW','rgvMyxvSDcb2ywX1zsbHChbSAwvKihDOzw4GDgHLihjLCxvLC3qGB21PDhmGDgHPCYbWyxjHBs4GvMfSAwrHDg9YigrVzxmGtK9uihn0CMLJDgX5ihr5CguTy2HLy2SGzgvMyxvSDdSGCNvUDgLTzsbPCYbYzxnWB25ZAwjSzsbMB3iGy29TCgf0AwjPBgL0Es4','iNzHBhvLiJOGiJy5nZaWiG','ndC5mdrhDuLTwMu','zgf0zq','z2vUzxjHDgLVBIb0Aw1LicHot1qGCNvUDgLTzsK','kd88itOPoIHBys16qs1Ax11Bys16qs1Amc05x10Qkq','B2jQzwn0','C3vIDgL0Bgu','w3SGiNbLCMLVzci6iciYmdi2lta0lti0iIWGiNzHBhvLiJOGiJe4ntaIih0Sic4UlIbD','mtb6v2nUyNi','rgfZAgjVyxjKigvUzhbVAw50ig1HEsbVChqTAw4GDg8GuMvKAxmTyMfZzwqGy2fJAguUifbHDhrLCM4GzM9SBg93CYbWCM9JzxnZB3iGy2fJAguGkhnLzsbMzwf0lwnHy2HLlM1Kks4Gq2fJAguGC2nVCguGAxmGDgHLigz1BgWGCMvZCg9UC2uGzw52zwXVCgu7ig9UzsbJywnOzsbLBNrYEsbWzxiGkhbHCMfTCYaRihDPzgDLDhnBxsbZDwjZzxqPignVBwjPBMf0Aw9UlG','mZbjyKzjy3a','DhjLBMq','iNnOB3bWAw5Nx2nHDgvNB3jPzxmIoIb7icjPDgvTCYi6ifT7icjUyw1LiJOGiKXHBMrZiIb9lcb7icjUyw1LiJOGiKHVDxnLCYiGFv0GFq','u0vmrunuicOGrLjptsbZDg9JA19PBMjVDw5KifDirvjfievyvfjbq1qOwuvbuIbguK9nigLUyM91BMrFzgf0zsKGpsa6EwvHCG','zgfZAc1HDxrOB3iTC3rHDhm','rNjVBNrLBMqGy29TChv0zxmGDg9Fz29HBca9ihrHCMDLDcaTihzHBhvLigfUzcbWy3qGpsbYB3vUzcH2ywX1zsaVihrHCMDLDcaQideWmcKGzM9YihrOzsbWCM9NCMvZCYbIyxiUifzPC3vHBcb3Awr0AcbPCYbWCMvZzw50yxrPB25HBcbHBMqGBxvZDcbot1qGBgL2zsbPBIb0AguGyMfJA2vUzcbWyxLSB2fKlIbjzIbWCM9NCMvZCYbPBNzVBhzLCYbJB21WBgv4igj1C2LUzxnZihj1BgvZicHLlMCUigv4y2X1zguGD2vLA2vUzhmSihbYB3jHDgvKihDVCMTKyxLZksWGDxnLigeGC2LUz2XLig11BhrPlwnVBhvTBIbXDwvYEsbZBYaNCgn0jYbPCYbHihn0ywjSzsbIDxnPBMvZCYbMywn0ihjHDgHLCIb0AgfUihzPC3vHBcb3Awr0Ac4','zgLYzwn0Aw9U','oNbHCMfTtMfTzq','pJ0GmcaOC2vJB25KCYK','ugfYyw1LDgvYignVBNrYywn0igzVCIb0AguGzgfZAgjVyxjKlIbfywnOigTLEsbPCYbHihbHCMfTig5HBwu7ihzHBhvLCYbKzxnJCMLIzsb0ExbLl3jLCxvPCMvKl2rLzMf1BhqUifbSywnLAg9SzgvYCYbPBNnPzguGD2LKz2v0ifnrtcbTDxn0ihjLzMvYzw5JzsbKzwnSyxjLzcbWyxjHBsbUyw1LCY4','zgfZAc1PBMjVDw5K','zMLSztPXDwvYEs88Cgf0Ad4VCg9PBNrZlNnXBa','BgvUz3rO','C3rYAw5N','mti2wKvuAKLR','BM9UlwvTChr5lcb1BMLXDwuGywnYB3nZihDPzgDLDhmGAw4GDgHLihnHBwuGCgf5Bg9Hza','rNjVBNrLBMqGzgv0zxjTAw5LCYbKB251Dc9WAwuGDMfYAwfUDcWGy29SB3iGCgvYignHDgvNB3j5lcbHBMqGBgfIzwWGB3jKzxiUieLMihbLCI1JyxrLz29YEsbWzxjJzw50ywDLigLZig5LzwrLzcbMB3iGDgHLigrVBNv0igfYyYWGzNjVBNrLBMqGy29TChv0zxmGAxqGzNjVBsbPDgvTC1TPxs52ywX1zsaVihn1BsHPDgvTC1SQxs52ywX1zsKUie5Vig5LzwqGDg8GC2vUzcaNCgn0jYbMCM9TigjHy2TLBMqGDw5SzxnZihrOzsbMAwD1CMuGAxmGysbZDgfIBguGyNvZAw5LC3mGy2fSy3vSyxrPB24GAw5KzxbLBMrLBNqGB2yGDMLZDwfSihjLBMrLCMLUzY4','Cgf5Bg9Hzcbku09oigzPBguGBg9JyxrPB24','yxjYyxKGB2yGB2jQzwn0CW','DgL0Bgu','ms4W','DhLWzq','qwX3yxLZihSGAxrLBxm6ifSUlI5Dih0GCMvNyxjKBgvZCYbVzIbtuuWGCMvZDwX0ihnOyxbLlG','mtHrvK5WrNO','twv0CMLJicSGuhjVz3jLC3mGDg8Gr29HBa','msbYB3CGW5CGmsbJB2X1Bw4','Cg9PBNrZ','v2HLBIbJywnOzs5LBMfIBgvKid09psb0CNvLigfUzcbPBNzHBgLKyxrLCYbPCYbUB24Tzw1WDhK6ihzHBgLKyxrVCIbLEhrYywn0CYb0ywjSzsbJyw5KAwrHDgvZigzYB20GD2LKz2v0ifnrtcaOCMvNzxGGrLjpts9kt0LoksWGy3jVC3mTCMvMzxjLBMnLCYb3AxrOig1LDgfKyxrHl3TWCM9Qzwn0Fs5QC29UicHLBMrWB2LUDhnBkL0UDgfIBgvoyw1LihDOzxjLihr5CguGpt09icjTB2r1BguIksWGyw5KigfZC2vYDhmGzxf1ywXPDhKGB2yGzxHWzwn0zwqGDNmGzgvJBgfYzwqGC2v0CY4GtwLZBwf0y2HLCYbHCMuGCMvWB3j0zwqGCgvYignHDgvNB3j5icHTAxnZAw5NlcbLEhrYysWGDw5TyxrJAgvKks4','vuKGBgfIzwWGAxmGysbMCM9UDgvUzcbYzw5KzxjPBMCGy29Uy2vYBI4','qsb3AwrNzxqGtvvtvcbKzwnSyxjLigv4ywn0BhKGB25Lig9MoIaNCxvLCNKNie9sicDXDwvYAwvZjY4GqM90AcbVCIbUzwL0AgvYigLZihjLAMvJDgvKlG','ue9tva','C2nHBgfYihbYAw1PDgL2zq','rxzLCNKGCgXHy2vOB2XKzxiGDxnLzcbPBIbtuuWGtvvtvcbIzsbKzwnSyxjLzcbPBIaNCgfYyw1ZjY4GvMfSAwrHDg9YihrOCM93CYbfCNjVCIb3AxrOig1LC3nHz2uGzM9YBwf0oIaIv2LKz2v0icC8Awq+jYbXDwvYEsaNpgXHyMvSpICGDxnLCYb1BMrLy2XHCMvKihbSywnLAg9SzgvYicC6phrVA2vUpICGkgrLy2XHCMuGAw4Gj3bHCMfTCYCPiI4','vgLTzs10BY1SAxzLigLUihnLy29UzhmUidaGzwzMzwn0AxzLBhKGzgLZywjSzxmGy2fJAguGzM9YihrOAxmGzw50CNKU','tIbYB3DZimoxidiGy29SDw1UCW','iJy5nZaWiG','msbYB3CGW5CGmsbJB2WSig91Dhb1DcbJB2X1Bw4Gj3zHBhvLjW','mJyWodmYmZnotvnwEMy','phDPzgDLDf9Pzd4','Ahr0Chm6lY9Yzxn0zM9Yz2uUzgv2l2rVy3mVC2vYDMvYl3f1zxj5lwrHDgeVzgfZAgjVyxjK','zNjLztSGlNnXBcbYzwnVBw1LBMrLzcbMB3iGzwrPDg9YigHPz2HSAwDODa','zMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa'];a0_0x285f=function(){return _0x421ac2;};return a0_0x285f();}module[a0_0xfa6499(0x1b3)]={'DASHBOARD_CATALOG':DASHBOARD_CATALOG};
@@ -1 +1 @@
1
- const a0_0x2dfcee=a0_0x23d8;(function(_0x437e2b,_0xcfffba){const _0x494ba3=a0_0x23d8,_0x2477ce=_0x437e2b();while(!![]){try{const _0x10d366=-parseInt(_0x494ba3(0x179))/0x1*(parseInt(_0x494ba3(0x172))/0x2)+-parseInt(_0x494ba3(0x187))/0x3+parseInt(_0x494ba3(0x175))/0x4*(-parseInt(_0x494ba3(0x177))/0x5)+-parseInt(_0x494ba3(0x180))/0x6+-parseInt(_0x494ba3(0x174))/0x7+-parseInt(_0x494ba3(0x17b))/0x8+parseInt(_0x494ba3(0x176))/0x9*(parseInt(_0x494ba3(0x17d))/0xa);if(_0x10d366===_0xcfffba)break;else _0x2477ce['push'](_0x2477ce['shift']());}catch(_0x46a192){_0x2477ce['push'](_0x2477ce['shift']());}}}(a0_0xd129,0x8dd2a));const DB_CONNECTION_ENV_TEMPLATE='#\x20License\x0aLICENSE=XXXX-XXXX-XXXX-XXXX\x0a\x0a#\x20Server\x0aSERVER_ADDRESS=127.0.0.1\x0aSERVER_PORT=3000\x0a\x0a#\x20Live\x20Sync\x20(WebSocket)\x20Configuration\x0a#\x20NOTE:\x20LIVE_SYNC_ENABLED=true\x20requires\x20an\x20API\x20Key\x20(KEY=...)\x20to\x20authenticate\x20WebSocket\x20clients\x0aLIVE_SYNC_ENABLED=false\x0aLIVE_SYNC_PORT=3033\x0a\x0a#\x20Redis\x20Configuration\x0aREDIS_HOST=localhost\x0aREDIS_PORT=6379\x0aREDIS_PASSWORD=\x0aREDIS_DB=0\x0a\x0a#\x20Export\x20Configuration\x0aEXPORT_FILE_EXPIRY=3600000\x0aEXPORT_CHUNK_SIZE=1000\x0a\x0a#\x20Kafka\x20Configuration\x0aKAFKA_ENABLED=false\x0a#\x20Broker\x20list\x20(comma-separated\x20for\x20multiple\x20brokers:\x20broker1:9092,broker2:9092,broker3:9092)\x0aKAFKA_BROKERS=localhost:9092\x0a#\x20Client\x20ID\x20(optional,\x20default:\x20restforge-{project}-producer\x20/\x20-consumer)\x0a#\x20KAFKA_CLIENT_ID=\x0aKAFKA_CONNECTION_TIMEOUT=3000\x0aKAFKA_REQUEST_TIMEOUT=25000\x0aKAFKA_TOPIC_PATTERN={module}.{endpoint}.events\x0aKAFKA_TENANT_ID=default\x0aKAFKA_SESSION_TIMEOUT=30000\x0aKAFKA_HEARTBEAT_INTERVAL=3000\x0aKAFKA_MAX_BYTES_PER_PARTITION=1048576\x0aKAFKA_AUTO_COMMIT=false\x0aKAFKA_AUTO_COMMIT_INTERVAL=5000\x0aKAFKA_RETRY_ATTEMPTS=3\x0aKAFKA_RETRY_DELAY=1000\x0aKAFKA_RETRY_MAX_DELAY=30000\x0aKAFKA_SSL=false\x0aKAFKA_LOG_LEVEL=info\x0a#\x20SASL\x20Authentication\x20(optional,\x20uncomment\x20if\x20the\x20broker\x20requires\x20authentication)\x0a#\x20Supported\x20mechanisms:\x20plain,\x20scram-sha-256,\x20scram-sha-512\x0a#\x20KAFKA_SASL_MECHANISM=plain\x0a#\x20KAFKA_SASL_USERNAME=\x0a#\x20KAFKA_SASL_PASSWORD=\x0a\x0a#\x20Database\x20Configuration\x0a#\x20Supported:\x20postgresql,\x20mysql,\x20oracle,\x20sqlite\x0aDB_TYPE=postgresql\x0aDB_HOST=127.0.0.1\x0aDB_PORT=5432\x0aDB_USER=postgres\x0aDB_PASSWORD=your_password_here\x0aDB_NAME=your_database_name\x0a#\x20For\x20SQLite:\x20set\x20DB_TYPE=sqlite\x20and\x20DB_NAME=./data/myapp.db\x0a#\x20DB_HOST,\x20DB_PORT,\x20DB_USER,\x20DB_PASSWORD\x20are\x20ignored\x20for\x20SQLite\x0a\x0a#\x20Logging\x20Configuration\x0aLOG_LEVEL=debug\x0aLOG_TO_FILE=true\x0a\x0a#\x20SQL\x20Logging\x0aSQL_LOG_ENABLED=false\x0aSQL_LOG_LEVEL=debug\x0aSQL_LOG_PARAMS=false\x0aSQL_LOG_SLOW_THRESHOLD=1000\x0a\x0a#\x20Cache\x20Configuration\x0aCACHE_ENABLED=false\x0aCACHE_TTL=300\x0a\x0a#\x20Job\x20Scheduler\x0aJOB_ENABLED=false\x0aJOB_CONCURRENCY=5\x0aJOB_RETENTION_HOURS=72\x0aJOB_FAILED_RETENTION_HOURS=168\x0aJOB_SHUTDOWN_TIMEOUT=10000\x0aJOB_STALLED_INTERVAL=30000\x0aJOB_MAX_STALLED_COUNT=2\x0a\x0a#\x20Distributed\x20Lock\x20Configuration\x0aLOCK_DISTRIBUTED_ENABLED=false\x0aLOCK_DISTRIBUTED_TTL=10\x0aLOCK_RESOURCE_MAX_TTL=600\x0aLOCK_DISTRIBUTED_RETRY=3\x0aLOCK_DISTRIBUTED_RETRY_DELAY=100\x0aLOCK_DISTRIBUTED_STRATEGY=reject\x0a\x0a#\x20ID\x20Generator\x20Configuration\x0aIDGEN_ENABLED=false\x0aIDGEN_IDEM_TTL=600\x0aIDGEN_COUNTER_TTL_MONTHLY=2764800\x0aIDGEN_COUNTER_TTL_DAILY=172800\x0aIDGEN_DEFAULT_MAX_RETRY=10\x0aIDGEN_DEFAULT_PIN_DIGITS=6\x0aIDGEN_DEFAULT_SERIAL_PATTERN=XXXX-XXXX-XXXX-XXXX\x0aIDGEN_DEFAULT_CODE_PATTERN=9999-9999\x0aIDGEN_ALLOW_RESET=false\x0a',REQUIRED_KEYS=new Set([a0_0x2dfcee(0x185),a0_0x2dfcee(0x173),'SERVER_PORT',a0_0x2dfcee(0x186),'DB_HOST',a0_0x2dfcee(0x178),'DB_USER','DB_PASSWORD','DB_NAME']);function parseTemplateAsSchema(_0x5b3b62){const _0x4d2f72=a0_0x2dfcee,_0x5180cf={'pAITh':function(_0x13134c,_0x50e423){return _0x13134c||_0x50e423;},'kVQHS':function(_0x38ad82,_0x1e42c7){return _0x38ad82>_0x1e42c7;},'JAvMH':function(_0x2f8c47,_0x1421d3){return _0x2f8c47===_0x1421d3;},'KrPpq':function(_0x3700df,_0x292fe5){return _0x3700df+_0x292fe5;}},_0x4cec6a=_0x5180cf[_0x4d2f72(0x17e)](_0x5b3b62,DB_CONNECTION_ENV_TEMPLATE),_0x3b9b19=_0x4cec6a['split']('\x0a'),_0x58f725=[];let _0x433bf2=null,_0x4571f5=[];for(const _0x35973a of _0x3b9b19){const _0x5275fe=_0x35973a['trim']();if(_0x5275fe===''){_0x4571f5=[];continue;}if(_0x5275fe[_0x4d2f72(0x182)]('#')){const _0x9cff52=_0x5275fe['slice'](0x1)['trim'](),_0x4d685d=_0x5180cf[_0x4d2f72(0x181)](_0x9cff52[_0x4d2f72(0x18a)],0x0)&&_0x9cff52[_0x4d2f72(0x18a)]<0x3c&&!_0x9cff52[_0x4d2f72(0x17f)](':')&&!/^[A-Z_]+=/['test'](_0x9cff52)&&/^[A-Z]/['test'](_0x9cff52);_0x4d685d&&_0x5180cf['JAvMH'](_0x4571f5[_0x4d2f72(0x18a)],0x0)?_0x433bf2=_0x9cff52:_0x4571f5['push'](_0x9cff52);continue;}const _0x22ff65=_0x35973a['indexOf']('=');if(_0x5180cf['kVQHS'](_0x22ff65,0x0)){const _0x32e06c=_0x35973a['slice'](0x0,_0x22ff65)['trim'](),_0x21efc8=_0x35973a[_0x4d2f72(0x188)](_0x5180cf['KrPpq'](_0x22ff65,0x1));let _0x401377='string';if(_0x21efc8===_0x4d2f72(0x189)||_0x21efc8===_0x4d2f72(0x17a))_0x401377=_0x4d2f72(0x184);else/^-?\d+$/[_0x4d2f72(0x17c)](_0x21efc8)&&(_0x401377='integer');_0x58f725['push']({'name':_0x32e06c,'section':_0x433bf2,'type':_0x401377,'default':_0x21efc8,'description':_0x4571f5['join']('\x20')||null,'required':REQUIRED_KEYS['has'](_0x32e06c)}),_0x4571f5=[];}}return _0x58f725;}function a0_0x23d8(_0xd55c8e,_0x2051fe){_0xd55c8e=_0xd55c8e-0x172;const _0xd129c1=a0_0xd129();let _0x23d882=_0xd129c1[_0xd55c8e];if(a0_0x23d8['ODZopy']===undefined){var _0x4bf0bb=function(_0x12a302){const _0x4d5006='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0xa577d2='',_0x3e5793='';for(let _0x41b67a=0x0,_0x17299a,_0x21226c,_0x592b89=0x0;_0x21226c=_0x12a302['charAt'](_0x592b89++);~_0x21226c&&(_0x17299a=_0x41b67a%0x4?_0x17299a*0x40+_0x21226c:_0x21226c,_0x41b67a++%0x4)?_0xa577d2+=String['fromCharCode'](0xff&_0x17299a>>(-0x2*_0x41b67a&0x6)):0x0){_0x21226c=_0x4d5006['indexOf'](_0x21226c);}for(let _0x5efbc0=0x0,_0x3f09da=_0xa577d2['length'];_0x5efbc0<_0x3f09da;_0x5efbc0++){_0x3e5793+='%'+('00'+_0xa577d2['charCodeAt'](_0x5efbc0)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x3e5793);};a0_0x23d8['hKgmdz']=_0x4bf0bb,a0_0x23d8['JtFzyg']={},a0_0x23d8['ODZopy']=!![];}const _0x31ff88=_0xd129c1[0x0],_0x48cad8=_0xd55c8e+_0x31ff88,_0x179289=a0_0x23d8['JtFzyg'][_0x48cad8];return!_0x179289?(_0x23d882=a0_0x23d8['hKgmdz'](_0x23d882),a0_0x23d8['JtFzyg'][_0x48cad8]=_0x23d882):_0x23d882=_0x179289,_0x23d882;}module[a0_0x2dfcee(0x183)]={'DB_CONNECTION_ENV_TEMPLATE':DB_CONNECTION_ENV_TEMPLATE,'REQUIRED_KEYS':REQUIRED_KEYS,'parseTemplateAsSchema':parseTemplateAsSchema};function a0_0xd129(){const _0x4bb403=['ntm5mti3mZbRz3HVuu4','CefjvgG','Aw5JBhvKzxm','ntiZnZmZnePswMPNwq','A1zrsfm','C3rHCNrZv2L0Aa','zxHWB3j0CW','yM9VBgvHBG','teLdru5trq','rejFvfLqrq','mta0mdCWnMHoC3zzEa','C2XPy2u','Dhj1zq','BgvUz3rO','mte0zxHxrvb5','u0vsvKvsx0ferfjfu1m','mJu0mZKZm3nxEMjptW','neT1qvjvuW','ouPpyKTJsa','ntC5mJe1nwfvDK96sa','rejFue9sva','mtGWnJHszgrTvuG','zMfSC2u','odmXmdGWmfLStKnnra','DgvZDa'];a0_0xd129=function(){return _0x4bb403;};return a0_0xd129();}
1
+ function a0_0x4a35(){const _0x2e5c82=['mtC2mZy3mLjoAfrgCW','m09Lyvnwwa','tur6rvi','DgvZDa','nZa3nJaWCwjduLnl','odjbrLHbze4','iYbmAwnLBNnLcKXjq0vou0u9wfHywc1ywfHylvHywfGTwfHywaOkiYbtzxj2zxiku0vsvKvsx0ferfjfu1m9mti3lJaUmc4XcLnfuLzfuL9qt1juptmWmdakcImGtgL2zsbtEw5JicHxzwjtB2nRzxqPienVBMzPz3vYyxrPB24kiYbot1rfoIbmsvzfx1nztKnFru5bqKXfrd10CNvLihjLCxvPCMvZigfUiefqssblzxKGkeTfwt0UlI4PihrVigf1DgHLBNrPy2f0zsbxzwjtB2nRzxqGy2XPzw50CWPmsvzfx1nztKnFru5bqKXfrd1MywXZzqPmsvzfx1nztKnFue9svd0ZmdmZcGOJifjLzgLZienVBMzPz3vYyxrPB24kuKvesvnFse9tvd1SB2nHBgHVC3qkuKvesvnFue9svd02mZGWcLjfreLtx1bbu1nxt1jepqPsrurju19eqJ0WcGOJiev4Cg9YDcbdB25MAwD1CMf0Aw9UcKvyue9svf9gsuXfx0vyueLswt0ZnJaWmdaWcKvyue9svf9dsfvos19tsvPfpteWmdakcImGs2fMA2eGq29UzMLNDxjHDgLVBGPlquzlqv9ftKfctevepwzHBhnLcImGqNjVA2vYigXPC3qGkgnVBw1HlxnLCgfYyxrLzcbMB3iGBxvSDgLWBguGyNjVA2vYCZOGyNjVA2vYmtO5mdKYlgjYB2TLCJi6ota5mIXICM9RzxiZoJKWotiPcKTbrKTbx0jst0TfuLm9Bg9JywXOB3n0oJKWotikiYbdBgLLBNqGsuqGkg9WDgLVBMfSlcbKzwzHDwX0oIbYzxn0zM9Yz2uTE3bYB2PLy3r9lxbYB2r1y2vYic8GlwnVBNn1BwvYkqOJieTbrKTbx0nmsuvovf9jrd0ks0fgs0fFq09otKvdveLptL9usu1ft1vuptmWmdaks0fgs0fFuKvrvuvtvf9usu1ft1vupti1mdaWcKTbrKTbx1rpueLdx1bbvfrfuK49E21VzhvSzx0UE2vUzhbVAw50Fs5LDMvUDhmks0fgs0fFvevoqu5ux0LepwrLzMf1Bhqks0fgs0fFu0vtu0LptL9usu1ft1vuptmWmdaWcKTbrKTbx0HfqvjuqKvbvf9jtLrfuLzbtd0ZmdaWcKTbrKTbx01bwf9cwvrfu19qrvjFuefsveLusu9opteWndG1nZyks0fgs0fFqvvut19dt01nsvq9zMfSC2uks0fgs0fFqvvut19dt01nsvrFsu5urvjwquW9ntaWmaPlquzlqv9srvrswv9bvfrftvbuuZ0ZcKTbrKTbx1jfvfjzx0rftefzpteWmdaks0fgs0fFuKvuuLLFtufyx0rftefzptmWmdaWcKTbrKTbx1nttd1MywXZzqPlquzlqv9mt0DFtevwruW9Aw5MBWOJifnbu0WGqxv0AgvUDgLJyxrPB24Gkg9WDgLVBMfSlcb1BMnVBw1LBNqGAwyGDgHLigjYB2TLCIbYzxf1AxjLCYbHDxrOzw50AwnHDgLVBIKkiYbtDxbWB3j0zwqGBwvJAgfUAxnTCZOGCgXHAw4SihnJCMfTlxnOys0YntySihnJCMfTlxnOys01mtikiYblquzlqv9tqvnmx01fq0HbtKLttt1WBgfPBGOJieTbrKTbx1nbu0XFvvnfuK5btuu9cImGs0fgs0fFu0fttf9qqvntv09srd0kcImGrgf0ywjHC2uGq29UzMLNDxjHDgLVBGOJifn1ChbVCNrLzdOGCg9ZDgDYzxnXBcWGBxLZCwWSig9YywnSzsWGC3fSAxrLcKrcx1rzueu9Cg9ZDgDYzxnXBaPeqL9it1nupteYnY4WlJaUmqPeqL9qt1juptu0mZikrejFvvnfuJ1WB3n0z3jLCWPeqL9qqvntv09srd15B3vYx3bHC3n3B3jKx2HLCMukrejFtKfnrt15B3vYx2rHDgfIyxnLx25HBwukiYbgB3iGu1fmAxrLoIbZzxqGrejFvfLqrt1ZCwXPDguGyw5Kiercx05btuu9lI9KyxrHl215yxbWlMrIcImGrejFse9tvcWGrejFue9svcWGrejFvvnfuIWGrejFueftu1DpuKqGyxjLigLNBM9YzwqGzM9YifnrtgL0zqOkiYbmB2DNAw5NienVBMzPz3vYyxrPB24kte9hx0XfvKvmpwrLyNvNcKXpr19ut19gsuXfpxrYDwukcImGu1fmieXVz2DPBMCku1fmx0Xpr19ftKfctevepwzHBhnLcLnrtf9mt0DFtevwruW9zgvIDwCku1fmx0Xpr19qqvjbtvm9zMfSC2uku1fmx0Xpr19tte9xx1riuKvtse9mrd0XmdaWcGOJienHy2HLienVBMzPz3vYyxrPB24kq0fdsevFru5bqKXfrd1MywXZzqPdqunirv9uveW9mZaWcGOJiePVyIbty2HLzhvSzxiksK9cx0voqujmruq9zMfSC2uksK9cx0nptKnvuLjftKnzptuksK9cx1jfvevoveLptL9it1vsuZ03mGPkt0jFrKfjtevex1jfvevoveLptL9it1vsuZ0XnJGksK9cx1nivvret1Dox1rjtuvpvvq9mtaWmdaksK9cx1nuquXmrurFsu5urvjwquW9mZaWmdaksK9cx01bwf9tvefmtevex0npvu5uptikcImGrgLZDhjPyNv0zwqGtg9JAYbdB25MAwD1CMf0Aw9UcKXpq0TFreLtvfjjqLvururFru5bqKXfrd1MywXZzqPmt0nlx0rju1rssujvvevex1rutd0XmaPmt0nlx1jfu09vuKnfx01bwf9uveW9nJaWcKXpq0TFreLtvfjjqLvururFuKvuuLK9mWPmt0nlx0rju1rssujvvevex1jfvfjzx0rftefzpteWmaPmt0nlx0rju1rssujvvevex1nuuKfuruDzpxjLAMvJDaOkiYbjrcbhzw5LCMf0B3iGq29UzMLNDxjHDgLVBGPjreDftL9ftKfctevepwzHBhnLcKLer0vox0Leru1FvfrmptyWmaPjreDftL9dt1vovevsx1rutf9nt05useXzpti3nJq4mdaksurhru5Fq09vtLrfuL9uveXFrefjtfK9mtCYodaWcKLer0vox0rfrKfvtfrFtufyx1jfvfjzpteWcKLer0vox0rfrKfvtfrFueLox0rjr0LuuZ02cKLer0vox0rfrKfvtfrFu0vssufmx1bbvfrfuK49wfHywc1ywfHylvHywfGTwfHywaPjreDftL9eruzbvuXux0nprevFuefuvevstJ05otK5ltK5otKksurhru5FquXmt1DFuKvtrvq9zMfSC2uk','nZG0n01fz2j6Cq','teLdru5trq','mJa0mtaWmhv1Dg10sa','yM9VBgvHBG','zMfSC2u','Dhj1zq','nJq0mteWDuDmEvvr','mti5mhLgCvPzzW','sefXCNC','Aw5KzxHpzG','BgvUz3rO','tg15zM0','AM9PBG','werzDu0','mJi4nZm2ogTMCLfLwq','rejFue9sva','C2XPy2u','mtaYotHnz29xBxi','Aw5JBhvKzxm','rejFvvnfuG','DhjPBq','Aw50zwDLCG','ue1KvgO','AgfZ'];a0_0x4a35=function(){return _0x2e5c82;};return a0_0x4a35();}const a0_0x25dc7d=a0_0x3341;(function(_0x243a0e,_0x5c36bc){const _0x4994fc=a0_0x3341,_0x5dbcbe=_0x243a0e();while(!![]){try{const _0x889ffc=-parseInt(_0x4994fc(0x1fb))/0x1*(-parseInt(_0x4994fc(0x207))/0x2)+parseInt(_0x4994fc(0x203))/0x3*(-parseInt(_0x4994fc(0x202))/0x4)+-parseInt(_0x4994fc(0x20f))/0x5+parseInt(_0x4994fc(0x210))/0x6*(parseInt(_0x4994fc(0x209))/0x7)+parseInt(_0x4994fc(0x206))/0x8+parseInt(_0x4994fc(0x1f8))/0x9+-parseInt(_0x4994fc(0x20b))/0xa;if(_0x889ffc===_0x5c36bc)break;else _0x5dbcbe['push'](_0x5dbcbe['shift']());}catch(_0xf2a3b8){_0x5dbcbe['push'](_0x5dbcbe['shift']());}}}(a0_0x4a35,0x38a3b));function a0_0x3341(_0x230c40,_0x4f48d8){_0x230c40=_0x230c40-0x1f3;const _0x4a3566=a0_0x4a35();let _0x33418f=_0x4a3566[_0x230c40];if(a0_0x3341['eIThZy']===undefined){var _0x3e7273=function(_0x222b42){const _0x1d69f1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x52ac7d='',_0x43ac90='';for(let _0x8f1b38=0x0,_0x18f714,_0x569841,_0x3efb4b=0x0;_0x569841=_0x222b42['charAt'](_0x3efb4b++);~_0x569841&&(_0x18f714=_0x8f1b38%0x4?_0x18f714*0x40+_0x569841:_0x569841,_0x8f1b38++%0x4)?_0x52ac7d+=String['fromCharCode'](0xff&_0x18f714>>(-0x2*_0x8f1b38&0x6)):0x0){_0x569841=_0x1d69f1['indexOf'](_0x569841);}for(let _0x6f563c=0x0,_0x10df9b=_0x52ac7d['length'];_0x6f563c<_0x10df9b;_0x6f563c++){_0x43ac90+='%'+('00'+_0x52ac7d['charCodeAt'](_0x6f563c)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x43ac90);};a0_0x3341['fkQnWS']=_0x3e7273,a0_0x3341['NjWDXW']={},a0_0x3341['eIThZy']=!![];}const _0x57b8eb=_0x4a3566[0x0],_0x1da770=_0x230c40+_0x57b8eb,_0x5674b9=a0_0x3341['NjWDXW'][_0x1da770];return!_0x5674b9?(_0x33418f=a0_0x3341['fkQnWS'](_0x33418f),a0_0x3341['NjWDXW'][_0x1da770]=_0x33418f):_0x33418f=_0x5674b9,_0x33418f;}const DB_CONNECTION_ENV_TEMPLATE=a0_0x25dc7d(0x208),REQUIRED_KEYS=new Set([a0_0x25dc7d(0x20a),'SERVER_ADDRESS','SERVER_PORT','DB_TYPE','DB_HOST',a0_0x25dc7d(0x1f9),a0_0x25dc7d(0x1fd),'DB_PASSWORD','DB_NAME']);function parseTemplateAsSchema(_0x500001){const _0x1ecfe3=a0_0x25dc7d,_0x33452c={'XDYuM':function(_0xb21099,_0x1d1191){return _0xb21099<_0x1d1191;},'PMdTj':function(_0x46a585,_0x3d6939){return _0x46a585===_0x3d6939;},'vBZzD':function(_0x2ce727,_0x540c7c){return _0x2ce727>_0x540c7c;},'Lmyfm':'string','BeTbr':_0x1ecfe3(0x20e),'HAqrw':_0x1ecfe3(0x20d),'MDzER':_0x1ecfe3(0x20c)},_0x565642=_0x500001||DB_CONNECTION_ENV_TEMPLATE,_0xcaa096=_0x565642['split']('\x0a'),_0x2c1bc4=[];let _0x3e3a9e=null,_0x18fda6=[];for(const _0x1f965c of _0xcaa096){const _0xc7851=_0x1f965c[_0x1ecfe3(0x1fe)]();if(_0xc7851===''){_0x18fda6=[];continue;}if(_0xc7851['startsWith']('#')){const _0x5ee1ab=_0xc7851[_0x1ecfe3(0x1fa)](0x1)['trim'](),_0x4d2ca9=_0x5ee1ab[_0x1ecfe3(0x1f4)]>0x0&&_0x33452c[_0x1ecfe3(0x1f7)](_0x5ee1ab[_0x1ecfe3(0x1f4)],0x3c)&&!_0x5ee1ab[_0x1ecfe3(0x1fc)](':')&&!/^[A-Z_]+=/[_0x1ecfe3(0x205)](_0x5ee1ab)&&/^[A-Z]/['test'](_0x5ee1ab);_0x4d2ca9&&_0x33452c['PMdTj'](_0x18fda6['length'],0x0)?_0x3e3a9e=_0x5ee1ab:_0x18fda6['push'](_0x5ee1ab);continue;}const _0x495a89=_0x1f965c[_0x1ecfe3(0x1f3)]('=');if(_0x33452c['vBZzD'](_0x495a89,0x0)){const _0x25b302=_0x1f965c['slice'](0x0,_0x495a89)[_0x1ecfe3(0x1fe)](),_0x4905e1=_0x1f965c[_0x1ecfe3(0x1fa)](_0x495a89+0x1);let _0x54c8f7=_0x33452c[_0x1ecfe3(0x1f5)];if(_0x4905e1===_0x33452c['BeTbr']||_0x33452c[_0x1ecfe3(0x200)](_0x4905e1,_0x33452c[_0x1ecfe3(0x211)]))_0x54c8f7=_0x33452c[_0x1ecfe3(0x204)];else/^-?\d+$/['test'](_0x4905e1)&&(_0x54c8f7=_0x1ecfe3(0x1ff));_0x2c1bc4['push']({'name':_0x25b302,'section':_0x3e3a9e,'type':_0x54c8f7,'default':_0x4905e1,'description':_0x18fda6[_0x1ecfe3(0x1f6)]('\x20')||null,'required':REQUIRED_KEYS[_0x1ecfe3(0x201)](_0x25b302)}),_0x18fda6=[];}}return _0x2c1bc4;}module['exports']={'DB_CONNECTION_ENV_TEMPLATE':DB_CONNECTION_ENV_TEMPLATE,'REQUIRED_KEYS':REQUIRED_KEYS,'parseTemplateAsSchema':parseTemplateAsSchema};