@carthooks/arcubase-cli 0.1.21 → 0.1.22
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/bundle/arcubase-admin.mjs +1297 -79
- package/bundle/arcubase.mjs +1297 -79
- package/dist/generated/command_registry.generated.d.ts +285 -0
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +395 -0
- package/dist/generated/help_examples.generated.d.ts +77 -0
- package/dist/generated/help_examples.generated.d.ts.map +1 -1
- package/dist/generated/help_examples.generated.js +122 -0
- package/dist/generated/type_index.generated.d.ts +40 -1
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +42 -1
- package/dist/generated/zod_registry.generated.d.ts +29 -1
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +37 -0
- package/dist/runtime/dev_sdk_gen.d.ts +9 -0
- package/dist/runtime/dev_sdk_gen.d.ts.map +1 -0
- package/dist/runtime/dev_sdk_gen.js +319 -0
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +280 -1
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +58 -0
- package/package.json +1 -1
- package/sdk-dist/api/admin/index.ts +1 -0
- package/sdk-dist/api/admin/public-link.ts +48 -0
- package/sdk-dist/api/shared-link/form.ts +42 -1
- package/sdk-dist/api/user/index.ts +0 -1
- package/sdk-dist/docs/runtime-reference/dashboard.md +15 -0
- package/sdk-dist/docs/runtime-reference/dev-sdk-gen.md +41 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/dashboard/update-layout.json +16 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/create.json +33 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/preview-data.json +42 -0
- package/sdk-dist/docs/runtime-reference/widgets.md +40 -0
- package/sdk-dist/generated/command_registry.generated.ts +395 -0
- package/sdk-dist/generated/help_examples.generated.ts +122 -0
- package/sdk-dist/generated/type_index.generated.ts +42 -1
- package/sdk-dist/generated/zod_registry.generated.ts +56 -0
- package/sdk-dist/types/app.ts +37 -1
- package/sdk-dist/types/common.ts +77 -35
- package/sdk-dist/types/index.ts +1 -1
- package/sdk-dist/types/public-link.ts +10 -0
- package/sdk-dist/types/shared-link.ts +16 -1
- package/sdk-dist/types/user-action.ts +1 -43
- package/src/generated/command_registry.generated.ts +395 -0
- package/src/generated/help_examples.generated.ts +122 -0
- package/src/generated/type_index.generated.ts +42 -1
- package/src/generated/zod_registry.generated.ts +56 -0
- package/src/runtime/dev_sdk_gen.ts +360 -0
- package/src/runtime/execute.ts +325 -1
- package/src/runtime/zod_registry.ts +58 -0
- package/src/tests/command_registry.test.ts +20 -2
- package/src/tests/dev_sdk_gen.test.ts +226 -0
- package/src/tests/docs_readability.test.ts +15 -0
- package/src/tests/execute_validation.test.ts +129 -0
- package/src/tests/help.test.ts +70 -0
- package/sdk-dist/api/user/copilot.ts +0 -20
- package/sdk-dist/types/copilot.ts +0 -34
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/runtime/execute.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1D,OAAO,EAAyF,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/runtime/execute.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1D,OAAO,EAAyF,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAShJ,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,YAAY,CAAA;IACnB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAC9B,CAAA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAe1D;AAaD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,MAAM,CAoB7G;AA8BD,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAUlD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,MAAM,CA8HpI;AAurED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,uBAAuB,CActH;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAyOrI"}
|
package/dist/runtime/execute.js
CHANGED
|
@@ -11,9 +11,11 @@ import { resolveArcubaseSDKPath } from './paths.js';
|
|
|
11
11
|
import { compileCSVImportMapping, compileExcelImportMapping } from './entity_import_mapping.js';
|
|
12
12
|
import { uploadLocalFileWithToken } from './local_upload.js';
|
|
13
13
|
import { renderCommandHelpExamples } from './help_examples.js';
|
|
14
|
+
import { generateArcubaseProjectSDK } from './dev_sdk_gen.js';
|
|
14
15
|
export function renderRootHelp(scope) {
|
|
15
16
|
const binary = scope === 'admin' ? 'arcubase-admin' : 'arcubase';
|
|
16
|
-
const
|
|
17
|
+
const modules = scope === 'admin' ? [...listModules(scope), 'dev'].sort() : listModules(scope);
|
|
18
|
+
const items = modules.map((item) => ` - ${item}`);
|
|
17
19
|
return [
|
|
18
20
|
`${binary} <command> [subcommand] [flags]`,
|
|
19
21
|
'',
|
|
@@ -26,6 +28,16 @@ export function renderRootHelp(scope) {
|
|
|
26
28
|
...items,
|
|
27
29
|
].join('\n');
|
|
28
30
|
}
|
|
31
|
+
function renderDevModuleHelp() {
|
|
32
|
+
return [
|
|
33
|
+
'arcubase-admin dev <command> [flags]',
|
|
34
|
+
'',
|
|
35
|
+
'developer commands for local project tooling',
|
|
36
|
+
'',
|
|
37
|
+
'commands:',
|
|
38
|
+
' - sdk-gen',
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
29
41
|
export function renderModuleHelp(scope, moduleName, env = process.env) {
|
|
30
42
|
const items = listModuleCommands(scope, moduleName);
|
|
31
43
|
if (items.length === 0) {
|
|
@@ -226,6 +238,41 @@ function getFixedValue(value) {
|
|
|
226
238
|
const fixedValue = value.fixedValue;
|
|
227
239
|
return typeof fixedValue === 'string' ? fixedValue : undefined;
|
|
228
240
|
}
|
|
241
|
+
function isDevSDKGenCommand(scope, moduleName, commandName) {
|
|
242
|
+
return scope === 'admin' && moduleName === 'dev' && commandName === 'sdk-gen';
|
|
243
|
+
}
|
|
244
|
+
function validateDevSDKGenFlags(flags) {
|
|
245
|
+
const allowed = new Set(['help', 'app-id', 'out']);
|
|
246
|
+
for (const flag of Object.keys(flags)) {
|
|
247
|
+
if (!allowed.has(flag)) {
|
|
248
|
+
throw new CLIError('UNKNOWN_FLAG', `unknown flag --${flag} for dev sdk-gen`, 2, {
|
|
249
|
+
operation: 'dev sdk-gen',
|
|
250
|
+
hint: 'run dev sdk-gen --help for the supported flags',
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function renderDevSDKGenHelp() {
|
|
256
|
+
return [
|
|
257
|
+
'arcubase-admin dev sdk-gen',
|
|
258
|
+
'method: local generator',
|
|
259
|
+
'endpoint: /apps/:app_id',
|
|
260
|
+
'body: none',
|
|
261
|
+
'path flags: --app-id',
|
|
262
|
+
'flags: --out',
|
|
263
|
+
'',
|
|
264
|
+
'generates typed TypeScript source for the current Web project',
|
|
265
|
+
'',
|
|
266
|
+
'example:',
|
|
267
|
+
' - arcubase-admin dev sdk-gen --app-id <app_id> --out src/arcubase',
|
|
268
|
+
'',
|
|
269
|
+
'requirements:',
|
|
270
|
+
' - missing field.key values are initialized through admin auth as field<id>',
|
|
271
|
+
' - existing field.key values must be stable unique TypeScript identifiers',
|
|
272
|
+
' - entity property names use entity.key when available, otherwise a stable entity<id> fallback',
|
|
273
|
+
' - generated code accepts createArcubaseSdk({ baseURL, accessToken, refreshToken })',
|
|
274
|
+
].join('\n');
|
|
275
|
+
}
|
|
229
276
|
function resolveEndpoint(command, flags) {
|
|
230
277
|
let resolved = command.endpoint;
|
|
231
278
|
for (const mapping of command.pathParams) {
|
|
@@ -592,6 +639,12 @@ function isTableSchemaCommand(scope, command) {
|
|
|
592
639
|
function isAccessRuleCommand(scope, command) {
|
|
593
640
|
return scope === 'admin' && command.commandPath[0] === 'access-rule';
|
|
594
641
|
}
|
|
642
|
+
function isWidgetPreviewDataCommand(scope, command) {
|
|
643
|
+
return scope === 'admin' && command.commandPath[0] === 'widget' && command.commandPath[1] === 'preview-data';
|
|
644
|
+
}
|
|
645
|
+
function isWidgetUpdateCommand(scope, command) {
|
|
646
|
+
return scope === 'admin' && command.commandPath[0] === 'widget' && command.commandPath[1] === 'update';
|
|
647
|
+
}
|
|
595
648
|
function isAccessRuleUpdateCommand(scope, command) {
|
|
596
649
|
return scope === 'admin' && command.commandPath[0] === 'access-rule' && command.commandPath[1] === 'update';
|
|
597
650
|
}
|
|
@@ -888,6 +941,45 @@ function buildBodyGuidance(requestType) {
|
|
|
888
941
|
],
|
|
889
942
|
};
|
|
890
943
|
}
|
|
944
|
+
if (requestType === 'AppPreviewWidgetsDataReqVO') {
|
|
945
|
+
return {
|
|
946
|
+
shapeHint: {
|
|
947
|
+
dataSource: 2188891001,
|
|
948
|
+
dataSourceType: 'entity',
|
|
949
|
+
dimensions: [{ IsField: true, FieldID: 1004, Name: 'Source' }],
|
|
950
|
+
measures: [{ FieldID: 1005, Aggregate: 'sum', Name: 'Total Amount' }],
|
|
951
|
+
},
|
|
952
|
+
commonMistakes: [
|
|
953
|
+
'widget preview-data body is the chart query itself',
|
|
954
|
+
'do not send a saved widget object with id, entity_id, or options',
|
|
955
|
+
'field dimensions with FieldID must set IsField:true',
|
|
956
|
+
'unsupported aggregates are invalid; use sum, avg, max, min, count, or distinct_count',
|
|
957
|
+
],
|
|
958
|
+
suggestions: [
|
|
959
|
+
'retry with: arcubase-admin widget preview-data --app-id <app_id> --body-json \'{"dataSource":<table_id>,"dataSourceType":"entity","dimensions":[{"IsField":true,"FieldID":<field_id>,"Name":"Source"}],"measures":[{"FieldID":<number_field_id>,"Aggregate":"sum","Name":"Total Amount"}]}\'',
|
|
960
|
+
],
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
if (requestType === 'AppUpdateWidgetsReqVO') {
|
|
964
|
+
return {
|
|
965
|
+
shapeHint: {
|
|
966
|
+
entity_id: 2188891001,
|
|
967
|
+
options: {
|
|
968
|
+
charts: {
|
|
969
|
+
entity_id: 2188891001,
|
|
970
|
+
dimensions: [{ IsField: true, FieldID: 1004, Name: 'Source' }],
|
|
971
|
+
measures: [{ FieldID: 1005, Aggregate: 'sum', Name: 'Total Amount' }],
|
|
972
|
+
type: 'bar',
|
|
973
|
+
},
|
|
974
|
+
},
|
|
975
|
+
},
|
|
976
|
+
commonMistakes: [
|
|
977
|
+
'dashboard chart config belongs in body.options.charts',
|
|
978
|
+
'field dimensions with FieldID must set IsField:true',
|
|
979
|
+
'verify persistence with widget get or widget list after update',
|
|
980
|
+
],
|
|
981
|
+
};
|
|
982
|
+
}
|
|
891
983
|
return {};
|
|
892
984
|
}
|
|
893
985
|
function buildBodyValidationDetails(scope, command, requestType, issues, env = process.env) {
|
|
@@ -1038,6 +1130,18 @@ function requireUpdateContains(scope, command, body, field, env) {
|
|
|
1038
1130
|
throwBodyValidationFailure(`body.update must include "${field}" when body.${field} is present. Example: {"update":["${field}"],"${field}":${field === 'enabled' ? 'true' : '"value"'}}`, command.requestType ?? 'AppIngressUpdateReqVO', 'body.update', scope, command, env);
|
|
1039
1131
|
}
|
|
1040
1132
|
}
|
|
1133
|
+
function validateChartFieldDimensions(scope, command, requestType, dimensions, pathPrefix, env) {
|
|
1134
|
+
if (!Array.isArray(dimensions))
|
|
1135
|
+
return;
|
|
1136
|
+
for (const [index, dimension] of dimensions.entries()) {
|
|
1137
|
+
if (!isRecord(dimension) || typeof dimension.FieldID !== 'number') {
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (dimension.IsField !== true) {
|
|
1141
|
+
throwBodyValidationFailure('widget chart field dimensions require IsField:true when FieldID is present; otherwise grouped statistics collapse into total rows', requestType, `${pathPrefix}.${index}.IsField`, scope, command, env);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1041
1145
|
function validateActionSpecificRawBody(scope, command, body, env) {
|
|
1042
1146
|
if (!isRecord(body) || !command.requestType) {
|
|
1043
1147
|
return;
|
|
@@ -1093,6 +1197,14 @@ function validateActionSpecificRawBody(scope, command, body, env) {
|
|
|
1093
1197
|
validateAccessRuleUserScope(scope, command, command.requestType, options?.user_scope, env);
|
|
1094
1198
|
validateAccessRulePolicyShape(scope, command, command.requestType, policy, env, 'body.options.policy');
|
|
1095
1199
|
}
|
|
1200
|
+
if (isWidgetPreviewDataCommand(scope, command)) {
|
|
1201
|
+
validateChartFieldDimensions(scope, command, command.requestType, body.dimensions, 'body.dimensions', env);
|
|
1202
|
+
}
|
|
1203
|
+
if (isWidgetUpdateCommand(scope, command)) {
|
|
1204
|
+
const options = isRecord(body.options) ? body.options : undefined;
|
|
1205
|
+
const charts = options && isRecord(options.charts) ? options.charts : undefined;
|
|
1206
|
+
validateChartFieldDimensions(scope, command, command.requestType, charts?.dimensions, 'body.options.charts.dimensions', env);
|
|
1207
|
+
}
|
|
1096
1208
|
}
|
|
1097
1209
|
function validateActionSpecificBody(scope, command, flags, body, env) {
|
|
1098
1210
|
if (!isRecord(body) || !command.requestType) {
|
|
@@ -1443,6 +1555,163 @@ async function requestJSON(runtimeEnv, method, endpoint, body, fetchImpl) {
|
|
|
1443
1555
|
}
|
|
1444
1556
|
return { status: response.status, data: parsedResponse };
|
|
1445
1557
|
}
|
|
1558
|
+
function unwrapResponseData(payload) {
|
|
1559
|
+
return isRecord(payload) && 'data' in payload ? payload.data : payload;
|
|
1560
|
+
}
|
|
1561
|
+
function extractSDKGenAppPayload(payload) {
|
|
1562
|
+
const appPayload = unwrapResponseData(payload);
|
|
1563
|
+
if (!isRecord(appPayload)) {
|
|
1564
|
+
throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected app detail response data', 2);
|
|
1565
|
+
}
|
|
1566
|
+
return appPayload;
|
|
1567
|
+
}
|
|
1568
|
+
function extractSDKGenEntityRefs(appPayload) {
|
|
1569
|
+
const sourceEntities = Array.isArray(appPayload.entities)
|
|
1570
|
+
? appPayload.entities
|
|
1571
|
+
: Array.isArray(appPayload.tables)
|
|
1572
|
+
? appPayload.tables
|
|
1573
|
+
: Array.isArray(appPayload.entitys)
|
|
1574
|
+
? appPayload.entitys
|
|
1575
|
+
: [];
|
|
1576
|
+
const refs = sourceEntities
|
|
1577
|
+
.filter((item) => isRecord(item))
|
|
1578
|
+
.map((item) => item.id)
|
|
1579
|
+
.filter((id) => typeof id === 'string' || typeof id === 'number')
|
|
1580
|
+
.map((id) => ({ id: String(id) }));
|
|
1581
|
+
return refs;
|
|
1582
|
+
}
|
|
1583
|
+
function resolveSDKGenAppId(flagAppId, appPayload) {
|
|
1584
|
+
const fromPayload = typeof appPayload.id === 'number' ? appPayload.id : typeof appPayload.app_id === 'number' ? appPayload.app_id : undefined;
|
|
1585
|
+
const fromFlag = Number(flagAppId);
|
|
1586
|
+
if (Number.isFinite(fromFlag))
|
|
1587
|
+
return fromFlag;
|
|
1588
|
+
if (fromPayload)
|
|
1589
|
+
return fromPayload;
|
|
1590
|
+
throw new CLIError('SDK_GEN_APP_ID_REQUIRED', 'sdk-gen expected numeric app id', 2);
|
|
1591
|
+
}
|
|
1592
|
+
function walkSDKGenFields(fields, visit) {
|
|
1593
|
+
if (!Array.isArray(fields)) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
for (const field of fields) {
|
|
1597
|
+
if (!isRecord(field)) {
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
visit(field);
|
|
1601
|
+
walkSDKGenFields(field.children, visit);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function collectExistingSDKGenFieldKeys(entity) {
|
|
1605
|
+
const keys = new Set();
|
|
1606
|
+
walkSDKGenFields(entity.fields, (field) => {
|
|
1607
|
+
if (typeof field.key === 'string' && field.key.trim() !== '') {
|
|
1608
|
+
keys.add(field.key.trim());
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
return keys;
|
|
1612
|
+
}
|
|
1613
|
+
function nextSDKGenFallbackFieldKey(fieldId, usedKeys) {
|
|
1614
|
+
const base = `field${fieldId}`;
|
|
1615
|
+
if (!usedKeys.has(base)) {
|
|
1616
|
+
usedKeys.add(base);
|
|
1617
|
+
return base;
|
|
1618
|
+
}
|
|
1619
|
+
let index = 2;
|
|
1620
|
+
while (usedKeys.has(`${base}_${index}`)) {
|
|
1621
|
+
index++;
|
|
1622
|
+
}
|
|
1623
|
+
const key = `${base}_${index}`;
|
|
1624
|
+
usedKeys.add(key);
|
|
1625
|
+
return key;
|
|
1626
|
+
}
|
|
1627
|
+
function initializeMissingSDKGenFieldKeys(entity) {
|
|
1628
|
+
const usedKeys = collectExistingSDKGenFieldKeys(entity);
|
|
1629
|
+
const updates = [];
|
|
1630
|
+
walkSDKGenFields(entity.fields, (field) => {
|
|
1631
|
+
if (typeof field.id !== 'number') {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (typeof field.key === 'string' && field.key.trim() !== '') {
|
|
1635
|
+
field.key = field.key.trim();
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
const key = nextSDKGenFallbackFieldKey(field.id, usedKeys);
|
|
1639
|
+
field.key = key;
|
|
1640
|
+
updates.push({ id: field.id, key });
|
|
1641
|
+
});
|
|
1642
|
+
return updates;
|
|
1643
|
+
}
|
|
1644
|
+
async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
|
|
1645
|
+
validateDevSDKGenFlags(flags);
|
|
1646
|
+
const appId = flagValue(flags, 'app-id');
|
|
1647
|
+
const outDir = flagValue(flags, 'out');
|
|
1648
|
+
if (!appId) {
|
|
1649
|
+
throw new CLIError('MISSING_PATH_FLAG', 'missing required flag --app-id', 2);
|
|
1650
|
+
}
|
|
1651
|
+
if (!outDir) {
|
|
1652
|
+
throw new CLIError('MISSING_FLAG', 'missing required flag --out', 2, {
|
|
1653
|
+
operation: 'dev sdk-gen',
|
|
1654
|
+
hint: 'retry with --out src/arcubase',
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
const endpoint = `/api/apps/${encodeURIComponent(appId)}`;
|
|
1658
|
+
const appDetail = await requestJSON(runtimeEnv, 'GET', endpoint, undefined, fetchImpl);
|
|
1659
|
+
const appPayload = extractSDKGenAppPayload(appDetail.data);
|
|
1660
|
+
let entityRefs = extractSDKGenEntityRefs(appPayload);
|
|
1661
|
+
if (entityRefs.length === 0) {
|
|
1662
|
+
const entitiesEndpoint = `/api/apps/${encodeURIComponent(appId)}/entities`;
|
|
1663
|
+
const entityList = await requestJSON(runtimeEnv, 'GET', entitiesEndpoint, undefined, fetchImpl);
|
|
1664
|
+
const entityListPayload = unwrapResponseData(entityList.data);
|
|
1665
|
+
entityRefs = extractSDKGenEntityRefs({ entities: entityListPayload });
|
|
1666
|
+
}
|
|
1667
|
+
if (entityRefs.length === 0) {
|
|
1668
|
+
throw new CLIError('SDK_GEN_NO_ENTITIES', 'sdk-gen could not find entities in app detail response', 2);
|
|
1669
|
+
}
|
|
1670
|
+
const entities = [];
|
|
1671
|
+
const initializedFieldKeys = [];
|
|
1672
|
+
for (const entityRef of entityRefs) {
|
|
1673
|
+
const entityEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}`;
|
|
1674
|
+
const entityDetail = await requestJSON(runtimeEnv, 'GET', entityEndpoint, undefined, fetchImpl);
|
|
1675
|
+
const entityPayload = unwrapResponseData(entityDetail.data);
|
|
1676
|
+
if (!isRecord(entityPayload)) {
|
|
1677
|
+
throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected entity detail response data', 2);
|
|
1678
|
+
}
|
|
1679
|
+
const customKeyUpdates = initializeMissingSDKGenFieldKeys(entityPayload);
|
|
1680
|
+
if (customKeyUpdates.length > 0) {
|
|
1681
|
+
const customKeysEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/custom-keys`;
|
|
1682
|
+
await requestJSON(runtimeEnv, 'PUT', customKeysEndpoint, customKeyUpdates, fetchImpl);
|
|
1683
|
+
initializedFieldKeys.push({
|
|
1684
|
+
entityId: typeof entityPayload.id === 'number' ? entityPayload.id : Number(entityRef.id),
|
|
1685
|
+
fields: customKeyUpdates.map((item) => item.key),
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
entities.push(entityPayload);
|
|
1689
|
+
}
|
|
1690
|
+
const generated = generateArcubaseProjectSDK({
|
|
1691
|
+
id: resolveSDKGenAppId(appId, appPayload),
|
|
1692
|
+
name: typeof appPayload.name === 'string' ? appPayload.name : undefined,
|
|
1693
|
+
entities,
|
|
1694
|
+
});
|
|
1695
|
+
const absoluteOutDir = path.resolve(process.cwd(), outDir);
|
|
1696
|
+
for (const file of generated.files) {
|
|
1697
|
+
const targetPath = path.resolve(absoluteOutDir, file.path);
|
|
1698
|
+
if (!targetPath.startsWith(`${absoluteOutDir}${path.sep}`)) {
|
|
1699
|
+
throw new CLIError('SDK_GEN_INVALID_OUTPUT_PATH', `invalid generated file path: ${file.path}`, 2);
|
|
1700
|
+
}
|
|
1701
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
1702
|
+
fs.writeFileSync(targetPath, file.contents);
|
|
1703
|
+
}
|
|
1704
|
+
return {
|
|
1705
|
+
kind: 'result',
|
|
1706
|
+
commandPath: ['dev', 'sdk-gen'],
|
|
1707
|
+
status: appDetail.status,
|
|
1708
|
+
data: {
|
|
1709
|
+
out: absoluteOutDir,
|
|
1710
|
+
files: generated.files.map((file) => file.path),
|
|
1711
|
+
initializedFieldKeys,
|
|
1712
|
+
},
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1446
1715
|
function requiredStringFlag(flags, flag) {
|
|
1447
1716
|
const value = flagValue(flags, flag);
|
|
1448
1717
|
if (!value) {
|
|
@@ -1772,6 +2041,16 @@ export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
|
|
|
1772
2041
|
}
|
|
1773
2042
|
return { kind: 'help', text: renderRootHelp(scope) };
|
|
1774
2043
|
}
|
|
2044
|
+
if (isDevSDKGenCommand(scope, moduleName, commandName)) {
|
|
2045
|
+
if (hasFlag(parsed.flags, 'help')) {
|
|
2046
|
+
return { kind: 'help', text: renderDevSDKGenHelp() };
|
|
2047
|
+
}
|
|
2048
|
+
const runtimeEnv = env ?? loadRuntimeEnv(scope);
|
|
2049
|
+
return executeDevSDKGen(runtimeEnv, parsed.flags, fetchImpl);
|
|
2050
|
+
}
|
|
2051
|
+
if (scope === 'admin' && moduleName === 'dev' && !commandName) {
|
|
2052
|
+
return { kind: 'help', text: renderDevModuleHelp() };
|
|
2053
|
+
}
|
|
1775
2054
|
const singleTokenCommand = !commandName ? findCommand(scope, moduleName) : null;
|
|
1776
2055
|
if (!commandName && !singleTokenCommand) {
|
|
1777
2056
|
if (hasFlag(parsed.flags, 'help-examples')) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod_registry.d.ts","sourceRoot":"","sources":["../../src/runtime/zod_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AA8BrC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQ9D;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"zod_registry.d.ts","sourceRoot":"","sources":["../../src/runtime/zod_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AA8BrC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQ9D;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAiJlD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,eAAe,EAAE,CAqB7F;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,cAAc,EAAE,CAI3F;AAkGD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,cAAc,EAAE,CAInG"}
|
|
@@ -60,11 +60,31 @@ const conditionDocHint = {
|
|
|
60
60
|
title: 'Condition',
|
|
61
61
|
file: 'docs/runtime-reference/condition.md',
|
|
62
62
|
};
|
|
63
|
+
const dashboardDocHint = {
|
|
64
|
+
title: 'Dashboard',
|
|
65
|
+
file: 'docs/runtime-reference/dashboard.md',
|
|
66
|
+
};
|
|
67
|
+
const widgetsDocHint = {
|
|
68
|
+
title: 'Widgets',
|
|
69
|
+
file: 'docs/runtime-reference/widgets.md',
|
|
70
|
+
};
|
|
63
71
|
const docHintIndex = {
|
|
64
72
|
AppCreateByTenantsReqVO: [
|
|
65
73
|
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
66
74
|
examplesIndexDocHint,
|
|
67
75
|
],
|
|
76
|
+
AppNewWidgetsReqVO: [
|
|
77
|
+
widgetsDocHint,
|
|
78
|
+
],
|
|
79
|
+
AppPreviewWidgetsDataReqVO: [
|
|
80
|
+
widgetsDocHint,
|
|
81
|
+
],
|
|
82
|
+
AppUpdateWidgetsIngressOptionsReqVO: [
|
|
83
|
+
widgetsDocHint,
|
|
84
|
+
],
|
|
85
|
+
AppUpdateWidgetsReqVO: [
|
|
86
|
+
widgetsDocHint,
|
|
87
|
+
],
|
|
68
88
|
AppCreateEntityReqVO: [
|
|
69
89
|
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
70
90
|
{ title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
|
|
@@ -83,6 +103,19 @@ const docHintIndex = {
|
|
|
83
103
|
{ title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
|
|
84
104
|
examplesIndexDocHint,
|
|
85
105
|
],
|
|
106
|
+
DashboardCreateReqVO: [
|
|
107
|
+
dashboardDocHint,
|
|
108
|
+
],
|
|
109
|
+
DashboardUpdateLayoutReqVO: [
|
|
110
|
+
dashboardDocHint,
|
|
111
|
+
widgetsDocHint,
|
|
112
|
+
],
|
|
113
|
+
DashboardUpdateOptionsReqVO: [
|
|
114
|
+
dashboardDocHint,
|
|
115
|
+
],
|
|
116
|
+
DashboardUpdateTitleReqVO: [
|
|
117
|
+
dashboardDocHint,
|
|
118
|
+
],
|
|
86
119
|
EntitySaveReqVO: [
|
|
87
120
|
{ title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
|
|
88
121
|
{ title: 'Field type index', file: 'docs/runtime-reference/entity-schema/README.md' },
|
|
@@ -122,6 +155,9 @@ const docHintIndex = {
|
|
|
122
155
|
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
123
156
|
examplesIndexDocHint,
|
|
124
157
|
],
|
|
158
|
+
FetchWidgetsDataReqVO: [
|
|
159
|
+
widgetsDocHint,
|
|
160
|
+
],
|
|
125
161
|
'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
|
|
126
162
|
selectionDocHint,
|
|
127
163
|
conditionDocHint,
|
|
@@ -178,6 +214,21 @@ const commandDocHintIndex = {
|
|
|
178
214
|
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
179
215
|
examplesIndexDocHint,
|
|
180
216
|
],
|
|
217
|
+
'admin.dashboard.getDashboardList': [
|
|
218
|
+
dashboardDocHint,
|
|
219
|
+
],
|
|
220
|
+
'admin.dashboard.getDashboardOptions': [
|
|
221
|
+
dashboardDocHint,
|
|
222
|
+
],
|
|
223
|
+
'admin.widget.widgetsList': [
|
|
224
|
+
widgetsDocHint,
|
|
225
|
+
],
|
|
226
|
+
'admin.widget.getWidgets': [
|
|
227
|
+
widgetsDocHint,
|
|
228
|
+
],
|
|
229
|
+
'admin.widget.deleteWidgets': [
|
|
230
|
+
widgetsDocHint,
|
|
231
|
+
],
|
|
181
232
|
'admin.app.createEntity': [
|
|
182
233
|
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
183
234
|
examplesIndexDocHint,
|
|
@@ -245,6 +296,13 @@ const commandDocHintIndex = {
|
|
|
245
296
|
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
246
297
|
examplesIndexDocHint,
|
|
247
298
|
],
|
|
299
|
+
'user.dashboard.getDashboard': [
|
|
300
|
+
dashboardDocHint,
|
|
301
|
+
],
|
|
302
|
+
'user.dashboard.getDashboardWidgets': [
|
|
303
|
+
dashboardDocHint,
|
|
304
|
+
widgetsDocHint,
|
|
305
|
+
],
|
|
248
306
|
};
|
|
249
307
|
export function getCommandDocHints(operation, env = process.env) {
|
|
250
308
|
const key = String(operation || '').trim();
|
package/package.json
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getDefaultClient } from '../../factory'
|
|
2
|
+
import type {
|
|
3
|
+
EntityPublicLinkType,
|
|
4
|
+
PublicLinkConfigItemVO,
|
|
5
|
+
PublicLinkConfigReqVO,
|
|
6
|
+
PublicLinkConfigRespVO,
|
|
7
|
+
PublicRecordLinkRespVO
|
|
8
|
+
} from '../../types/public-link'
|
|
9
|
+
|
|
10
|
+
// @endpoint GET /api/apps/:app_id/entity/:entity_id/public-links
|
|
11
|
+
// @controller PublicLink.ConfigList
|
|
12
|
+
export async function getPublicLinkConfigs(
|
|
13
|
+
appId: string,
|
|
14
|
+
entityId: string
|
|
15
|
+
): Promise<PublicLinkConfigRespVO> {
|
|
16
|
+
return getDefaultClient().get(`/apps/${appId}/entity/${entityId}/public-links`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// @endpoint PUT /api/apps/:app_id/entity/:entity_id/public-links/:type
|
|
20
|
+
// @controller PublicLink.ConfigSave
|
|
21
|
+
export async function savePublicLinkConfig(
|
|
22
|
+
appId: string,
|
|
23
|
+
entityId: string,
|
|
24
|
+
type: EntityPublicLinkType,
|
|
25
|
+
data: PublicLinkConfigReqVO
|
|
26
|
+
): Promise<PublicLinkConfigItemVO> {
|
|
27
|
+
return getDefaultClient().put(`/apps/${appId}/entity/${entityId}/public-links/${type}`, data)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// @endpoint POST /api/apps/:app_id/entity/:entity_id/public-links/:type/regenerate
|
|
31
|
+
// @controller PublicLink.ConfigRegenerate
|
|
32
|
+
export async function regeneratePublicLinkConfig(
|
|
33
|
+
appId: string,
|
|
34
|
+
entityId: string,
|
|
35
|
+
type: EntityPublicLinkType
|
|
36
|
+
): Promise<PublicLinkConfigItemVO> {
|
|
37
|
+
return getDefaultClient().post(`/apps/${appId}/entity/${entityId}/public-links/${type}/regenerate`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// @endpoint POST /api/apps/:app_id/entity/:entity_id/public-links/record/:row_id
|
|
41
|
+
// @controller PublicLink.RecordLinkCreate
|
|
42
|
+
export async function createPublicRecordLink(
|
|
43
|
+
appId: string,
|
|
44
|
+
entityId: string,
|
|
45
|
+
rowId: number | string
|
|
46
|
+
): Promise<PublicRecordLinkRespVO> {
|
|
47
|
+
return getDefaultClient().post(`/apps/${appId}/entity/${entityId}/public-links/record/${rowId}`)
|
|
48
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { getDefaultClient } from '../../factory'
|
|
2
2
|
import type {
|
|
3
3
|
SharedLinkInitRespVO,
|
|
4
|
+
SharedLinkPublicQueryReqVO,
|
|
5
|
+
SharedLinkPublicQueryRespVO,
|
|
4
6
|
SharedLinkPushMobileUploadReqVO,
|
|
5
7
|
SharedLinkPushMobileUploadRespVO,
|
|
6
8
|
SharedLinkSubmissionSubmitReqVO,
|
|
7
9
|
SharedLinkSubmissionSubmitRespVO
|
|
8
|
-
} from '../../types
|
|
10
|
+
} from '../../types'
|
|
9
11
|
import type {
|
|
10
12
|
EntityValidateUniqReqVO,
|
|
11
13
|
EntityValidateUniqRespVO,
|
|
12
14
|
EntityQueryRelationReqVO,
|
|
13
15
|
EntityQueryRelationRespVO,
|
|
16
|
+
EntityQueryLinkToValuesReqVO,
|
|
14
17
|
GetUploadTokenRespVO,
|
|
15
18
|
UploadPreviewReqVO,
|
|
16
19
|
UploadPreviewRespVO,
|
|
@@ -31,6 +34,36 @@ export async function querySharedLinkEntityRelation(
|
|
|
31
34
|
return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-rel`, data)
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
// @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-linkto-pick
|
|
38
|
+
// @controller SharedLink.EntityQueryLinkToPick
|
|
39
|
+
export async function querySharedLinkEntityLinkToPick(
|
|
40
|
+
appId: string,
|
|
41
|
+
entityId: string,
|
|
42
|
+
data: EntityQueryRelationReqVO
|
|
43
|
+
): Promise<EntityQueryRelationRespVO> {
|
|
44
|
+
return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-linkto-pick`, data)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-linkto-values
|
|
48
|
+
// @controller SharedLink.EntityQueryLinkToValues
|
|
49
|
+
export async function querySharedLinkEntityLinkToValues(
|
|
50
|
+
appId: string,
|
|
51
|
+
entityId: string,
|
|
52
|
+
data: EntityQueryLinkToValuesReqVO
|
|
53
|
+
): Promise<EntityQueryRelationRespVO> {
|
|
54
|
+
return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-linkto-values`, data)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-subform-rel
|
|
58
|
+
// @controller SharedLink.EntityQuerySubformRelation
|
|
59
|
+
export async function querySharedLinkEntitySubformRelation(
|
|
60
|
+
appId: string,
|
|
61
|
+
entityId: string,
|
|
62
|
+
data: any
|
|
63
|
+
): Promise<any> {
|
|
64
|
+
return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-subform-rel`, data)
|
|
65
|
+
}
|
|
66
|
+
|
|
34
67
|
// @endpoint GET /link/api/form/dataset/:id/:node_id
|
|
35
68
|
// @controller SharedLink.GetDataset
|
|
36
69
|
export async function getSharedLinkDataset(
|
|
@@ -52,6 +85,14 @@ export async function initSharedLink(): Promise<SharedLinkInitRespVO> {
|
|
|
52
85
|
return getDefaultClient().post('/init')
|
|
53
86
|
}
|
|
54
87
|
|
|
88
|
+
// @endpoint POST /link/api/query
|
|
89
|
+
// @controller SharedLink.PublicQuery
|
|
90
|
+
export async function querySharedLinkPublic(
|
|
91
|
+
data: SharedLinkPublicQueryReqVO
|
|
92
|
+
): Promise<SharedLinkPublicQueryRespVO> {
|
|
93
|
+
return getDefaultClient().post('/query', data)
|
|
94
|
+
}
|
|
95
|
+
|
|
55
96
|
// @endpoint POST /link/api/form/upload/mobile-upload
|
|
56
97
|
// @controller SharedLink.MobileUpload
|
|
57
98
|
export async function uploadSharedLinkMobile(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Dashboard
|
|
2
|
+
|
|
3
|
+
Use `arcubase dashboard get` and `arcubase dashboard widgets` to read dashboards as a tenant user.
|
|
4
|
+
|
|
5
|
+
Use `arcubase-admin dashboard list`, `arcubase-admin dashboard create`, `arcubase-admin dashboard get-options`, `arcubase-admin dashboard update-options`, `arcubase-admin dashboard update-layout`, and `arcubase-admin dashboard rename` to manage dashboards as an admin.
|
|
6
|
+
|
|
7
|
+
Dashboard title updates use body `{"name":"..."}`.
|
|
8
|
+
|
|
9
|
+
Dashboard layout updates use a top-level JSON array of layout positions, not an object wrapper. Example:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
[{"id":2188892001,"x":0,"y":0,"w":12,"h":6}]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Dashboard delete and clone are not exposed in the CLI surface until backend routes are registered.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Dev SDK Gen
|
|
2
|
+
|
|
3
|
+
Use `arcubase-admin dev sdk-gen` to generate typed TypeScript source for a Web project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
arcubase-admin dev sdk-gen \
|
|
7
|
+
--app-id <app_id> \
|
|
8
|
+
--out src/arcubase
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The command reads the app schema from Arcubase through the normal `arcubase-admin` runtime auth and writes project-local source files under `--out`.
|
|
12
|
+
|
|
13
|
+
Generated code is key based. Application code uses stable `field.key` names, while generated runtime code converts those keys to internal numeric field ids.
|
|
14
|
+
If a field is missing `field.key`, the command initializes it through `PUT /api/apps/:app_id/entity/:entity_id/custom-keys` with a stable `field<id>` fallback before generation.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createArcubaseSdk } from './arcubase'
|
|
18
|
+
|
|
19
|
+
const sdk = createArcubaseSdk({
|
|
20
|
+
baseURL: 'https://team.example.arcubase.co/api',
|
|
21
|
+
accessToken,
|
|
22
|
+
refreshToken,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
await sdk.story.create({
|
|
26
|
+
story: 'Once upon a time',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
await sdk.story.update(rowId, {
|
|
30
|
+
story: 'Updated story',
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Rules:
|
|
35
|
+
|
|
36
|
+
- missing `field.key` values are initialized through admin auth as `field<id>`
|
|
37
|
+
- existing `field.key` values must be stable unique TypeScript identifiers
|
|
38
|
+
- `field.key` is a developer API contract and should not track display labels
|
|
39
|
+
- entity property names use `entity.key` when available, otherwise a stable `entity<id>` fallback
|
|
40
|
+
- row create/update/query code should use generated types, not raw field ids
|
|
41
|
+
- rerun the command after schema changes and commit the regenerated source with the Web project
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scope": "admin",
|
|
3
|
+
"command": ["dashboard", "update-layout"],
|
|
4
|
+
"examples": [
|
|
5
|
+
{
|
|
6
|
+
"title": "place one dashboard widget in the top-left area",
|
|
7
|
+
"body": [
|
|
8
|
+
{"id": 2188892001, "x": 0, "y": 0, "w": 12, "h": 6}
|
|
9
|
+
],
|
|
10
|
+
"notes": [
|
|
11
|
+
"the request body is a top-level array",
|
|
12
|
+
"do not wrap the array as {\"layout\":[...]}"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|