@carthooks/arcubase-cli 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/bundle/arcubase-admin.mjs +907 -10
  2. package/bundle/arcubase.mjs +907 -10
  3. package/dist/runtime/command_registry.d.ts +8 -0
  4. package/dist/runtime/command_registry.d.ts.map +1 -1
  5. package/dist/runtime/command_registry.js +35 -0
  6. package/dist/runtime/entity_save_schema.d.ts +15 -0
  7. package/dist/runtime/entity_save_schema.d.ts.map +1 -0
  8. package/dist/runtime/entity_save_schema.js +641 -0
  9. package/dist/runtime/errors.d.ts +10 -0
  10. package/dist/runtime/errors.d.ts.map +1 -1
  11. package/dist/runtime/execute.d.ts.map +1 -1
  12. package/dist/runtime/execute.js +89 -10
  13. package/dist/runtime/zod_registry.d.ts +6 -0
  14. package/dist/runtime/zod_registry.d.ts.map +1 -1
  15. package/dist/runtime/zod_registry.js +133 -0
  16. package/dist/tests/command_registry.test.js +5 -0
  17. package/dist/tests/entity_save_schema.test.d.ts +2 -0
  18. package/dist/tests/entity_save_schema.test.d.ts.map +1 -0
  19. package/dist/tests/entity_save_schema.test.js +221 -0
  20. package/dist/tests/execute_validation.test.js +450 -0
  21. package/dist/tests/help.test.js +33 -0
  22. package/package.json +1 -1
  23. package/sdk-dist/docs/runtime-reference/README.md +177 -0
  24. package/sdk-dist/docs/runtime-reference/entity-schema/README.md +120 -0
  25. package/sdk-dist/docs/runtime-reference/entity-schema/address.md +34 -0
  26. package/sdk-dist/docs/runtime-reference/entity-schema/boolean.md +37 -0
  27. package/sdk-dist/docs/runtime-reference/entity-schema/button.md +47 -0
  28. package/sdk-dist/docs/runtime-reference/entity-schema/checkbox.md +61 -0
  29. package/sdk-dist/docs/runtime-reference/entity-schema/datasets.md +35 -0
  30. package/sdk-dist/docs/runtime-reference/entity-schema/datetime.md +40 -0
  31. package/sdk-dist/docs/runtime-reference/entity-schema/department.md +33 -0
  32. package/sdk-dist/docs/runtime-reference/entity-schema/departments.md +34 -0
  33. package/sdk-dist/docs/runtime-reference/entity-schema/divider.md +39 -0
  34. package/sdk-dist/docs/runtime-reference/entity-schema/file.md +44 -0
  35. package/sdk-dist/docs/runtime-reference/entity-schema/formula.md +44 -0
  36. package/sdk-dist/docs/runtime-reference/entity-schema/image.md +43 -0
  37. package/sdk-dist/docs/runtime-reference/entity-schema/linkto.md +67 -0
  38. package/sdk-dist/docs/runtime-reference/entity-schema/location.md +32 -0
  39. package/sdk-dist/docs/runtime-reference/entity-schema/lookup.md +41 -0
  40. package/sdk-dist/docs/runtime-reference/entity-schema/member.md +37 -0
  41. package/sdk-dist/docs/runtime-reference/entity-schema/members.md +31 -0
  42. package/sdk-dist/docs/runtime-reference/entity-schema/number.md +70 -0
  43. package/sdk-dist/docs/runtime-reference/entity-schema/query.md +50 -0
  44. package/sdk-dist/docs/runtime-reference/entity-schema/radio.md +53 -0
  45. package/sdk-dist/docs/runtime-reference/entity-schema/relation.md +55 -0
  46. package/sdk-dist/docs/runtime-reference/entity-schema/relationfield.md +38 -0
  47. package/sdk-dist/docs/runtime-reference/entity-schema/rollup.md +54 -0
  48. package/sdk-dist/docs/runtime-reference/entity-schema/select.md +63 -0
  49. package/sdk-dist/docs/runtime-reference/entity-schema/selects.md +51 -0
  50. package/sdk-dist/docs/runtime-reference/entity-schema/serialnumber.md +48 -0
  51. package/sdk-dist/docs/runtime-reference/entity-schema/signature.md +31 -0
  52. package/sdk-dist/docs/runtime-reference/entity-schema/status.md +70 -0
  53. package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +74 -0
  54. package/sdk-dist/docs/runtime-reference/entity-schema/tags.md +19 -0
  55. package/sdk-dist/docs/runtime-reference/entity-schema/text.md +76 -0
  56. package/sdk-dist/docs/runtime-reference/entity-schema/textarea.md +47 -0
  57. package/sdk-dist/docs/runtime-reference/entity-schema/workflow_status.md +17 -0
  58. package/sdk-dist/docs/runtime-reference/entity-schema.md +197 -0
  59. package/sdk-dist/docs/runtime-reference/examples/README.md +51 -0
  60. package/sdk-dist/docs/runtime-reference/examples/crm-01/README.md +20 -0
  61. package/sdk-dist/docs/runtime-reference/examples/mes-01/README.md +21 -0
  62. package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +164 -0
  63. package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +79 -0
  64. package/sdk-dist/docs/runtime-reference/examples/oms-01/create-app.json +3 -0
  65. package/sdk-dist/docs/runtime-reference/examples/oms-01/goods-receipt.create-entity.json +3 -0
  66. package/sdk-dist/docs/runtime-reference/examples/oms-01/goods-receipt.schema.json +159 -0
  67. package/sdk-dist/docs/runtime-reference/examples/oms-01/item-master.create-entity.json +3 -0
  68. package/sdk-dist/docs/runtime-reference/examples/oms-01/item-master.schema.json +179 -0
  69. package/sdk-dist/docs/runtime-reference/examples/oms-01/picking-list.create-entity.json +3 -0
  70. package/sdk-dist/docs/runtime-reference/examples/oms-01/picking-list.schema.json +168 -0
  71. package/sdk-dist/docs/runtime-reference/examples/oms-01/purchase-order.create-entity.json +3 -0
  72. package/sdk-dist/docs/runtime-reference/examples/oms-01/purchase-order.schema.json +417 -0
  73. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.bulk-update.json +22 -0
  74. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.create-entity.json +3 -0
  75. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.query.json +10 -0
  76. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.row.insert.json +28 -0
  77. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.schema.json +398 -0
  78. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.selection.condition.json +13 -0
  79. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.selection.ids.json +7 -0
  80. package/sdk-dist/docs/runtime-reference/examples/oms-01/stock-issue.create-entity.json +3 -0
  81. package/sdk-dist/docs/runtime-reference/examples/oms-01/stock-issue.schema.json +137 -0
  82. package/sdk-dist/docs/runtime-reference/examples/wms-01/README.md +21 -0
  83. package/sdk-dist/docs/runtime-reference/row-crud.md +186 -0
  84. package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +494 -0
  85. package/sdk-dist/docs/runtime-reference/table-lifecycle.md +123 -0
  86. package/sdk-dist/docs/runtime-reference/workflow/README.md +19 -0
  87. package/src/runtime/command_registry.ts +47 -0
  88. package/src/runtime/entity_save_schema.ts +708 -0
  89. package/src/runtime/errors.ts +11 -0
  90. package/src/runtime/execute.ts +123 -10
  91. package/src/runtime/zod_registry.ts +154 -0
  92. package/src/tests/command_registry.test.ts +6 -0
  93. package/src/tests/entity_save_schema.test.ts +236 -0
  94. package/src/tests/execute_validation.test.ts +598 -0
  95. package/src/tests/help.test.ts +39 -0
@@ -2,9 +2,9 @@ import { parseCLIArgs, hasFlag, flagValue } from './argv.js';
2
2
  import { loadRuntimeEnv } from './env.js';
3
3
  import { CLIError } from './errors.js';
4
4
  import { buildRequestHeaders, buildURL, normalizeExternalEndpoint } from './http.js';
5
- import { listModules, listModuleCommands, findCommand } from './command_registry.js';
5
+ import { listModules, listModuleCommands, findCommand, findCommandSuggestions } from './command_registry.js';
6
6
  import { loadBodyFromFlags, loadQueryFromFlags } from './body_loader.js';
7
- import { getBodySchema, getTypeHints, getUnsupportedBodySchemaReason } from './zod_registry.js';
7
+ import { getBodySchema, getCommandDocHints, getDocHints, getTypeHints, getUnsupportedBodySchemaReason } from './zod_registry.js';
8
8
  export function renderRootHelp(scope) {
9
9
  const binary = scope === 'admin' ? 'arcubase-admin' : 'arcubase';
10
10
  return [
@@ -17,7 +17,8 @@ export function renderRootHelp(scope) {
17
17
  export function renderModuleHelp(scope, moduleName) {
18
18
  const items = listModuleCommands(scope, moduleName);
19
19
  if (items.length === 0) {
20
- throw new CLIError('UNKNOWN_MODULE', `unknown module: ${moduleName}`, 2);
20
+ const suggestions = findCommandSuggestions(moduleName, '').map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`);
21
+ throw new CLIError('UNKNOWN_MODULE', `unknown module: ${moduleName}`, 2, suggestions.length > 0 ? { suggestions } : undefined);
21
22
  }
22
23
  return [
23
24
  `${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${moduleName} <command> [flags]`,
@@ -29,15 +30,24 @@ export function renderModuleHelp(scope, moduleName) {
29
30
  export function renderCommandHelp(scope, moduleName, commandName) {
30
31
  const command = findCommand(scope, moduleName, commandName);
31
32
  if (!command) {
32
- throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2);
33
+ const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`);
34
+ throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, suggestions.length > 0 ? { suggestions } : undefined);
33
35
  }
36
+ const docHints = [
37
+ ...getCommandDocHints(`${scope}.${command.module}.${command.functionName}`),
38
+ ...(command.requestType ? getDocHints(command.requestType) : []),
39
+ ].filter((item, index, list) => list.findIndex((candidate) => candidate.file === item.file) === index);
34
40
  return [
35
41
  `${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${moduleName} ${commandName}`,
36
42
  `method: ${command.method}`,
37
43
  `endpoint: ${normalizeExternalEndpoint(command.endpoint)}`,
38
44
  command.requestType ? `body: ${command.requestType} via --body-file` : 'body: none',
45
+ 'query flags: --query-file',
39
46
  command.endpointParams.length ? `path flags: ${command.endpointParams.map((item) => `--${item.replace(/_/g, '-')}`).join(', ')}` : 'path flags: none',
40
47
  command.scalarParams.length ? `scalar flags: ${command.scalarParams.map((item) => `--${item.name.replace(/([A-Z])/g, '-$1').toLowerCase()}`).join(', ')}` : 'scalar flags: none',
48
+ ...(docHints.length > 0
49
+ ? ['docs:', ...docHints.map((item) => ` - ${item.title}: ${item.file}`)]
50
+ : []),
41
51
  ].join('\n');
42
52
  }
43
53
  function coerceScalar(value, typeText) {
@@ -84,10 +94,55 @@ function buildScalarQuery(command, flags) {
84
94
  }
85
95
  return Object.keys(out).length > 0 ? out : undefined;
86
96
  }
97
+ function isRecord(value) {
98
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
99
+ }
100
+ function throwBodyValidationFailure(message, requestType, path, scope, command) {
101
+ throw new CLIError('BODY_VALIDATION_FAILED', message, 2, {
102
+ operation: `${scope}.${command.module}.${command.functionName}`,
103
+ requestType,
104
+ issues: [
105
+ {
106
+ path,
107
+ message,
108
+ },
109
+ ],
110
+ tsHints: getTypeHints(requestType),
111
+ docHints: getDocHints(requestType),
112
+ });
113
+ }
114
+ function validateActionSpecificBody(scope, command, flags, body) {
115
+ if (!isRecord(body) || !command.requestType) {
116
+ return;
117
+ }
118
+ if (command.module === 'workflow' && command.functionName === 'queryEntitySelection') {
119
+ const action = flagValue(flags, 'action');
120
+ const selection = body.selection;
121
+ if (!isRecord(selection) || typeof selection.type !== 'string') {
122
+ return;
123
+ }
124
+ const type = selection.type;
125
+ const condition = isRecord(selection.condition) ? selection.condition : undefined;
126
+ const conditionKeys = condition ? Object.keys(condition).filter((key) => key !== 'selectAll') : [];
127
+ if (action === 'query') {
128
+ if (type === 'condition') {
129
+ throwBodyValidationFailure('selection.type=condition is not supported for action=query; use selection.type=ids or selection.type=all, or use workflow query-entity for search conditions', command.requestType, 'body.selection.type', scope, command);
130
+ }
131
+ return;
132
+ }
133
+ if ((action === 'archive' || action === 'delete' || action === 'batch_print') && type === 'all') {
134
+ throwBodyValidationFailure(`selection.type=all is not supported for action=${action}; use ids or condition`, command.requestType, 'body.selection.type', scope, command);
135
+ }
136
+ if ((action === 'archive' || action === 'delete') && type === 'condition' && conditionKeys.length === 0) {
137
+ throwBodyValidationFailure(`selection.condition for action=${action} requires at least one q, tab, or filter_* key in addition to selectAll`, command.requestType, 'body.selection.condition', scope, command);
138
+ }
139
+ }
140
+ }
87
141
  export function summarizeCommand(scope, moduleName, commandName) {
88
142
  const command = findCommand(scope, moduleName, commandName);
89
143
  if (!command) {
90
- throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2);
144
+ const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`);
145
+ throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, suggestions.length > 0 ? { suggestions } : undefined);
91
146
  }
92
147
  return {
93
148
  scope,
@@ -99,7 +154,7 @@ export function summarizeCommand(scope, moduleName, commandName) {
99
154
  scalarFlags: command.scalarParams.map((item) => item.name.replace(/([A-Z])/g, '-$1').toLowerCase()),
100
155
  };
101
156
  }
102
- export async function executeCLI(scope, argv, env = loadRuntimeEnv(scope), fetchImpl = fetch) {
157
+ export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
103
158
  const parsed = parseCLIArgs(argv);
104
159
  if (parsed.commandTokens.length === 0) {
105
160
  return { kind: 'help', text: renderRootHelp(scope) };
@@ -114,6 +169,7 @@ export async function executeCLI(scope, argv, env = loadRuntimeEnv(scope), fetch
114
169
  if (hasFlag(parsed.flags, 'help')) {
115
170
  return { kind: 'help', text: renderCommandHelp(scope, moduleName, commandName) };
116
171
  }
172
+ const runtimeEnv = env ?? loadRuntimeEnv(scope);
117
173
  const command = findCommand(scope, moduleName, commandName);
118
174
  if (!command) {
119
175
  throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2);
@@ -123,6 +179,7 @@ export async function executeCLI(scope, argv, env = loadRuntimeEnv(scope), fetch
123
179
  const fileQuery = loadQueryFromFlags(parsed.flags);
124
180
  const query = { ...(scalarQuery ?? {}), ...(fileQuery ?? {}) };
125
181
  const body = loadBodyFromFlags(parsed.flags);
182
+ let validatedBody = body;
126
183
  if (command.requestType) {
127
184
  if (body === undefined) {
128
185
  throw new CLIError('MISSING_BODY_FILE', `command requires --body-file for ${command.requestType}`, 2);
@@ -139,8 +196,11 @@ export async function executeCLI(scope, argv, env = loadRuntimeEnv(scope), fetch
139
196
  message: issue.message,
140
197
  })),
141
198
  tsHints: getTypeHints(command.requestType),
199
+ docHints: getDocHints(command.requestType),
142
200
  });
143
201
  }
202
+ validatedBody = parsedBody.data;
203
+ validateActionSpecificBody(scope, command, parsed.flags, validatedBody);
144
204
  }
145
205
  else {
146
206
  const unsupportedReason = getUnsupportedBodySchemaReason(command.requestType);
@@ -150,18 +210,18 @@ export async function executeCLI(scope, argv, env = loadRuntimeEnv(scope), fetch
150
210
  requestType: command.requestType,
151
211
  reason: unsupportedReason,
152
212
  tsHints: getTypeHints(command.requestType),
213
+ docHints: getDocHints(command.requestType),
153
214
  });
154
215
  }
155
216
  }
156
217
  }
157
- const headers = buildRequestHeaders(env);
158
- const response = await fetchImpl(buildURL(env.ARCUBASE_BASE_URL, endpoint, Object.keys(query).length > 0 ? query : undefined), {
218
+ const response = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, endpoint, Object.keys(query).length > 0 ? query : undefined), {
159
219
  method: command.method,
160
220
  headers: {
161
- ...headers,
221
+ ...buildRequestHeaders(runtimeEnv),
162
222
  'Content-Type': 'application/json',
163
223
  },
164
- body: body === undefined || command.method === 'GET' ? undefined : JSON.stringify(body),
224
+ body: validatedBody === undefined || command.method === 'GET' ? undefined : JSON.stringify(validatedBody),
165
225
  });
166
226
  const raw = await response.text();
167
227
  let parsedResponse = raw;
@@ -174,6 +234,25 @@ export async function executeCLI(scope, argv, env = loadRuntimeEnv(scope), fetch
174
234
  if (!response.ok) {
175
235
  throw new CLIError('UPSTREAM_REQUEST_FAILED', typeof parsedResponse === 'string' ? parsedResponse : JSON.stringify(parsedResponse), 1);
176
236
  }
237
+ if (parsedResponse && typeof parsedResponse === 'object' && 'error' in parsedResponse && parsedResponse.error) {
238
+ const upstreamError = parsedResponse.error;
239
+ const upstreamMessage = typeof upstreamError === 'object' && upstreamError && 'message' in upstreamError && typeof upstreamError.message === 'string'
240
+ ? upstreamError.message
241
+ : typeof upstreamError === 'string'
242
+ ? upstreamError
243
+ : JSON.stringify(upstreamError);
244
+ const traceId = typeof parsedResponse.trace_id === 'string'
245
+ ? parsedResponse.trace_id
246
+ : typeof parsedResponse.request_id === 'string'
247
+ ? parsedResponse.request_id
248
+ : undefined;
249
+ throw new CLIError('UPSTREAM_BODY_ERROR', upstreamMessage, 1, {
250
+ endpoint,
251
+ method: command.method,
252
+ traceId,
253
+ upstreamError,
254
+ });
255
+ }
177
256
  return {
178
257
  kind: 'result',
179
258
  commandPath: command.commandPath,
@@ -6,5 +6,11 @@ export type RuntimeTypeHint = {
6
6
  symbol: string;
7
7
  file: string;
8
8
  };
9
+ export type RuntimeDocHint = {
10
+ title: string;
11
+ file: string;
12
+ };
9
13
  export declare function getTypeHints(typeName: string): RuntimeTypeHint[];
14
+ export declare function getDocHints(typeName: string): RuntimeDocHint[];
15
+ export declare function getCommandDocHints(operation: string): RuntimeDocHint[];
10
16
  //# sourceMappingURL=zod_registry.d.ts.map
@@ -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;AAIrC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAIjE;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,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqBhE"}
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;AA6BrC,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;AA2DD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqBhE;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAI9D;AAsDD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAItE"}
@@ -1,9 +1,25 @@
1
1
  import { bodySchemas, unsupportedBodySchemas } from '../generated/zod_registry.generated.js';
2
2
  import { typeIndex } from '../generated/type_index.generated.js';
3
+ import { AppCreateByTenantsReqVOSchemaOverride, EntityBulkUpdateReqVOSchemaOverride, EntityInvokeBatchOperatorReqVOSchemaOverride, EntityQueryReqVOSchemaOverride, EntitySaveReqVOSchemaOverride, EntitySelectionActionReqVOSchemaOverride, EntityTagsBatchReqVOSchemaOverride, EntityTagsSelectionReqVOSchemaOverride, EntityUpdateReqVOSchemaOverride, InsertReqVOSchemaOverride, } from './entity_save_schema.js';
4
+ const schemaOverrides = {
5
+ AppCreateByTenantsReqVO: AppCreateByTenantsReqVOSchemaOverride,
6
+ EntityBulkUpdateReqVO: EntityBulkUpdateReqVOSchemaOverride,
7
+ 'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': EntityInvokeBatchOperatorReqVOSchemaOverride,
8
+ EntityQueryReqVO: EntityQueryReqVOSchemaOverride,
9
+ EntitySaveReqVO: EntitySaveReqVOSchemaOverride,
10
+ EntitySelectionActionReqVO: EntitySelectionActionReqVOSchemaOverride,
11
+ '{ policy_id: string selection: any }': EntityTagsSelectionReqVOSchemaOverride,
12
+ '{ policy_id: string selection: any tag_names: string[] }': EntityTagsBatchReqVOSchemaOverride,
13
+ EntityUpdateReqVO: EntityUpdateReqVOSchemaOverride,
14
+ InsertReqVO: InsertReqVOSchemaOverride,
15
+ };
3
16
  export function getBodySchema(typeName) {
4
17
  const name = String(typeName || '').trim();
5
18
  if (!name)
6
19
  return null;
20
+ if (schemaOverrides[name]) {
21
+ return schemaOverrides[name];
22
+ }
7
23
  return bodySchemas[name] ?? null;
8
24
  }
9
25
  export function getUnsupportedBodySchemaReason(typeName) {
@@ -22,6 +38,60 @@ export function requireBodySchema(typeName) {
22
38
  }
23
39
  throw new Error(`runtime validation schema not found for ${typeName}`);
24
40
  }
41
+ const examplesIndexDocHint = {
42
+ title: 'Examples index',
43
+ file: '/opt/arcubase-sdk/docs/runtime-reference/examples/README.md',
44
+ };
45
+ const omsExampleDocHint = {
46
+ title: 'OMS example 01',
47
+ file: '/opt/arcubase-sdk/docs/runtime-reference/examples/oms-01/README.md',
48
+ };
49
+ const docHintIndex = {
50
+ AppCreateByTenantsReqVO: [
51
+ { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
52
+ examplesIndexDocHint,
53
+ omsExampleDocHint,
54
+ ],
55
+ AppCreateEntityReqVO: [
56
+ { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
57
+ examplesIndexDocHint,
58
+ omsExampleDocHint,
59
+ ],
60
+ EntitySaveReqVO: [
61
+ { title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
62
+ omsExampleDocHint,
63
+ ],
64
+ EntityQueryReqVO: [
65
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
66
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
67
+ omsExampleDocHint,
68
+ ],
69
+ EntityUpdateReqVO: [
70
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
71
+ omsExampleDocHint,
72
+ ],
73
+ EntitySelectionActionReqVO: [
74
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
75
+ omsExampleDocHint,
76
+ ],
77
+ InsertReqVO: [
78
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
79
+ omsExampleDocHint,
80
+ ],
81
+ EntityBulkUpdateReqVO: [
82
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
83
+ omsExampleDocHint,
84
+ ],
85
+ 'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
86
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
87
+ ],
88
+ '{ policy_id: string selection: any }': [
89
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
90
+ ],
91
+ '{ policy_id: string selection: any tag_names: string[] }': [
92
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
93
+ ],
94
+ };
25
95
  export function getTypeHints(typeName) {
26
96
  const name = String(typeName || '').trim();
27
97
  if (!name)
@@ -48,3 +118,66 @@ export function getTypeHints(typeName) {
48
118
  }
49
119
  return out;
50
120
  }
121
+ export function getDocHints(typeName) {
122
+ const name = String(typeName || '').trim();
123
+ if (!name)
124
+ return [];
125
+ return docHintIndex[name] ?? [];
126
+ }
127
+ const commandDocHintIndex = {
128
+ 'admin.app.createAppByTenants': [
129
+ { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
130
+ examplesIndexDocHint,
131
+ omsExampleDocHint,
132
+ ],
133
+ 'admin.app.createEntity': [
134
+ { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
135
+ omsExampleDocHint,
136
+ ],
137
+ 'admin.entity.adminGetEntityInfo': [
138
+ { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
139
+ omsExampleDocHint,
140
+ ],
141
+ 'admin.entity.adminSaveEntity': [
142
+ { title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
143
+ omsExampleDocHint,
144
+ ],
145
+ 'user.workflow.insertEntity': [
146
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
147
+ omsExampleDocHint,
148
+ ],
149
+ 'user.workflow.queryEntity': [
150
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
151
+ omsExampleDocHint,
152
+ ],
153
+ 'user.workflow.getEntityRow': [
154
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
155
+ omsExampleDocHint,
156
+ ],
157
+ 'user.workflow.updateEntityRow': [
158
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
159
+ omsExampleDocHint,
160
+ ],
161
+ 'user.workflow.queryEntitySelection': [
162
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
163
+ omsExampleDocHint,
164
+ ],
165
+ 'user.workflow.deleteEntityRow': [
166
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
167
+ omsExampleDocHint,
168
+ ],
169
+ 'user.workflow.restoreEntityRow': [
170
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
171
+ omsExampleDocHint,
172
+ ],
173
+ 'user.entity.updateEntityBulk': [
174
+ { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
175
+ omsExampleDocHint,
176
+ ],
177
+ };
178
+ export function getCommandDocHints(operation) {
179
+ const key = String(operation || '').trim();
180
+ if (!key)
181
+ return [];
182
+ return commandDocHintIndex[key] ?? [];
183
+ }
@@ -1,6 +1,7 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { adminCommands, userCommands } from '../generated/command_registry.generated.js';
4
+ import { findCommandSuggestions } from '../runtime/command_registry.js';
4
5
  test('admin app create-entity command exists', () => {
5
6
  assert.ok(adminCommands.some((c) => c.commandPath.join(' ') === 'app create-entity'));
6
7
  });
@@ -20,3 +21,7 @@ test('tag commands no longer emit truncated inline request type', () => {
20
21
  assert.match(String(typed.requestType), /^\{\s*policy_id:\s*string.*selection:\s*any.*tag_names:\s*string\[\]\s*\}$/);
21
22
  assert.equal(typed.scalarParams.length, 0);
22
23
  });
24
+ test('suggestions can point wrong admin delete guess to user workflow delete command', () => {
25
+ const suggestions = findCommandSuggestions('entity', 'admin-delete-entity-row');
26
+ assert.ok(suggestions.some((item) => item.binary === 'arcubase' && item.moduleName === 'workflow' && item.commandName === 'delete-entity-row'));
27
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entity_save_schema.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity_save_schema.test.d.ts","sourceRoot":"","sources":["../../src/tests/entity_save_schema.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,221 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { getBodySchema } from '../runtime/zod_registry.js';
4
+ const schema = getBodySchema('EntitySaveReqVO');
5
+ function buildMinimalField(id = 1001) {
6
+ return {
7
+ id,
8
+ label: '订单号',
9
+ type: 'text',
10
+ required: true,
11
+ unique: false,
12
+ editable: true,
13
+ visible: true,
14
+ show_label: true,
15
+ scannable: false,
16
+ default_value_mode: 0,
17
+ description: '',
18
+ value: null,
19
+ number_decimal: 0,
20
+ depends: [],
21
+ key: 'order_id',
22
+ code_index: false,
23
+ options: {},
24
+ transfers: null,
25
+ children: null,
26
+ };
27
+ }
28
+ test('EntitySaveReqVO accepts fields as array', () => {
29
+ assert.ok(schema);
30
+ const parsed = schema.safeParse({
31
+ id: 2188889845,
32
+ app_id: 2188889844,
33
+ name: '订单',
34
+ schema_version: 1,
35
+ field_id_seq: 1002,
36
+ layout: [[1001]],
37
+ fields: [buildMinimalField()],
38
+ workflow_enabled: false,
39
+ });
40
+ assert.equal(parsed.success, true);
41
+ });
42
+ test('EntitySaveReqVO rejects fields as object', () => {
43
+ assert.ok(schema);
44
+ const parsed = schema.safeParse({
45
+ name: '订单',
46
+ fields: {
47
+ order_id: {
48
+ id: 1001,
49
+ label: '订单号',
50
+ },
51
+ },
52
+ });
53
+ assert.equal(parsed.success, false);
54
+ });
55
+ test('EntitySaveReqVO rejects field_id_seq not greater than max field id', () => {
56
+ assert.ok(schema);
57
+ const parsed = schema.safeParse({
58
+ field_id_seq: 1001,
59
+ layout: [[1001]],
60
+ fields: [buildMinimalField(1001)],
61
+ });
62
+ assert.equal(parsed.success, false);
63
+ assert.ok(parsed.error.issues.some((issue) => issue.path.join('.') === 'field_id_seq'));
64
+ });
65
+ test('EntitySaveReqVO rejects layout unknown top-level field id', () => {
66
+ assert.ok(schema);
67
+ const parsed = schema.safeParse({
68
+ field_id_seq: 1002,
69
+ layout: [[9999]],
70
+ fields: [buildMinimalField(1001)],
71
+ });
72
+ assert.equal(parsed.success, false);
73
+ assert.ok(parsed.error.issues.some((issue) => issue.path.join('.') === 'layout.0.0'));
74
+ });
75
+ test('EntitySaveReqVO rejects non-subform field children', () => {
76
+ assert.ok(schema);
77
+ const parsed = schema.safeParse({
78
+ field_id_seq: 1003,
79
+ layout: [[1001]],
80
+ fields: [{
81
+ ...buildMinimalField(1001),
82
+ children: [buildMinimalField(1002)],
83
+ }],
84
+ });
85
+ assert.equal(parsed.success, false);
86
+ });
87
+ test('EntitySaveReqVO requires status options items', () => {
88
+ assert.ok(schema);
89
+ const parsed = schema.safeParse({
90
+ field_id_seq: 1002,
91
+ layout: [[1001]],
92
+ fields: [{
93
+ ...buildMinimalField(1001),
94
+ type: 'status',
95
+ options: {},
96
+ }],
97
+ });
98
+ assert.equal(parsed.success, false);
99
+ });
100
+ test('EntitySaveReqVO requires serialnumber parts', () => {
101
+ assert.ok(schema);
102
+ const parsed = schema.safeParse({
103
+ field_id_seq: 1002,
104
+ layout: [[1001]],
105
+ fields: [{
106
+ ...buildMinimalField(1001),
107
+ type: 'serialnumber',
108
+ options: {},
109
+ }],
110
+ });
111
+ assert.equal(parsed.success, false);
112
+ });
113
+ test('EntitySaveReqVO requires lookup relation structure', () => {
114
+ assert.ok(schema);
115
+ const parsed = schema.safeParse({
116
+ field_id_seq: 1002,
117
+ layout: [[1001]],
118
+ fields: [{
119
+ ...buildMinimalField(1001),
120
+ type: 'lookup',
121
+ options: {},
122
+ }],
123
+ });
124
+ assert.equal(parsed.success, false);
125
+ });
126
+ test('EntitySaveReqVO requires relationfield relation app and entity ids', () => {
127
+ assert.ok(schema);
128
+ const parsed = schema.safeParse({
129
+ field_id_seq: 1002,
130
+ layout: [[1001]],
131
+ fields: [{
132
+ ...buildMinimalField(1001),
133
+ type: 'relationfield',
134
+ options: {
135
+ relation: {},
136
+ },
137
+ }],
138
+ });
139
+ assert.equal(parsed.success, false);
140
+ });
141
+ test('EntitySaveReqVO requires rollup relation and aggregation formula', () => {
142
+ assert.ok(schema);
143
+ const parsed = schema.safeParse({
144
+ field_id_seq: 1002,
145
+ layout: [[1001]],
146
+ fields: [{
147
+ ...buildMinimalField(1001),
148
+ type: 'rollup',
149
+ options: {
150
+ relation: {
151
+ local_linkto_field_id: 1009,
152
+ target_field_id: 1010,
153
+ target_field_type: 'number',
154
+ },
155
+ },
156
+ }],
157
+ });
158
+ assert.equal(parsed.success, false);
159
+ });
160
+ test('EntitySaveReqVO rejects subform layout that references non-child field id', () => {
161
+ assert.ok(schema);
162
+ const parsed = schema.safeParse({
163
+ field_id_seq: 1004,
164
+ layout: [[1001]],
165
+ fields: [{
166
+ ...buildMinimalField(1001),
167
+ type: 'subform',
168
+ children: [buildMinimalField(1002)],
169
+ layout: [[1003]],
170
+ }],
171
+ });
172
+ assert.equal(parsed.success, false);
173
+ assert.ok(parsed.error.issues.some((issue) => issue.path.join('.') === 'fields.0.layout.0.0'));
174
+ });
175
+ test('EntitySaveReqVO rejects lookup local_linkto_field_id that does not point to linkto field', () => {
176
+ assert.ok(schema);
177
+ const parsed = schema.safeParse({
178
+ field_id_seq: 1004,
179
+ layout: [[1001], [1002]],
180
+ fields: [
181
+ buildMinimalField(1001),
182
+ {
183
+ ...buildMinimalField(1002),
184
+ type: 'lookup',
185
+ options: {
186
+ relation: {
187
+ local_linkto_field_id: 1001,
188
+ target_field_id: 2001,
189
+ target_field_type: 'text',
190
+ },
191
+ },
192
+ },
193
+ ],
194
+ });
195
+ assert.equal(parsed.success, false);
196
+ assert.ok(parsed.error.issues.some((issue) => issue.path.join('.') === 'fields.1.options.relation.local_linkto_field_id'));
197
+ });
198
+ test('EntitySaveReqVO rejects formula references to unknown field ids', () => {
199
+ assert.ok(schema);
200
+ const parsed = schema.safeParse({
201
+ field_id_seq: 1003,
202
+ layout: [[1001]],
203
+ fields: [
204
+ buildMinimalField(1001),
205
+ {
206
+ ...buildMinimalField(1002),
207
+ key: 'calc',
208
+ label: '计算值',
209
+ type: 'formula',
210
+ options: {
211
+ formula: {
212
+ code: '[[订单号:9999]]',
213
+ jsonlogic: { var: '9999' },
214
+ },
215
+ },
216
+ },
217
+ ],
218
+ });
219
+ assert.equal(parsed.success, false);
220
+ assert.ok(parsed.error.issues.some((issue) => issue.message.includes('unknown field id 9999')));
221
+ });