@restforgejs/platform 4.3.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/sdf-tools.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/payload/migrate.js +96 -0
- package/generators/lib/dbschema-kit/apply-engine.js +211 -46
- package/generators/lib/dbschema-kit/diff-engine.js +14 -2
- package/generators/lib/dbschema-kit/emitters/alter-table.js +96 -2
- package/generators/lib/dbschema-kit/introspect-mapper.js +9 -0
- package/generators/lib/migrate/backend-payload-migrator.js +221 -0
- package/generators/lib/migrate/field-type-resolver.js +319 -0
- package/generators/lib/migrate/label-generator.js +38 -0
- package/generators/lib/migrate/migrate-runner.js +187 -0
- package/generators/lib/migrate/naming.js +43 -0
- package/generators/lib/migrate/sql-parser.js +124 -0
- package/generators/lib/payload/payload-runner.js +106 -11
- package/generators/lib/templates/dashboard-catalog.js +1 -1
- package/generators/lib/templates/db-connection-env.js +1 -1
- package/generators/lib/templates/dbschema-catalog.js +1 -1
- package/generators/lib/templates/field-validation-catalog.js +1 -1
- package/generators/lib/templates/mysql-template.js +1 -1
- package/generators/lib/templates/oracle-template.js +1 -1
- package/generators/lib/templates/postgres-template.js +1 -1
- package/generators/lib/templates/query-declarative-catalog.js +1 -1
- package/generators/lib/templates/sqlite-template.js +1 -1
- package/integrity-manifest.json +18 -18
- package/node_modules/brace-expansion/index.js +1 -1
- package/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/dayjs/CHANGELOG.md +7 -0
- package/node_modules/dayjs/README.md +12 -10
- package/node_modules/dayjs/dayjs.min.js +1 -1
- package/node_modules/dayjs/esm/constant.js +1 -1
- package/node_modules/dayjs/esm/plugin/duration/index.js +5 -4
- package/node_modules/dayjs/locale.json +1 -1
- package/node_modules/dayjs/package.json +2 -2
- package/node_modules/dayjs/plugin/duration.js +1 -1
- package/node_modules/tmp/lib/tmp.js +37 -7
- package/node_modules/tmp/package.json +4 -16
- package/package.json +1 -1
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- package/src/utils/workflow-hook-executor.js +1 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migrate Runner — orchestrator untuk command `payload migrate`.
|
|
5
|
+
*
|
|
6
|
+
* Tanggung jawab:
|
|
7
|
+
* 1. Resolve file config (.env) via cascade lookup standar platform
|
|
8
|
+
* 2. Baca SERVER_ADDRESS dan SERVER_PORT dari config → konstruksi apiBaseUrl
|
|
9
|
+
* dengan format `http://{host}:{port}/api/{project}`
|
|
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`)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const { resolveConfig, printDefaultConfigWarning } = require('../utils/config-resolver');
|
|
21
|
+
const { readEnvFile } = require('../utils/env-manager');
|
|
22
|
+
const { migrate } = require('./backend-payload-migrator');
|
|
23
|
+
|
|
24
|
+
const DEFAULT_APP_NAME = 'My Application';
|
|
25
|
+
const DEFAULT_APP_CODE_FALLBACK = 'my-app';
|
|
26
|
+
const DEFAULT_PLUGIN = 'vanilla-js-basic';
|
|
27
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
28
|
+
const DEFAULT_BACKEND_PORT = 3000;
|
|
29
|
+
const DEFAULT_FRONTEND_PORT = 8000;
|
|
30
|
+
|
|
31
|
+
function resolveInputPath(nameArg, cwd) {
|
|
32
|
+
if (path.isAbsolute(nameArg)) return nameArg;
|
|
33
|
+
const direct = path.resolve(cwd, nameArg);
|
|
34
|
+
if (fs.existsSync(direct)) return direct;
|
|
35
|
+
const inPayload = path.resolve(cwd, 'payload', nameArg);
|
|
36
|
+
if (fs.existsSync(inPayload)) return inPayload;
|
|
37
|
+
// Fallback: return cwd-resolved path so caller dapat tampilkan error path
|
|
38
|
+
return direct;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildOutputPath(outputArg, inputBaseName, cwd) {
|
|
42
|
+
const targetFileName = inputBaseName;
|
|
43
|
+
|
|
44
|
+
if (!outputArg) {
|
|
45
|
+
const defaultDir = path.resolve(cwd, 'frontend', 'payload');
|
|
46
|
+
return path.join(defaultDir, targetFileName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
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);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readServerConfig(envFilePath) {
|
|
59
|
+
const { data } = readEnvFile(envFilePath);
|
|
60
|
+
const host = (typeof data.SERVER_ADDRESS === 'string' && data.SERVER_ADDRESS.length > 0)
|
|
61
|
+
? data.SERVER_ADDRESS
|
|
62
|
+
: DEFAULT_HOST;
|
|
63
|
+
const portRaw = (typeof data.SERVER_PORT === 'string' && data.SERVER_PORT.length > 0)
|
|
64
|
+
? data.SERVER_PORT
|
|
65
|
+
: '';
|
|
66
|
+
const portNum = parseInt(portRaw, 10);
|
|
67
|
+
const port = Number.isFinite(portNum) && portNum > 0 ? portNum : DEFAULT_BACKEND_PORT;
|
|
68
|
+
return { host, port };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildApiBaseUrl(host, port, project) {
|
|
72
|
+
const projectSegment = project ? `/${project}` : '';
|
|
73
|
+
return `http://${host}:${port}/api${projectSegment}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function run(args) {
|
|
77
|
+
const cwd = process.cwd();
|
|
78
|
+
|
|
79
|
+
if (!args.name || typeof args.name !== 'string') {
|
|
80
|
+
const err = new Error('Flag --name is required (backend payload file name, e.g. visitors.json)');
|
|
81
|
+
err.exitCode = 2;
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
if (!args.project || typeof args.project !== 'string') {
|
|
85
|
+
const err = new Error('Flag --project is required (project name used in apiBaseUrl)');
|
|
86
|
+
err.exitCode = 2;
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const resolved = resolveConfig(args.config, cwd);
|
|
91
|
+
if (!resolved) {
|
|
92
|
+
const err = new Error('Flag --config is required, or set a default config via `restforge config set-default`');
|
|
93
|
+
err.exitCode = 2;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
if (resolved.source === 'default') {
|
|
97
|
+
printDefaultConfigWarning(resolved.defaultName);
|
|
98
|
+
}
|
|
99
|
+
if (!fs.existsSync(resolved.path)) {
|
|
100
|
+
const err = new Error(`Config file not found: ${resolved.path}`);
|
|
101
|
+
err.exitCode = 3;
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { host, port: backendPort } = readServerConfig(resolved.path);
|
|
106
|
+
const project = args.project;
|
|
107
|
+
const apiBaseUrl = buildApiBaseUrl(host, backendPort, project);
|
|
108
|
+
|
|
109
|
+
const frontendPortArg = (typeof args.port === 'number' && Number.isFinite(args.port) && args.port > 0)
|
|
110
|
+
? args.port
|
|
111
|
+
: DEFAULT_FRONTEND_PORT;
|
|
112
|
+
|
|
113
|
+
const appName = args.appName || DEFAULT_APP_NAME;
|
|
114
|
+
const appCode = args.appCode || project || DEFAULT_APP_CODE_FALLBACK;
|
|
115
|
+
const plugin = args.plugin || DEFAULT_PLUGIN;
|
|
116
|
+
|
|
117
|
+
const inputPath = resolveInputPath(args.name, cwd);
|
|
118
|
+
if (!fs.existsSync(inputPath)) {
|
|
119
|
+
const err = new Error(`Input payload file not found: ${inputPath}`);
|
|
120
|
+
err.exitCode = 3;
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
let backendPayload;
|
|
124
|
+
try {
|
|
125
|
+
const content = fs.readFileSync(inputPath, 'utf8');
|
|
126
|
+
backendPayload = JSON.parse(content);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
const err = new Error(`Failed to parse JSON from ${inputPath}: ${e.message}`);
|
|
129
|
+
err.exitCode = 3;
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const inputBaseName = path.basename(inputPath);
|
|
134
|
+
const outputPath = buildOutputPath(args.output, inputBaseName, cwd);
|
|
135
|
+
|
|
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;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = migrate([backendPayload], appName, appCode, plugin, apiBaseUrl, frontendPortArg);
|
|
143
|
+
if (!result.success) {
|
|
144
|
+
const msg = (result.errors && result.errors.length > 0)
|
|
145
|
+
? result.errors.join('; ')
|
|
146
|
+
: 'Migration failed without details';
|
|
147
|
+
const err = new Error(msg);
|
|
148
|
+
err.exitCode = 1;
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const outputDir = path.dirname(outputPath);
|
|
153
|
+
if (!fs.existsSync(outputDir)) {
|
|
154
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
fs.writeFileSync(outputPath, JSON.stringify(result.payload, null, 2), 'utf8');
|
|
157
|
+
|
|
158
|
+
const stdout = process.stdout;
|
|
159
|
+
stdout.write('============================================================\n');
|
|
160
|
+
stdout.write('PAYLOAD MIGRATE - RDF (backend) -> UDF (frontend)\n');
|
|
161
|
+
stdout.write('============================================================\n\n');
|
|
162
|
+
stdout.write(` Input : ${inputPath}\n`);
|
|
163
|
+
stdout.write(` Output : ${outputPath}\n`);
|
|
164
|
+
stdout.write(` Project : ${project}\n`);
|
|
165
|
+
stdout.write(` apiBaseUrl : ${apiBaseUrl}\n`);
|
|
166
|
+
stdout.write(` Backend port : ${backendPort}\n`);
|
|
167
|
+
stdout.write(` Frontend port: ${frontendPortArg}\n`);
|
|
168
|
+
stdout.write(` Pages : ${result.pageResults.length}\n\n`);
|
|
169
|
+
for (const pr of result.pageResults) {
|
|
170
|
+
stdout.write(` [OK] ${pr.pageId}: ${pr.fieldCount} field(s), ${pr.tableColCount} table column(s)\n`);
|
|
171
|
+
}
|
|
172
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
173
|
+
stdout.write('\n Warnings:\n');
|
|
174
|
+
for (const w of result.warnings) {
|
|
175
|
+
stdout.write(` - ${w}\n`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
stdout.write('\n Migration completed successfully.\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
run,
|
|
183
|
+
resolveInputPath,
|
|
184
|
+
buildOutputPath,
|
|
185
|
+
readServerConfig,
|
|
186
|
+
buildApiBaseUrl
|
|
187
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Port dari packages/designer/src/utils/naming.rs.
|
|
5
|
+
* Konversi naming convention: snake_case -> kebab-case / camelCase / PascalCase / Title Case.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
function capitalize(s) {
|
|
9
|
+
if (!s || s.length === 0) return '';
|
|
10
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function snakeToKebab(name) {
|
|
14
|
+
return String(name || '').replace(/_/g, '-');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function snakeToCamel(name) {
|
|
18
|
+
const parts = String(name || '').split('_');
|
|
19
|
+
if (parts.length === 0) return '';
|
|
20
|
+
const first = parts.shift();
|
|
21
|
+
return first + parts.map(capitalize).join('');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function snakeToPascal(name) {
|
|
25
|
+
return String(name || '').split('_').map(capitalize).join('');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function snakeToTitle(name) {
|
|
29
|
+
return String(name || '').split('_').map(capitalize).join(' ');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toClassName(appCode) {
|
|
33
|
+
return String(appCode || '').replace(/_/g, '-').split('-').map(capitalize).join('');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
capitalize,
|
|
38
|
+
snakeToKebab,
|
|
39
|
+
snakeToCamel,
|
|
40
|
+
snakeToPascal,
|
|
41
|
+
snakeToTitle,
|
|
42
|
+
toClassName
|
|
43
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Port dari packages/designer/src/migrators/sql_parser.rs.
|
|
5
|
+
* Regex-based parser untuk SELECT/JOIN dari `datatablesQuery` string.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const SELECT_RE = /select\s+([\s\S]*?)\s+from\s+/i;
|
|
9
|
+
const FROM_RE = /\bfrom\s+([\s\S]*)/i;
|
|
10
|
+
const JOIN_SPLIT_RE = /\b(left\s+join|inner\s+join|right\s+join|cross\s+join|join)\b/i;
|
|
11
|
+
const JOIN_SPLIT_RE_GLOBAL = /\b(left\s+join|inner\s+join|right\s+join|cross\s+join|join)\b/ig;
|
|
12
|
+
const WHERE_SPLIT_RE = /\s+where\s+/i;
|
|
13
|
+
const ORDER_SPLIT_RE = /\s+order\s+/i;
|
|
14
|
+
const ON_CLAUSE_RE = /([\s\S]*?)\s+on\s+([\s\S]*)/i;
|
|
15
|
+
const JOIN_COND_RE = /(\w+)\.(\w+)\s*=\s*(\w+)\.(\w+)/;
|
|
16
|
+
|
|
17
|
+
function stripSchema(name) {
|
|
18
|
+
const idx = name.lastIndexOf('.');
|
|
19
|
+
return idx >= 0 ? name.slice(idx + 1) : name;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseTableRef(refstr) {
|
|
23
|
+
const afterWhere = refstr.split(WHERE_SPLIT_RE)[0].trim();
|
|
24
|
+
const cleaned = afterWhere.split(ORDER_SPLIT_RE)[0].trim();
|
|
25
|
+
const tokens = cleaned.split(/\s+/).filter(Boolean);
|
|
26
|
+
if (tokens.length === 0) return { tableName: '', alias: '' };
|
|
27
|
+
const tableName = stripSchema(tokens[0]);
|
|
28
|
+
const alias = tokens.length > 1 ? tokens[1] : '';
|
|
29
|
+
return { tableName, alias };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseJoinCondition(condition, mainAlias) {
|
|
33
|
+
const m = JOIN_COND_RE.exec(condition.trim());
|
|
34
|
+
if (!m) return { localColumn: '', remoteColumn: '' };
|
|
35
|
+
const alias1 = m[1];
|
|
36
|
+
const col1 = m[2];
|
|
37
|
+
const col2 = m[4];
|
|
38
|
+
if (alias1 === mainAlias) {
|
|
39
|
+
return { localColumn: col1, remoteColumn: col2 };
|
|
40
|
+
}
|
|
41
|
+
return { localColumn: col2, remoteColumn: col1 };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseDatatablesQuery(sql) {
|
|
45
|
+
const trimmed = String(sql || '').trim();
|
|
46
|
+
if (!trimmed) {
|
|
47
|
+
return { selectColumns: [], joins: [], mainTable: '', mainAlias: '' };
|
|
48
|
+
}
|
|
49
|
+
const sqlClean = trimmed.replace(/;+\s*$/, '');
|
|
50
|
+
|
|
51
|
+
const selectMatch = SELECT_RE.exec(sqlClean);
|
|
52
|
+
if (!selectMatch) {
|
|
53
|
+
return { selectColumns: [], joins: [], mainTable: '', mainAlias: '' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const columnsStr = selectMatch[1];
|
|
57
|
+
const rawColumns = columnsStr.split(',');
|
|
58
|
+
const selectColumns = [];
|
|
59
|
+
for (let i = 0; i < rawColumns.length; i++) {
|
|
60
|
+
const col = rawColumns[i].trim();
|
|
61
|
+
if (!col) continue;
|
|
62
|
+
const parts = col.split('.');
|
|
63
|
+
let tableAlias = '';
|
|
64
|
+
let name;
|
|
65
|
+
if (parts.length >= 2) {
|
|
66
|
+
tableAlias = parts[parts.length - 2].trim();
|
|
67
|
+
name = parts[parts.length - 1].trim();
|
|
68
|
+
} else {
|
|
69
|
+
name = parts[0].trim();
|
|
70
|
+
}
|
|
71
|
+
selectColumns.push({ name, tableAlias, position: i + 1 });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fromMatch = FROM_RE.exec(sqlClean);
|
|
75
|
+
if (!fromMatch) {
|
|
76
|
+
return { selectColumns, joins: [], mainTable: '', mainAlias: '' };
|
|
77
|
+
}
|
|
78
|
+
const fromClause = fromMatch[1];
|
|
79
|
+
|
|
80
|
+
// Python re.split with capture group returns [before, kw, body, kw, body, ...]
|
|
81
|
+
const parts = [];
|
|
82
|
+
let lastEnd = 0;
|
|
83
|
+
JOIN_SPLIT_RE_GLOBAL.lastIndex = 0;
|
|
84
|
+
let match;
|
|
85
|
+
while ((match = JOIN_SPLIT_RE_GLOBAL.exec(fromClause)) !== null) {
|
|
86
|
+
parts.push(fromClause.slice(lastEnd, match.index));
|
|
87
|
+
parts.push(match[0]);
|
|
88
|
+
lastEnd = match.index + match[0].length;
|
|
89
|
+
}
|
|
90
|
+
parts.push(fromClause.slice(lastEnd));
|
|
91
|
+
|
|
92
|
+
const mainRef = parseTableRef(parts[0] || '');
|
|
93
|
+
const mainTable = mainRef.tableName;
|
|
94
|
+
const mainAlias = mainRef.alias;
|
|
95
|
+
|
|
96
|
+
const joins = [];
|
|
97
|
+
let j = 1;
|
|
98
|
+
while (j + 1 < parts.length) {
|
|
99
|
+
const joinBody = parts[j + 1].trim();
|
|
100
|
+
const onMatch = ON_CLAUSE_RE.exec(joinBody);
|
|
101
|
+
if (!onMatch) {
|
|
102
|
+
j += 2;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const tableRef = onMatch[1];
|
|
106
|
+
const condition = onMatch[2];
|
|
107
|
+
const ref = parseTableRef(tableRef);
|
|
108
|
+
const cond = parseJoinCondition(condition, mainAlias);
|
|
109
|
+
joins.push({
|
|
110
|
+
tableName: ref.tableName,
|
|
111
|
+
tableAlias: ref.alias,
|
|
112
|
+
localColumn: cond.localColumn,
|
|
113
|
+
remoteColumn: cond.remoteColumn
|
|
114
|
+
});
|
|
115
|
+
j += 2;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { selectColumns, joins, mainTable, mainAlias };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
parseDatatablesQuery,
|
|
123
|
+
stripSchema
|
|
124
|
+
};
|
|
@@ -155,10 +155,52 @@ class PayloadGenerator {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
158
|
+
* Enrich detailedColumns dengan flag `is_unique` berdasarkan output
|
|
159
|
+
* db.getConstraints(). Hanya constraint UNIQUE single-column yang ditandai;
|
|
160
|
+
* UNIQUE composite (multi-column) di-skip karena tidak dapat diatribusikan
|
|
161
|
+
* ke satu kolom secara semantik (uniqueness berlaku pada kombinasi kolom,
|
|
162
|
+
* bukan kolom individual).
|
|
163
|
+
*
|
|
164
|
+
* PRIMARY KEY tidak di-tag is_unique karena sudah di-handle terpisah via
|
|
165
|
+
* `isVarcharLikePk` / `isUuidNativeType` di generateFieldValidation.
|
|
166
|
+
*
|
|
167
|
+
* Pemanggil bertanggung jawab fetch constraints via `db.getConstraints(table)`
|
|
168
|
+
* sebelum memanggil method ini.
|
|
169
|
+
*
|
|
161
170
|
* @param {Array} detailedColumns - Array from getDetailedColumnInfo()
|
|
171
|
+
* @param {Array} constraints - Array from getConstraints() with shape {name, type, columns}
|
|
172
|
+
* @returns {Array} New array dengan flag is_unique pada kolom yang relevan
|
|
173
|
+
*/
|
|
174
|
+
enrichDetailedColumnsWithConstraints(detailedColumns, constraints) {
|
|
175
|
+
if (!Array.isArray(detailedColumns)) return [];
|
|
176
|
+
if (!Array.isArray(constraints) || constraints.length === 0) return detailedColumns;
|
|
177
|
+
|
|
178
|
+
const uniqueColumns = new Set();
|
|
179
|
+
for (const c of constraints) {
|
|
180
|
+
if (c.type === 'UNIQUE' && Array.isArray(c.columns) && c.columns.length === 1) {
|
|
181
|
+
uniqueColumns.add(c.columns[0]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (uniqueColumns.size === 0) return detailedColumns;
|
|
186
|
+
|
|
187
|
+
return detailedColumns.map(col => (
|
|
188
|
+
uniqueColumns.has(col.column_name) ? { ...col, is_unique: true } : col
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Generate fieldValidation array from detailed column info
|
|
194
|
+
* Includes: primary key (UUID/auto-generate), numeric, boolean, date/time, and
|
|
195
|
+
* string fields yang membawa constraint actionable dari DB introspection
|
|
196
|
+
* (NOT NULL -> required, character_maximum_length -> maxLength,
|
|
197
|
+
* UNIQUE single-column -> unique). Nullable string tanpa constraint tetap
|
|
198
|
+
* di-skip agar payload tidak bloat.
|
|
199
|
+
*
|
|
200
|
+
* Untuk derive `unique`, kolom harus punya flag `is_unique: true` yang
|
|
201
|
+
* di-inject oleh `enrichDetailedColumnsWithConstraints()` sebelum dipanggil.
|
|
202
|
+
*
|
|
203
|
+
* @param {Array} detailedColumns - Array from getDetailedColumnInfo() (opsional di-enrich dengan is_unique)
|
|
162
204
|
* @param {Array} fieldNames - Array of selected field names
|
|
163
205
|
* @param {string} primaryKey - Primary key field name
|
|
164
206
|
* @returns {Array} fieldValidation array
|
|
@@ -211,14 +253,22 @@ class PayloadGenerator {
|
|
|
211
253
|
}
|
|
212
254
|
|
|
213
255
|
if (isVarcharLikePk) {
|
|
256
|
+
const constraints = {
|
|
257
|
+
primaryKey: true,
|
|
258
|
+
autoGenerate: true,
|
|
259
|
+
required: true
|
|
260
|
+
};
|
|
261
|
+
// Defense in depth: derive maxLength dari character_maximum_length agar
|
|
262
|
+
// user yang kirim PK manual (override autoGenerate) tetap divalidasi
|
|
263
|
+
// di application layer sebelum hit DB. UUID v7 generated value (36 char)
|
|
264
|
+
// selalu fit di varchar(>=36); maxLength tidak mengganggu autoGenerate.
|
|
265
|
+
if (col.character_maximum_length) {
|
|
266
|
+
constraints.maxLength = col.character_maximum_length;
|
|
267
|
+
}
|
|
214
268
|
fieldValidation.push({
|
|
215
269
|
name: fieldName,
|
|
216
270
|
type: 'string',
|
|
217
|
-
constraints
|
|
218
|
-
primaryKey: true,
|
|
219
|
-
autoGenerate: true,
|
|
220
|
-
required: true
|
|
221
|
-
}
|
|
271
|
+
constraints
|
|
222
272
|
});
|
|
223
273
|
continue;
|
|
224
274
|
}
|
|
@@ -370,7 +420,46 @@ class PayloadGenerator {
|
|
|
370
420
|
continue;
|
|
371
421
|
}
|
|
372
422
|
|
|
373
|
-
// --- String fields
|
|
423
|
+
// --- String fields ---
|
|
424
|
+
// VARCHAR/TEXT/CHAR non-PK: derive validation actionable dari DB introspection.
|
|
425
|
+
// - required: dari is_nullable = 'NO' (NOT NULL di database)
|
|
426
|
+
// - maxLength: dari character_maximum_length
|
|
427
|
+
// - unique: dari flag is_unique yang di-inject via enrichDetailedColumnsWithConstraints()
|
|
428
|
+
// PK varchar sudah di-handle di branch isVarcharLikePk di atas (continue).
|
|
429
|
+
// Defensive guard `isPrimaryKey` mencegah duplicate entry bila PK varchar
|
|
430
|
+
// lolos branch isVarcharLikePk (mis. PK dengan nextval default).
|
|
431
|
+
// Nullable string tanpa constraint sama sekali tetap di-skip agar payload
|
|
432
|
+
// tidak bloat dengan entry kosong yang tidak menambah validasi runtime.
|
|
433
|
+
if (['character varying', 'varchar', 'text', 'character', 'char'].includes(dataType)
|
|
434
|
+
|| ['varchar', 'text', 'bpchar'].includes(udtName)) {
|
|
435
|
+
|
|
436
|
+
if (isPrimaryKey) continue;
|
|
437
|
+
|
|
438
|
+
const entry = {
|
|
439
|
+
name: fieldName,
|
|
440
|
+
type: 'string',
|
|
441
|
+
constraints: {}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
if (col.is_nullable === 'NO' || col.is_nullable === false) {
|
|
445
|
+
entry.constraints.required = true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (col.character_maximum_length) {
|
|
449
|
+
entry.constraints.maxLength = col.character_maximum_length;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (col.is_unique === true) {
|
|
453
|
+
entry.constraints.unique = true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (Object.keys(entry.constraints).length === 0) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
fieldValidation.push(entry);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
374
463
|
}
|
|
375
464
|
|
|
376
465
|
return fieldValidation;
|
|
@@ -605,7 +694,9 @@ class PayloadGenerator {
|
|
|
605
694
|
|
|
606
695
|
// Generate fieldValidation for special fields
|
|
607
696
|
const detailedColumns = await this.db.getDetailedColumnInfo(args.table);
|
|
608
|
-
const
|
|
697
|
+
const constraints = await this.db.getConstraints(args.table);
|
|
698
|
+
const enrichedColumns = this.enrichDetailedColumnsWithConstraints(detailedColumns, constraints);
|
|
699
|
+
const fieldValidation = this.generateFieldValidation(enrichedColumns, payloadData.fieldName, payloadData.primaryKey);
|
|
609
700
|
if (fieldValidation.length > 0) {
|
|
610
701
|
payloadData.fieldValidation = fieldValidation;
|
|
611
702
|
}
|
|
@@ -1056,6 +1147,9 @@ class SchemaValidator {
|
|
|
1056
1147
|
const dbColumns = await this.db.getColumns(tableName);
|
|
1057
1148
|
const columnTypes = await this.db.getColumnTypes(tableName);
|
|
1058
1149
|
const detailedColumns = await this.db.getDetailedColumnInfo(tableName);
|
|
1150
|
+
const constraints = typeof this.db.getConstraints === 'function'
|
|
1151
|
+
? await this.db.getConstraints(tableName)
|
|
1152
|
+
: [];
|
|
1059
1153
|
const primaryKey = await this.db.getPrimaryKey(tableName) || oldPayload.primaryKey;
|
|
1060
1154
|
|
|
1061
1155
|
// Bangun fieldName baru: pertahankan urutan field lama, tambahkan field baru di akhir.
|
|
@@ -1123,7 +1217,8 @@ class SchemaValidator {
|
|
|
1123
1217
|
}
|
|
1124
1218
|
|
|
1125
1219
|
// Re-generate fieldValidation
|
|
1126
|
-
const
|
|
1220
|
+
const enrichedColumns = tempGenerator.enrichDetailedColumnsWithConstraints(detailedColumns, constraints);
|
|
1221
|
+
const fieldValidation = tempGenerator.generateFieldValidation(enrichedColumns, newFieldName, primaryKey);
|
|
1127
1222
|
if (fieldValidation.length > 0) {
|
|
1128
1223
|
updatedPayload.fieldValidation = fieldValidation;
|
|
1129
1224
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const a0_0x15a923=a0_0x591c;function a0_0xe2e1(){const _0x390e68=['ue9tvcaVyxbPl21PBMKTAw52zw50B3j5l2rHC2GTAw5IB3vUzc9KyxnOyM9HCMq','tMvNyxrPDMuGBg9VA2jLAgLUzcbWCMv2zw50CYbTyxrJAgLUzYaNoJONicHqB3n0z3jLCYbJyxn0ihn5BNrHEcKGyxmGysbWBgfJzwHVBgrLCI4','C2nHBgfYihbYAw1PDgL2zq','zxHWB3j0CW','qwX3yxLZihDYyxaGyxmGEYbPDgvTCZOGwY4UlL0GFsbYzwDHCMrSzxnZig9MifnrtcbYzxn1BhqGC2HHCguU','iNzHBhvLiJOGiJy5nZaWiG','v2HLBIb0CNvLlcb0AguGCMvXDwvZDcbIB2r5ie1vu1qGAw5JBhvKzsb0AgLZihbHCMfTicHVDgHLCNDPC2uGndaWks4','ugfYyw0Gzgf0ysb0ExbLlIbwywXPzgf0zxmGCMvXDwvZDcbIB2r5igfUzcbZAgfWzxmGCNvUDgLTzsbWyxjHBwv0zxiGyMLUzgLUzY4','ndi1mZu4mgnVufH6Eq','yM9VBgvHBG','yxzNx2rHAwX5x3nHBgvZ','qsb3AwrNzxqGtvvtvcbKzwnSyxjLigv4ywn0BhKGB25Lig9MoIaNCxvLCNKNie9sicDXDwvYAwvZjY4GqM90AcbVCIbUzwL0AgvYigLZihjLAMvJDgvKlG','Aw5OzxjPDhmGq0fdsevFvfrmigvUDG','EYaIzgLYzwn0Aw9UiJOGiMrVD24IlcaICgn0iJOGiJiUmIiGFq','CgfYyw1Z','vgLTzs10BY1SAxzLigLUihnLy29UzhmUidaGzwzMzwn0AxzLBhKGzgLZywjSzxmGy2fJAguGzM9YihrOAxmGzw50CNKU','tIbYB3DZimoxie0Gy29SCW','BwfW','qsbWyxLSB2fKihDPDgGGyM90AcaND2LKz2v0CYCGyw5KicD0ywjSzu5HBwuNigLZihjLAMvJDgvKigj5ierHC2HIB2fYzfzHBgLKyxrVCI4GugLJAYbVBMuGC2HHCguU','ugfYyw0GBMfTzsbTDxn0ig1HDgnOihrOzsbWBgfJzwHVBgrLCIbYzwDLEcbGw2eTEKeTwL9Dw2eTEKeTwJaTov9DkMaGkgfSCgHHBNvTzxjPyYaRihvUzgvYC2nVCMuSig11C3qGC3rHCNqGD2L0AcbSzxr0zxiGB3iGDw5KzxjZy29YzsKU','q29SBgfWC2uGDg8GC2nHBgfYihbYAw1PDgL2zsaODgHLihzHBhvLig9MihrOzsbZAw5NBguGy29SDw1Uks4','tIbYB3DZimoxidiGy29SDw1UCW','y29SB3i','yxjYyxK','zMLSztPXDwvYEs88Cgf0Ad4VyNjLywTKB3DUlNnXBa','mtKYmJu4vgTUtKXz','vgfIBguGyxbWzwfYCYbPBIbtuuWGqu5eigLUig1LDgfKyxrHihbYB2PLy3qSigj1DcbTAxnZAw5NigzYB20GAw52ywXPzgf0zxmGkgnHy2HLihn0ywXLihjPC2SP','zMLSztPXDwvYEs88Cgf0Ad4VDhjLBMqUC3fS','B3jKzxjZx3rOAxnFBw9UDgG','A2v54OAszMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa','iJi0mJaI','Cgf5Bg9Hzcbku09oigzPBguGBg9JyxrPB24','uMvZzxj2zwqGzM9YiensvuqGCgf5Bg9HzhmUieeGzgfZAgjVyxjKihbHEwXVywqGBxvZDcbKzwnSyxjLicD3AwrNzxrZjYbPBNn0zwfKlG','DgfIBgvoyw1L','rNjVBNrLBMqGzgv0zxjTAw5LCYbKB251Dc9WAwuGDMfYAwfUDcWGy29SB3iGCgvYignHDgvNB3j5lcbHBMqGBgfIzwWGB3jKzxiUieLMihbLCI1JyxrLz29YEsbWzxjJzw50ywDLigLZig5LzwrLzcbMB3iGDgHLigrVBNv0igfYyYWGzNjVBNrLBMqGy29TChv0zxmGAxqGzNjVBsbPDgvTC1TPxs52ywX1zsaVihn1BsHPDgvTC1SQxs52ywX1zsKUie5Vig5LzwqGDg8GC2vUzcaNCgn0jYbMCM9TigjHy2TLBMqGDw5SzxnZihrOzsbMAwD1CMuGAxmGysbZDgfIBguGyNvZAw5LC3mGy2fSy3vSyxrPB24GAw5KzxbLBMrLBNqGB2yGDMLZDwfSihjLBMrLCMLUzY4','BM9UlwvTChr5lcb1BMLXDwuGywnYB3nZihDPzgDLDhmGAw4GDgHLihnHBwuGCgf5Bg9Hza','EYbZDwnJzxnZoIbIB29SzwfUlcbKyxrHoIb7idX3AwrNzxrjzd46idXWzxjxAwrNzxrszxnWB25Zzt4Sic4UlIb9ih0','zgfZAc1HDxrOB3iTC3rHDhm','u2LUz2XLifnrtcbXDwvYEsbMB3iGDgHLihDPzgDLDc4','Bwv0CMLJx3bYB2DYzxnZx3rVx2DVywW','DhrS','BgvUz3rO','zMLSztPYzwXHDgL2zs9WyxrOl3rVl3f1zxj5lNnXBa','D2LKz2v0lNf1zxj5icHZAw5NDwXHCIK','twv0CMLJicSGu3bHCMTSAw5L','CgvYAw9K','vxbKyxrPBMCGyw4Gu1fmigzPBguGCMvXDwLYzxmGCMvNzw5LCMf0Aw5NihrOzsbKyxnOyM9HCMqGBw9KDwXLicGNy29KzwDLBL9JCMvHDgvFzgfZAgjVyxjKjYKGzM9YignOyw5NzxmGDg8GDgfRzsbLzMzLy3qU','yxjYyxKGB2yGB2jQzwn0CW','yw55icHTDxn0igjLignVBxbHDgLIBguGD2L0AcbKzwnSyxjLzcaNDhLWzsCP','uMv0DxjUigfZigfYCMf5ig9Mig9IAMvJDhmGkg5VignVBgXHChnLks4','z2vUzxjHDgLVBIb0Aw1LicHot1qGCNvUDgLTzsK','w3SGiMXHyMvSiJOGiLnOB2vZiIWGiNzHBhvLiJOGiJC2nJaIih0SihSGiMXHyMvSiJOGiKDHBwLUzYiSicj2ywX1zsi6iciYodiWiIb9lcb7icjSywjLBci6icjpDgHLCNmIlcaIDMfSDwuIoIaInduYntCIih1D','zgvMyxvSDa','tgf5B3v0igLZigeGzNjVBNrLBMqGCMvUzgvYAw5NignVBMnLCM4U','msbYB3CGW5CGmIbJB2X1Bw5Z','B2jQzwn0','nti2ndiWuNbOs1jp','rgfZAgjVyxjKigvUzhbVAw50ig1HEsbVChqTAw4GDg8GuMvKAxmTyMfZzwqGy2fJAguUifbHDhrLCM4GzM9SBg93CYbWCM9JzxnZB3iGy2fJAguGkhnLzsbMzwf0lwnHy2HLlM1Kks4Gq2fJAguGC2nVCguGAxmGDgHLigz1BgWGCMvZCg9UC2uGzw52zwXVCgu7ig9UzsbJywnOzsbLBNrYEsbWzxiGkhbHCMfTCYaRihDPzgDLDhnBxsbZDwjZzxqPignVBwjPBMf0Aw9UlG','txvSDgKTu1fmihDPzgDLDc4GrwfJAcbRzxKGyMvJB21LCYbHigTLEsbPBIb0AguGCMvZCg9UC2uGB2jQzwn0lG','zgfZAgjVyxjKihbHEwXVywq','Bwv0CMLJx2rVBNv0x2jYzwfRzg93BG','nZC0nZq0mfDpDgTZqG','msbYB3CGW5CGmsbJB2X1Bw4','iNnOB3bWAw5Nx2nHDgvNB3jPzxmIoIb7icjPDgvTCYi6ifT7icjUyw1LiJOGiKXHBMrZiIb9lcb7icjUyw1LiJOGiKHVDxnLCYiGFv0GFq','yxjYyxK8C3rYAw5NpIWGB3b0Aw9UywWG4Ocuihn1yNnLDcbVzIb3AwrNzxqGsurZihrVigv4zwn1DguUie9TAxqGDg8GzxHLy3v0zsbHBgWGzgvJBgfYzwqGD2LKz2v0CY4','Ahr0Chm6lY9Yzxn0zM9Yz2uUzgv2l2rVy3mVC2vYDMvYl3f1zxj5lwrHDgeVzgfZAgjVyxjK','BgfIzwW','zMLSztPXDwvYEs88Cgf0Ad4VDgfYz2v0lNnXBa','mteYmenpuxLLsa','sgvHzgXPBMuGBwv0CMLJihDPDgGGDhjLBMqGy2HPCcbHBMqGChjVz3jLC3mGyMfYigfNywLUC3qGysbWzxjPB2qGDgfYz2v0lIbtDwL0ywjSzsbMB3iGD2LKz2v0CYbSAwTLicDpCMrLCNmGvgHPCYbnB250AcCU','D2LKz2v0vhLWzq','rNjVBNrLBMqGy29TChv0zxmGDg9Fz29HBca9ihrHCMDLDcaTihzHBhvLigfUzcbWy3qGpsbYB3vUzcH2ywX1zsaVihrHCMDLDcaQideWmcKGzM9YihrOzsbWCM9NCMvZCYbIyxiUifzPC3vHBcb3Awr0AcbPCYbWCMvZzw50yxrPB25HBcbHBMqGBxvZDcbot1qGBgL2zsbPBIb0AguGyMfJA2vUzcbWyxLSB2fKlIbjzIbWCM9NCMvZCYbPBNzVBhzLCYbJB21WBgv4igj1C2LUzxnZihj1BgvZicHLlMCUigv4y2X1zguGD2vLA2vUzhmSihbYB3jHDgvKihDVCMTKyxLZksWGDxnLigeGC2LUz2XLig11BhrPlwnVBhvTBIbXDwvYEsbZBYaNCgn0jYbPCYbHihn0ywjSzsbIDxnPBMvZCYbMywn0ihjHDgHLCIb0AgfUihzPC3vHBcb3Awr0Ac4','tvvtvcbZDgfYDcb3AxrOicDKyxnOlsCGChjLzML4','Cgn0','nJG4mJq2yxv1ExPO','twv0CMLJicSGrg9UDxqGqNjLywTKB3DU','DMfSDwuGkg9Yign1CNjLBNqP','ntGYodb4yu1jy0i','phDPzgDLDf9Pzd4','zNjLztSGlNnXBcbYzwnVBw1LBMrLzcbMB3iGzwrPDg9YigHPz2HSAwDODa','m0PbqwnUtq','v2LKz2v0igLKzw50AwzPzxi7ihvZzwqGyxmGDgHLihjLC3bVBNnLigTLEsbPBIb0AguGzgfZAgjVyxjKigvUDMvSB3bLlG','DMfSDwu','ms4W','zMLSztPXDwvYEs88Cgf0Ad4VDMfSDwuUC3fS','mtm0mJy0mNPRAerkEq','vgHLihbYzwzPEcbIzwnVBwvZihbHCNqGB2yGDgHLifvstcbZzwDTzw50lIbuAguGCMvZzxj2zwqGC2nOzw1LigTLzxbZigrHC2HIB2fYzcbLBMrWB2LUDhmGDMLZDwfSBhKGzgLZDgLUy3qGzNjVBsbduLveigvUzhbVAw50CYbPBIb0AguGvvjmihnWywnLigfUzcbHBgXVD3mGzNv0DxjLihjVDxrPBMCGzgLMzMvYzw50Awf0Aw9UlG','CxvLCMLLCW','q29SBgfWC2uGDg8GB2jQzwn0ihDOB3nLigTLExmGyxjLifnrtcbJB2X1Bw4GBMfTzxmGkgXVD2vYy2fZzwqPlG','Bgf5B3v0','vgfIBguGzgv0zwn0zwqGAw4Gu1fmlcbIDxqGBM90ihjLz2LZDgvYzwqGyxmGq1jvrcbLBMrWB2LUDcbPBIbTzxrHzgf0ysbWCM9Qzwn0icHSAwTLBhKGysb2Awv3lcbdveuGywXPyxmSig9YignYB3nZlxbYB2PLy3qGDgfIBguG4OcuignHC2nHzguGD2LSBcbUB3qGzMLYzsK','zNjVBNrLBMqTy29Uy2vYBG','zgLYzwn0Aw9U'];a0_0xe2e1=function(){return _0x390e68;};return a0_0xe2e1();}function a0_0x591c(_0x522681,_0xd00375){_0x522681=_0x522681-0x1dd;const _0xe2e103=a0_0xe2e1();let _0x591cc5=_0xe2e103[_0x522681];if(a0_0x591c['xXiwze']===undefined){var _0x1359da=function(_0x2e4df6){const _0xd5c81='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x3da22b='',_0x575846='';for(let _0x4170a5=0x0,_0x4100e3,_0x2c4b48,_0x57a74d=0x0;_0x2c4b48=_0x2e4df6['charAt'](_0x57a74d++);~_0x2c4b48&&(_0x4100e3=_0x4170a5%0x4?_0x4100e3*0x40+_0x2c4b48:_0x2c4b48,_0x4170a5++%0x4)?_0x3da22b+=String['fromCharCode'](0xff&_0x4100e3>>(-0x2*_0x4170a5&0x6)):0x0){_0x2c4b48=_0xd5c81['indexOf'](_0x2c4b48);}for(let _0x1d6749=0x0,_0x119ff5=_0x3da22b['length'];_0x1d6749<_0x119ff5;_0x1d6749++){_0x575846+='%'+('00'+_0x3da22b['charCodeAt'](_0x1d6749)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x575846);};a0_0x591c['krLJYG']=_0x1359da,a0_0x591c['nAzaeG']={},a0_0x591c['xXiwze']=!![];}const _0x53f8e0=_0xe2e103[0x0],_0x1fbe7e=_0x522681+_0x53f8e0,_0x32ceea=a0_0x591c['nAzaeG'][_0x1fbe7e];return!_0x32ceea?(_0x591cc5=a0_0x591c['krLJYG'](_0x591cc5),a0_0x591c['nAzaeG'][_0x1fbe7e]=_0x591cc5):_0x591cc5=_0x32ceea,_0x591cc5;}(function(_0x293a3a,_0x273f8b){const _0x336c97=a0_0x591c,_0x2f826f=_0x293a3a();while(!![]){try{const _0x31a85e=parseInt(_0x336c97(0x1fb))/0x1+parseInt(_0x336c97(0x206))/0x2+parseInt(_0x336c97(0x201))/0x3*(-parseInt(_0x336c97(0x1e9))/0x4)+parseInt(_0x336c97(0x1ee))/0x5+-parseInt(_0x336c97(0x216))/0x6+-parseInt(_0x336c97(0x1f5))/0x7*(parseInt(_0x336c97(0x1fe))/0x8)+parseInt(_0x336c97(0x227))/0x9;if(_0x31a85e===_0x273f8b)break;else _0x2f826f['push'](_0x2f826f['shift']());}catch(_0x706318){_0x2f826f['push'](_0x2f826f['shift']());}}}(a0_0xe2e1,0xe1a7a));const FORBIDDEN_FRONTEND_FIELDS=[a0_0x15a923(0x1f7),a0_0x15a923(0x20a),'title','subtitle',a0_0x15a923(0x224)],ALLOWED_PARAM_TYPES=['string','number',a0_0x15a923(0x217),'date'],FRONTEND_CONCERN_REASONS={'widgetType':'Visual\x20variant\x20(donut,\x20bar,\x20pie,\x20area)\x20is\x20a\x20frontend\x20rendering\x20concern\x20(separation\x20of\x20concerns).','layout':a0_0x15a923(0x1e6),'title':'UI\x20label\x20is\x20a\x20frontend\x20rendering\x20concern.','subtitle':'UI\x20label\x20is\x20a\x20frontend\x20rendering\x20concern.','color':'Visual\x20color\x20is\x20a\x20frontend\x20rendering\x20concern.'},PAYLOAD_SHAPE={'discriminator':{'field':'widgets','presentMeans':a0_0x15a923(0x1ec),'absentMeans':'Not\x20a\x20dashboard\x20payload\x20(likely\x20CRUD\x20with\x20tableName,\x20or\x20invalid)','conflictsWith':'tableName','conflictRationale':a0_0x15a923(0x220)},'topLevelAllowed':[{'name':'widgets','type':a0_0x15a923(0x225),'required':!![],'minItems':0x1,'description':'List\x20of\x20widget\x20definitions.\x20Order\x20is\x20informational\x20only\x20(response\x20keys\x20are\x20by\x20widget\x20id,\x20not\x20array\x20index).'},{'name':a0_0x15a923(0x21c),'type':a0_0x15a923(0x1e8),'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':'cache','type':a0_0x15a923(0x1e8),'required':![],'description':'Optional\x20cache\x20configuration.\x20See\x20cacheSpec\x20for\x20details.'}],'topLevelForbidden':[{'name':a0_0x15a923(0x22f),'category':'shape-conflict','reason':a0_0x15a923(0x22e)},...FORBIDDEN_FRONTEND_FIELDS[a0_0x15a923(0x21f)](_0x3da22b=>({'name':_0x3da22b,'category':a0_0x15a923(0x20c),'reason':FRONTEND_CONCERN_REASONS[_0x3da22b]}))]},WIDGET_SPEC={'requiredFields':[{'name':'id','type':'string','constraint':a0_0x15a923(0x231),'description':a0_0x15a923(0x202)}],'exclusiveQueryFields':{'rule':a0_0x15a923(0x219),'options':[{'name':'query','type':'string','format':a0_0x15a923(0x238),'description':a0_0x15a923(0x234),'responseShape':'Always\x20{\x20items:\x20[...]\x20}\x20regardless\x20of\x20SQL\x20result\x20shape.'},{'name':a0_0x15a923(0x208),'type':'object','format':a0_0x15a923(0x22b),'minKeys':0x1,'description':a0_0x15a923(0x1eb),'responseShape':'Per-key\x20based\x20on\x20scalarCollapseRules\x20below.'}]},'forbiddenFields':FORBIDDEN_FRONTEND_FIELDS},PARAM_SPEC={'container':'top-level\x20\x27params\x27\x20object','keyConvention':a0_0x15a923(0x221),'perEntryFields':[{'name':'type','required':!![],'allowedValues':ALLOWED_PARAM_TYPES,'description':a0_0x15a923(0x215)},{'name':'required','required':![],'type':a0_0x15a923(0x217),'default':![],'description':a0_0x15a923(0x214)},{'name':a0_0x15a923(0x1e5),'required':![],'type':a0_0x15a923(0x1e1),'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_0x15a923(0x239),'rule':a0_0x15a923(0x212),'exampleSqlShape':'any\x20(1\x20row\x20×\x201\x20col,\x20N\x20rows\x20×\x20M\x20cols,\x20etc.)','exampleResponse':a0_0x15a923(0x1f0)},{'appliesTo':'widget.queries.<key>\x20with\x20SQL\x20returning\x201\x20row\x20×\x201\x20column','rule':a0_0x15a923(0x222),'exampleSqlShape':'1\x20row\x20×\x201\x20col,\x20output\x20column\x20\x27value\x27','exampleResponse':a0_0x15a923(0x213)},{'appliesTo':'widget.queries.<key>\x20with\x20SQL\x20returning\x201\x20row\x20×\x20multiple\x20columns','rule':a0_0x15a923(0x209),'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_0x15a923(0x1e2),'exampleSqlShape':a0_0x15a923(0x21e),'exampleResponse':'\x22items\x22:\x20[{\x20\x22label\x22:\x20\x22Shoes\x22,\x20\x22value\x22:\x20\x227660\x22\x20},\x20...]'}],COMMON_WIDGET_PATTERNS=[{'id':a0_0x15a923(0x1ed),'name':a0_0x15a923(0x1fc),'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_0x15a923(0x1ff),'queries':{'value':a0_0x15a923(0x205),'trend':'file:query/<path>/trend.sql','items':a0_0x15a923(0x226)}},'sqlShapesPerKey':[{'key':a0_0x15a923(0x203),'shape':a0_0x15a923(0x1ef),'outputColumns':['value'],'collapseRule':a0_0x15a923(0x210)},{'key':'trend','shape':'1\x20row\x20×\x202\x20columns','outputColumns':[a0_0x15a923(0x20d),'pct'],'collapseRule':a0_0x15a923(0x1e8)},{'key':'items','shape':a0_0x15a923(0x223),'outputColumns':[a0_0x15a923(0x1f3),a0_0x15a923(0x203)],'collapseRule':'array\x20of\x20objects'}],'responseShape':{'value':'\x2269700\x22','trend':'{\x20\x22direction\x22:\x20\x22up\x22,\x20\x22pct\x22:\x20\x222.2\x22\x20}','items':a0_0x15a923(0x1e4)},'referenceWidgetId':'expected_earnings','socNotes':a0_0x15a923(0x230)},{'id':'metric_sparkline','name':a0_0x15a923(0x1dd),'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':'file:query/<path>/value.sql','trend':a0_0x15a923(0x229),'points':'file:query/<path>/points.sql'}},'sqlShapesPerKey':[{'key':a0_0x15a923(0x203),'shape':a0_0x15a923(0x1ef),'outputColumns':[a0_0x15a923(0x203)],'collapseRule':a0_0x15a923(0x210)},{'key':'trend','shape':a0_0x15a923(0x1e7),'outputColumns':[a0_0x15a923(0x20d),'pct'],'collapseRule':'object'},{'key':'points','shape':a0_0x15a923(0x223),'outputColumns':[a0_0x15a923(0x1de),'value'],'collapseRule':a0_0x15a923(0x1e0)}],'responseShape':{'value':a0_0x15a923(0x22c),'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_0x15a923(0x218),'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_0x15a923(0x235),'name':'Metric\x20+\x20Progress\x20to\x20Goal','useCase':a0_0x15a923(0x1f6),'payloadShape':{'id':a0_0x15a923(0x1ff),'queries':{'value':'file:query/<path>/current.sql','trend':a0_0x15a923(0x229),'target':a0_0x15a923(0x1f4)}},'sqlShapesPerKey':[{'key':a0_0x15a923(0x203),'shape':a0_0x15a923(0x1ef),'outputColumns':[a0_0x15a923(0x1fd)],'collapseRule':'scalar\x20primitive'},{'key':'trend','shape':a0_0x15a923(0x1e7),'outputColumns':[a0_0x15a923(0x20d),a0_0x15a923(0x1fa)],'collapseRule':a0_0x15a923(0x1e8)},{'key':'target','shape':'1\x20row\x20×\x201\x20column','outputColumns':['target'],'collapseRule':a0_0x15a923(0x210)}],'responseShape':{'value':'\x221836\x22','trend':a0_0x15a923(0x21b),'target':'\x222884\x22'},'referenceWidgetId':a0_0x15a923(0x22a),'socNotes':a0_0x15a923(0x1f8)}],NAMING_CONVENTION={'dashboardName':{'constraint':a0_0x15a923(0x1f9),'minLength':0x6,'maxLength':0x32,'regex':'^dash-[a-zA-Z0-9_-]+$','examples':['dash-sales','dash-inbound',a0_0x15a923(0x233)],'rationale':a0_0x15a923(0x207)}},URL_PATTERN={'method':'POST','path':'/api/{project}/{name}/dashboard','exampleFull':a0_0x15a923(0x20e),'requestBodyShape':{'params':'object\x20—\x20values\x20for\x20declared\x20params\x20(validated\x20against\x20params\x20contract;\x20missing\x20required\x20→\x20400,\x20type\x20mismatch\x20→\x20400)','widgets':a0_0x15a923(0x1f1)},'responseShape':{'envelope':a0_0x15a923(0x232),'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':'file:relative/path/to/query.sql','pathRelativeTo':a0_0x15a923(0x22d),'fileExtensionPolicy':a0_0x15a923(0x200),'resolvedAt':a0_0x15a923(0x1e3),'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_0x15a923(0x1df)},PLACEHOLDER_CONVENTION={'format':':paramName','regex':'(?<!:):([a-zA-Z_][a-zA-Z0-9_]*)','regexNotes':a0_0x15a923(0x20f),'scanScope':'All\x20widget\x20SQL\x20—\x20both\x20\x27query\x27\x20(singular)\x20and\x20every\x20\x27queries.<key>\x27.','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':a0_0x15a923(0x1ea),'fields':[{'name':'enabled','type':a0_0x15a923(0x217),'required':!![],'description':'Toggle\x20cache\x20feature\x20for\x20this\x20dashboard.'},{'name':a0_0x15a923(0x236),'type':'number','required':![],'constraint':'>=\x200\x20(seconds)','default':a0_0x15a923(0x21a),'description':a0_0x15a923(0x21d)},{'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':'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':[a0_0x15a923(0x228),'Table\x20declared\x20in\x20invalidates,\x20but\x20not\x20detected\x20in\x20any\x20widget\x20SQL\x20(typo\x20or\x20dead\x20entry)'],'warningOn':[a0_0x15a923(0x20b)]}},DOCUMENTATION_URL=a0_0x15a923(0x1f2),DASHBOARD_CATALOG={'schemaVersion':a0_0x15a923(0x204),'source':'dashboard-catalog','summary':{'totalAllowedTopLevelFields':PAYLOAD_SHAPE['topLevelAllowed']['length'],'totalForbiddenFrontendFields':FORBIDDEN_FRONTEND_FIELDS[a0_0x15a923(0x237)],'totalParamTypes':ALLOWED_PARAM_TYPES[a0_0x15a923(0x237)],'totalScalarCollapseRules':SCALAR_COLLAPSE_RULES['length'],'totalCommonWidgetPatterns':COMMON_WIDGET_PATTERNS[a0_0x15a923(0x237)]},'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[a0_0x15a923(0x211)]={'DASHBOARD_CATALOG':DASHBOARD_CATALOG};
|
|
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};
|