@carthooks/arcubase-cli 0.1.4 → 0.1.5
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 +188 -210
- package/bundle/arcubase.mjs +188 -210
- package/dist/generated/command_registry.generated.d.ts +36 -112
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +40 -163
- package/dist/generated/type_index.generated.d.ts +0 -12
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +0 -12
- package/dist/generated/zod_registry.generated.d.ts +1 -19
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +0 -21
- package/dist/runtime/command_registry.d.ts +19 -1
- package/dist/runtime/command_registry.d.ts.map +1 -1
- package/dist/runtime/command_registry.js +19 -1
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +119 -9
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +24 -7
- package/dist/tests/command_registry.test.js +11 -4
- package/dist/tests/execute_validation.test.js +72 -17
- package/dist/tests/help.test.js +16 -9
- package/package.json +1 -1
- package/sdk-dist/api/admin/app.ts +8 -1
- package/sdk-dist/docs/runtime-reference/README.md +24 -16
- package/sdk-dist/docs/runtime-reference/app-discovery.md +68 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/README.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema.md +2 -2
- package/sdk-dist/docs/runtime-reference/examples/README.md +1 -1
- package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +4 -4
- package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +1 -1
- package/sdk-dist/docs/runtime-reference/row-crud.md +6 -6
- package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +5 -5
- package/sdk-dist/docs/runtime-reference/table-lifecycle.md +24 -8
- package/sdk-dist/docs/runtime-reference/uploads.md +1 -1
- package/sdk-dist/generated/command_registry.generated.ts +40 -163
- package/sdk-dist/generated/type_index.generated.ts +0 -12
- package/sdk-dist/generated/zod_registry.generated.ts +0 -36
- package/sdk-dist/types/app.ts +7 -0
- package/src/generated/command_registry.generated.ts +40 -163
- package/src/generated/type_index.generated.ts +0 -12
- package/src/generated/zod_registry.generated.ts +0 -36
- package/src/runtime/command_registry.ts +38 -2
- package/src/runtime/execute.ts +127 -9
- package/src/runtime/zod_registry.ts +25 -7
- package/src/tests/command_registry.test.ts +13 -4
- package/src/tests/execute_validation.test.ts +78 -17
- package/src/tests/help.test.ts +17 -9
- package/sdk-dist/docs/runtime-reference/workflow/README.md +0 -19
|
@@ -60,6 +60,61 @@ test('admin api-prefixed command executes against external root path', async ()
|
|
|
60
60
|
assert.equal(out.kind, 'result');
|
|
61
61
|
assert.equal(out.data.url, 'https://arcubase.example.com/apps/app_1/entity-shares');
|
|
62
62
|
});
|
|
63
|
+
test('app inventory returns apps with entity lists', async () => {
|
|
64
|
+
const calls = [];
|
|
65
|
+
const out = await executeCLI('admin', ['app', 'inventory'], env, async (url, init) => {
|
|
66
|
+
calls.push(`${init?.method ?? 'GET'} ${String(url)}`);
|
|
67
|
+
if (String(url) === 'https://arcubase.example.com/apps') {
|
|
68
|
+
return new Response(JSON.stringify({
|
|
69
|
+
data: [
|
|
70
|
+
{ id: 101, name: 'Workspace' },
|
|
71
|
+
{ id: 102, name: 'CRM' },
|
|
72
|
+
],
|
|
73
|
+
}), { status: 200 });
|
|
74
|
+
}
|
|
75
|
+
if (String(url) === 'https://arcubase.example.com/apps/101/entities') {
|
|
76
|
+
return new Response(JSON.stringify({
|
|
77
|
+
data: [
|
|
78
|
+
{ id: 201, name: 'Orders' },
|
|
79
|
+
{ id: 202, name: 'Customers' },
|
|
80
|
+
],
|
|
81
|
+
}), { status: 200 });
|
|
82
|
+
}
|
|
83
|
+
if (String(url) === 'https://arcubase.example.com/apps/102/entities') {
|
|
84
|
+
return new Response(JSON.stringify({
|
|
85
|
+
data: [
|
|
86
|
+
{ id: 203, name: 'Leads' },
|
|
87
|
+
],
|
|
88
|
+
}), { status: 200 });
|
|
89
|
+
}
|
|
90
|
+
return new Response('{}', { status: 404 });
|
|
91
|
+
});
|
|
92
|
+
assert.equal(out.kind, 'result');
|
|
93
|
+
assert.deepEqual(out.data, {
|
|
94
|
+
apps: [
|
|
95
|
+
{
|
|
96
|
+
id: '101',
|
|
97
|
+
name: 'Workspace',
|
|
98
|
+
entities: [
|
|
99
|
+
{ id: '201', name: 'Orders' },
|
|
100
|
+
{ id: '202', name: 'Customers' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: '102',
|
|
105
|
+
name: 'CRM',
|
|
106
|
+
entities: [
|
|
107
|
+
{ id: '203', name: 'Leads' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
assert.deepEqual(calls, [
|
|
113
|
+
'GET https://arcubase.example.com/apps',
|
|
114
|
+
'POST https://arcubase.example.com/apps/101/entities',
|
|
115
|
+
'POST https://arcubase.example.com/apps/102/entities',
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
63
118
|
test('admin save entity treats top-level error body as failure even on http 200', async () => {
|
|
64
119
|
const bodyFile = tempJSONFile({
|
|
65
120
|
id: 2188889845,
|
|
@@ -183,7 +238,7 @@ test('delete entity row rejects selection type outside ids', async () => {
|
|
|
183
238
|
},
|
|
184
239
|
});
|
|
185
240
|
await assert.rejects(async () => {
|
|
186
|
-
await executeCLI('user', ['
|
|
241
|
+
await executeCLI('user', ['rows', 'delete-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
187
242
|
}, (error) => {
|
|
188
243
|
assert.ok(error instanceof CLIError);
|
|
189
244
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -228,7 +283,7 @@ test('selection action accepts condition selection with selectAll', async () =>
|
|
|
228
283
|
],
|
|
229
284
|
},
|
|
230
285
|
});
|
|
231
|
-
const out = await executeCLI('user', ['
|
|
286
|
+
const out = await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
232
287
|
assert.equal(out.kind, 'result');
|
|
233
288
|
assert.equal(out.data.selection.type, 'condition');
|
|
234
289
|
assert.equal(out.data.selection.condition.selectAll, true);
|
|
@@ -244,7 +299,7 @@ test('selection action accepts condition selection with filter_* keys', async ()
|
|
|
244
299
|
},
|
|
245
300
|
},
|
|
246
301
|
});
|
|
247
|
-
const out = await executeCLI('user', ['
|
|
302
|
+
const out = await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
248
303
|
assert.equal(out.kind, 'result');
|
|
249
304
|
assert.equal(out.data.selection.type, 'condition');
|
|
250
305
|
assert.equal(out.data.selection.condition.selectAll, true);
|
|
@@ -259,7 +314,7 @@ test('selection action accepts condition selection with selectAll only for batch
|
|
|
259
314
|
},
|
|
260
315
|
},
|
|
261
316
|
});
|
|
262
|
-
const out = await executeCLI('user', ['
|
|
317
|
+
const out = await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'batch_print', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
263
318
|
assert.equal(out.kind, 'result');
|
|
264
319
|
assert.equal(out.data.selection.type, 'condition');
|
|
265
320
|
assert.equal(out.data.selection.condition.selectAll, true);
|
|
@@ -270,7 +325,7 @@ test('selection action accepts all selection for query-style actions', async ()
|
|
|
270
325
|
type: 'all',
|
|
271
326
|
},
|
|
272
327
|
});
|
|
273
|
-
const out = await executeCLI('user', ['
|
|
328
|
+
const out = await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
274
329
|
assert.equal(out.kind, 'result');
|
|
275
330
|
assert.equal(out.data.selection.type, 'all');
|
|
276
331
|
});
|
|
@@ -286,7 +341,7 @@ test('selection action rejects condition selection for query action', async () =
|
|
|
286
341
|
},
|
|
287
342
|
});
|
|
288
343
|
await assert.rejects(async () => {
|
|
289
|
-
await executeCLI('user', ['
|
|
344
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
290
345
|
}, (error) => {
|
|
291
346
|
assert.ok(error instanceof CLIError);
|
|
292
347
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -301,7 +356,7 @@ test('selection action rejects all selection for archive action', async () => {
|
|
|
301
356
|
},
|
|
302
357
|
});
|
|
303
358
|
await assert.rejects(async () => {
|
|
304
|
-
await executeCLI('user', ['
|
|
359
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
305
360
|
}, (error) => {
|
|
306
361
|
assert.ok(error instanceof CLIError);
|
|
307
362
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -320,7 +375,7 @@ test('selection action rejects selectAll-only condition for archive action', asy
|
|
|
320
375
|
},
|
|
321
376
|
});
|
|
322
377
|
await assert.rejects(async () => {
|
|
323
|
-
await executeCLI('user', ['
|
|
378
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
324
379
|
}, (error) => {
|
|
325
380
|
assert.ok(error instanceof CLIError);
|
|
326
381
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -337,7 +392,7 @@ test('insert entity rejects local file path for file field and points to arcubas
|
|
|
337
392
|
});
|
|
338
393
|
const calls = [];
|
|
339
394
|
await assert.rejects(async () => {
|
|
340
|
-
await executeCLI('user', ['
|
|
395
|
+
await executeCLI('user', ['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
341
396
|
calls.push({ url: String(url), method: init?.method });
|
|
342
397
|
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
343
398
|
return new Response(JSON.stringify({
|
|
@@ -378,7 +433,7 @@ test('update row rejects object value for image field and requires upload array'
|
|
|
378
433
|
},
|
|
379
434
|
});
|
|
380
435
|
await assert.rejects(async () => {
|
|
381
|
-
await executeCLI('user', ['
|
|
436
|
+
await executeCLI('user', ['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
382
437
|
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
383
438
|
return new Response(JSON.stringify({
|
|
384
439
|
data: {
|
|
@@ -416,7 +471,7 @@ test('insert entity accepts upload array for file field after schema preflight',
|
|
|
416
471
|
},
|
|
417
472
|
});
|
|
418
473
|
const calls = [];
|
|
419
|
-
const out = await executeCLI('user', ['
|
|
474
|
+
const out = await executeCLI('user', ['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
420
475
|
calls.push({ url: String(url), method: init?.method });
|
|
421
476
|
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
422
477
|
return new Response(JSON.stringify({
|
|
@@ -454,7 +509,7 @@ test('insert entity decorates upload bind failures with actionable guidance', as
|
|
|
454
509
|
},
|
|
455
510
|
});
|
|
456
511
|
await assert.rejects(async () => {
|
|
457
|
-
await executeCLI('user', ['
|
|
512
|
+
await executeCLI('user', ['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
458
513
|
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
459
514
|
return new Response(JSON.stringify({
|
|
460
515
|
data: {
|
|
@@ -492,7 +547,7 @@ test('selection action rejects condition selection without selectAll', async ()
|
|
|
492
547
|
},
|
|
493
548
|
});
|
|
494
549
|
await assert.rejects(async () => {
|
|
495
|
-
await executeCLI('user', ['
|
|
550
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
496
551
|
}, (error) => {
|
|
497
552
|
assert.ok(error instanceof CLIError);
|
|
498
553
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -512,7 +567,7 @@ test('selection action rejects condition selection that uses text_search instead
|
|
|
512
567
|
},
|
|
513
568
|
});
|
|
514
569
|
await assert.rejects(async () => {
|
|
515
|
-
await executeCLI('user', ['
|
|
570
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
516
571
|
}, (error) => {
|
|
517
572
|
assert.ok(error instanceof CLIError);
|
|
518
573
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -604,7 +659,7 @@ test('query entity rejects camelCase viewMode and points to row crud doc', async
|
|
|
604
659
|
viewMode: 'grid',
|
|
605
660
|
});
|
|
606
661
|
await assert.rejects(async () => {
|
|
607
|
-
await executeCLI('user', ['
|
|
662
|
+
await executeCLI('user', ['rows', 'query-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
608
663
|
}, (error) => {
|
|
609
664
|
assert.ok(error instanceof CLIError);
|
|
610
665
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -624,7 +679,7 @@ test('update entity row rejects fields key', async () => {
|
|
|
624
679
|
changed_fields: [1002],
|
|
625
680
|
});
|
|
626
681
|
await assert.rejects(async () => {
|
|
627
|
-
await executeCLI('user', ['
|
|
682
|
+
await executeCLI('user', ['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
628
683
|
}, (error) => {
|
|
629
684
|
assert.ok(error instanceof CLIError);
|
|
630
685
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -641,7 +696,7 @@ test('update entity row rejects changed_fields that are missing from data', asyn
|
|
|
641
696
|
changed_fields: [1003],
|
|
642
697
|
});
|
|
643
698
|
await assert.rejects(async () => {
|
|
644
|
-
await executeCLI('user', ['
|
|
699
|
+
await executeCLI('user', ['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
645
700
|
}, (error) => {
|
|
646
701
|
assert.ok(error instanceof CLIError);
|
|
647
702
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
package/dist/tests/help.test.js
CHANGED
|
@@ -24,9 +24,10 @@ test('module help prints create-entity command', async () => {
|
|
|
24
24
|
const out = await executeCLI('admin', ['app', '--help'], env, async () => new Response('{}'));
|
|
25
25
|
assert.equal(out.kind, 'help');
|
|
26
26
|
assert.match(out.text, /create-entity/);
|
|
27
|
+
assert.match(out.text, /inventory/);
|
|
27
28
|
});
|
|
28
29
|
test('module help does not require runtime env', async () => {
|
|
29
|
-
const out = await executeCLI('user', ['
|
|
30
|
+
const out = await executeCLI('user', ['rows', '--help'], undefined, async () => new Response('{}'));
|
|
30
31
|
assert.equal(out.kind, 'help');
|
|
31
32
|
assert.match(out.text, /delete-entity-row/);
|
|
32
33
|
});
|
|
@@ -37,19 +38,19 @@ test('command help prints normalized external endpoint', async () => {
|
|
|
37
38
|
assert.doesNotMatch(out.text, /endpoint: \/api\//);
|
|
38
39
|
});
|
|
39
40
|
test('command help does not require runtime env', async () => {
|
|
40
|
-
const out = await executeCLI('user', ['
|
|
41
|
+
const out = await executeCLI('user', ['rows', 'delete-entity-row', '--help'], undefined, async () => new Response('{}'));
|
|
41
42
|
assert.equal(out.kind, 'help');
|
|
42
43
|
assert.match(out.text, /endpoint: \/entity\/:app_id\/:entity_id\/delete-item/);
|
|
43
44
|
});
|
|
44
45
|
test('command help prints runtime doc hints for request body commands', async () => {
|
|
45
|
-
const out = await executeCLI('user', ['
|
|
46
|
+
const out = await executeCLI('user', ['rows', 'update-entity-row', '--help'], undefined, async () => new Response('{}'));
|
|
46
47
|
assert.equal(out.kind, 'help');
|
|
47
48
|
assert.match(out.text, /docs:/);
|
|
48
49
|
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/row-crud\.md/);
|
|
49
50
|
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/);
|
|
50
51
|
});
|
|
51
52
|
test('command help prints query-file support', async () => {
|
|
52
|
-
const out = await executeCLI('user', ['
|
|
53
|
+
const out = await executeCLI('user', ['rows', 'query-entity', '--help'], undefined, async () => new Response('{}'));
|
|
53
54
|
assert.equal(out.kind, 'help');
|
|
54
55
|
assert.match(out.text, /query flags: --query-file/);
|
|
55
56
|
});
|
|
@@ -59,19 +60,25 @@ test('command help without request body can still point to lifecycle and example
|
|
|
59
60
|
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/table-lifecycle\.md/);
|
|
60
61
|
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/);
|
|
61
62
|
});
|
|
63
|
+
test('app inventory help explains app discovery purpose', async () => {
|
|
64
|
+
const out = await executeCLI('admin', ['app', 'inventory', '--help'], undefined, async () => new Response('{}'));
|
|
65
|
+
assert.equal(out.kind, 'help');
|
|
66
|
+
assert.match(out.text, /list all accessible apps and each app entity list/);
|
|
67
|
+
assert.match(out.text, /use this before asking the user for app_id/);
|
|
68
|
+
});
|
|
62
69
|
test('admin-save-entity help prints row-operation next step', async () => {
|
|
63
70
|
const out = await executeCLI('admin', ['entity', 'admin-save-entity', '--help'], undefined, async () => new Response('{}'));
|
|
64
71
|
assert.equal(out.kind, 'help');
|
|
65
72
|
assert.match(out.text, /switch to arcubase for row operations/);
|
|
66
|
-
assert.match(out.text, /arcubase
|
|
67
|
-
assert.match(out.text, /arcubase
|
|
73
|
+
assert.match(out.text, /arcubase rows insert-entity/);
|
|
74
|
+
assert.match(out.text, /arcubase rows query-entity/);
|
|
68
75
|
});
|
|
69
76
|
test('wrong admin workflow command points to arcubase row command', async () => {
|
|
70
77
|
await assert.rejects(() => executeCLI('admin', ['workflow', 'insert-entity'], env, async () => new Response('{}')), (error) => {
|
|
71
78
|
assert.equal(error.code, 'UNKNOWN_COMMAND');
|
|
72
|
-
assert.equal(error.details?.reason, 'workflow insert-entity is a
|
|
73
|
-
assert.equal(error.details?.hint, 'use: arcubase
|
|
74
|
-
assert.ok((error.details?.suggestions ?? []).includes('arcubase
|
|
79
|
+
assert.equal(error.details?.reason, 'workflow insert-entity is not a valid command group; use arcubase rows for row operations');
|
|
80
|
+
assert.equal(error.details?.hint, 'use: arcubase rows insert-entity');
|
|
81
|
+
assert.ok((error.details?.suggestions ?? []).includes('arcubase rows insert-entity'));
|
|
75
82
|
return true;
|
|
76
83
|
});
|
|
77
84
|
});
|
package/package.json
CHANGED
|
@@ -13,7 +13,8 @@ import type {
|
|
|
13
13
|
AppUpdateAppRespVO,
|
|
14
14
|
AppUpdateI18nReqVO,
|
|
15
15
|
AppUpdateI18nRespVO,
|
|
16
|
-
AppGetEntitiesRespVO
|
|
16
|
+
AppGetEntitiesRespVO,
|
|
17
|
+
AppListRespVO
|
|
17
18
|
} from '../../types/app'
|
|
18
19
|
import type {
|
|
19
20
|
AppDeleteWidgetsRespVO,
|
|
@@ -46,6 +47,12 @@ export async function createAppByTenants(
|
|
|
46
47
|
return getDefaultClient().post('/apps', data)
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// @endpoint GET /apps
|
|
51
|
+
// @controller App.ListApps
|
|
52
|
+
export async function listApps(): Promise<AppListRespVO> {
|
|
53
|
+
return getDefaultClient().get('/apps')
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
// @endpoint POST /apps/:id/entities
|
|
50
57
|
// @controller App.CreateEntity
|
|
51
58
|
export async function createEntity(
|
|
@@ -25,6 +25,7 @@ Do not start from raw HTTP methods or paths. Start from the CLI command.
|
|
|
25
25
|
|
|
26
26
|
Use `arcubase-admin` for:
|
|
27
27
|
|
|
28
|
+
- app discovery
|
|
28
29
|
- app creation
|
|
29
30
|
- entity shell creation
|
|
30
31
|
- entity schema save
|
|
@@ -33,6 +34,7 @@ Use `arcubase-admin` for:
|
|
|
33
34
|
Common commands:
|
|
34
35
|
|
|
35
36
|
```bash
|
|
37
|
+
arcubase-admin app inventory
|
|
36
38
|
arcubase-admin app create-app-by-tenants --body-file create-app.json
|
|
37
39
|
arcubase-admin app create-entity --id <app_id> --body-file create-entity.json
|
|
38
40
|
arcubase-admin entity admin-get-entity-info --app-id <app_id> --entity-id <entity_id>
|
|
@@ -54,22 +56,23 @@ Use `arcubase` for:
|
|
|
54
56
|
Common commands:
|
|
55
57
|
|
|
56
58
|
```bash
|
|
57
|
-
arcubase
|
|
58
|
-
arcubase
|
|
59
|
-
arcubase
|
|
60
|
-
arcubase
|
|
61
|
-
arcubase
|
|
62
|
-
arcubase
|
|
59
|
+
arcubase rows insert-entity --app-id <app_id> --entity-id <entity_id> --body-file insert.json
|
|
60
|
+
arcubase rows query-entity --app-id <app_id> --entity-id <entity_id> --body-file query.json
|
|
61
|
+
arcubase rows update-entity-row --app-id <app_id> --entity-id <entity_id> --row-id <row_id> --body-file update.json
|
|
62
|
+
arcubase rows delete-entity-row --app-id <app_id> --entity-id <entity_id> --body-file delete.json
|
|
63
|
+
arcubase rows restore-entity-row --app-id <app_id> --entity-id <entity_id> --body-file restore.json
|
|
64
|
+
arcubase rows query-entity-selection --app-id <app_id> --entity-id <entity_id> --action archive --body-file selection.json
|
|
63
65
|
arcubase entity update-entity-bulk --app-id <app_id> --entity-id <entity_id> --body-file bulk-update.json
|
|
64
66
|
arcubase entity invoke-entity-batch-operator --app-id <app_id> --entity-id <entity_id> --body-file invoke-batch-operator.json
|
|
65
67
|
```
|
|
66
68
|
|
|
67
69
|
Hard rule:
|
|
68
70
|
|
|
71
|
+
- all Arcubase work starts by identifying the target app
|
|
69
72
|
- `arcubase-admin` stops at schema save
|
|
70
73
|
- all row operations must switch to `arcubase`
|
|
71
|
-
- do not use `arcubase-admin
|
|
72
|
-
- do not
|
|
74
|
+
- do not use `arcubase-admin` for row insertion or row queries
|
|
75
|
+
- do not ask the user for `entity_id` when creating a new table
|
|
73
76
|
|
|
74
77
|
## Required file inputs
|
|
75
78
|
|
|
@@ -86,7 +89,7 @@ Use `--query-file` when the command needs query-string parameters that do not be
|
|
|
86
89
|
Current important example:
|
|
87
90
|
|
|
88
91
|
```bash
|
|
89
|
-
arcubase
|
|
92
|
+
arcubase rows query-entity \
|
|
90
93
|
--app-id <app_id> \
|
|
91
94
|
--entity-id <entity_id> \
|
|
92
95
|
--query-file archived-query.json \
|
|
@@ -140,10 +143,16 @@ When a command is tied to a common task, `--help` should print:
|
|
|
140
143
|
|
|
141
144
|
If you need to create a table:
|
|
142
145
|
|
|
143
|
-
1. read `
|
|
144
|
-
2. read `
|
|
145
|
-
3. read `entity-schema
|
|
146
|
-
4.
|
|
146
|
+
1. read `app-discovery.md`
|
|
147
|
+
2. read `table-lifecycle.md`
|
|
148
|
+
3. read `entity-schema.md`
|
|
149
|
+
4. read `entity-schema/README.md`
|
|
150
|
+
5. open the exact field-type page you need
|
|
151
|
+
|
|
152
|
+
If you need to find an existing app or choose the right app:
|
|
153
|
+
|
|
154
|
+
1. read `app-discovery.md`
|
|
155
|
+
2. run `arcubase-admin app inventory`
|
|
147
156
|
|
|
148
157
|
If you need row CRUD:
|
|
149
158
|
|
|
@@ -164,6 +173,8 @@ If you need search, selection, tags, bulk update, or batch operator:
|
|
|
164
173
|
|
|
165
174
|
## Covered now
|
|
166
175
|
|
|
176
|
+
- `app-discovery.md`
|
|
177
|
+
- resolve the target app before asking the user for ids
|
|
167
178
|
- `table-lifecycle.md`
|
|
168
179
|
- app shell, entity shell, schema save, readiness probe
|
|
169
180
|
- `entity-schema.md`
|
|
@@ -182,6 +193,3 @@ If you need search, selection, tags, bulk update, or batch operator:
|
|
|
182
193
|
- business-shaped schema and payload templates
|
|
183
194
|
|
|
184
195
|
## Reserved expansion
|
|
185
|
-
|
|
186
|
-
- `workflow/README.md`
|
|
187
|
-
- reserved for future workflow-specific CLI docs
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# App Discovery
|
|
2
|
+
|
|
3
|
+
Arcubase is a headless app platform.
|
|
4
|
+
|
|
5
|
+
Core concepts:
|
|
6
|
+
|
|
7
|
+
- app: the top-level business container
|
|
8
|
+
- entity: a table inside an app
|
|
9
|
+
- row: a record inside an entity
|
|
10
|
+
|
|
11
|
+
Hard rule:
|
|
12
|
+
|
|
13
|
+
- every operation happens inside one app
|
|
14
|
+
- find the target app before schema work or row work
|
|
15
|
+
|
|
16
|
+
Do not ask the user for `app_id` unless:
|
|
17
|
+
|
|
18
|
+
- there is no matching app
|
|
19
|
+
- there are multiple matching apps and you cannot resolve the ambiguity
|
|
20
|
+
|
|
21
|
+
Do not ask the user for `entity_id` when creating a new table.
|
|
22
|
+
Entity ids are assigned by Arcubase.
|
|
23
|
+
|
|
24
|
+
## First command
|
|
25
|
+
|
|
26
|
+
Use the inventory command first:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
arcubase-admin app inventory
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
It returns:
|
|
33
|
+
|
|
34
|
+
- every accessible app
|
|
35
|
+
- each app id and app name
|
|
36
|
+
- each app entity list
|
|
37
|
+
|
|
38
|
+
Use it to resolve:
|
|
39
|
+
|
|
40
|
+
- which existing app matches the user's business name
|
|
41
|
+
- whether the table already exists
|
|
42
|
+
- whether the user is talking about the wrong app
|
|
43
|
+
|
|
44
|
+
## Resolution order
|
|
45
|
+
|
|
46
|
+
1. if the user gives an app name, run `arcubase-admin app inventory`
|
|
47
|
+
2. match the app by name yourself
|
|
48
|
+
3. if the match is unique, continue without asking for `app_id`
|
|
49
|
+
4. if there is no match, ask which app to use
|
|
50
|
+
5. if there are multiple matches, ask the user to choose by app name
|
|
51
|
+
|
|
52
|
+
## Existing app flow
|
|
53
|
+
|
|
54
|
+
If the app already exists:
|
|
55
|
+
|
|
56
|
+
1. run `arcubase-admin app inventory`
|
|
57
|
+
2. find the app id
|
|
58
|
+
3. inspect the existing entity list
|
|
59
|
+
4. decide whether to create a new entity or write rows into an existing entity
|
|
60
|
+
|
|
61
|
+
## New app flow
|
|
62
|
+
|
|
63
|
+
If the user clearly wants a brand-new app:
|
|
64
|
+
|
|
65
|
+
1. create the app with `arcubase-admin app create-app-by-tenants`
|
|
66
|
+
2. create the entity shell
|
|
67
|
+
3. save the schema
|
|
68
|
+
4. switch to `arcubase rows ...` for row operations
|
|
@@ -54,8 +54,8 @@ If you need row payloads, read:
|
|
|
54
54
|
Current important row rules:
|
|
55
55
|
|
|
56
56
|
- row delete / restore uses:
|
|
57
|
-
- `arcubase
|
|
58
|
-
- `arcubase
|
|
57
|
+
- `arcubase rows delete-entity-row`
|
|
58
|
+
- `arcubase rows restore-entity-row`
|
|
59
59
|
- `selection.type = "ids"`
|
|
60
60
|
- `subform` row values use:
|
|
61
61
|
- `[{ "id": 0, "fields": { ... } }]`
|
|
@@ -30,7 +30,7 @@ The current direct-write path is:
|
|
|
30
30
|
1. create the entity shell
|
|
31
31
|
2. fetch the current shell
|
|
32
32
|
3. save the full schema
|
|
33
|
-
4. probe rows with `arcubase
|
|
33
|
+
4. probe rows with `arcubase rows query-entity`
|
|
34
34
|
|
|
35
35
|
Do not treat shell creation as “table created”.
|
|
36
36
|
|
|
@@ -180,7 +180,7 @@ Also:
|
|
|
180
180
|
Do not call schema creation successful until:
|
|
181
181
|
|
|
182
182
|
1. `arcubase-admin entity admin-save-entity ...` succeeds without top-level `error`
|
|
183
|
-
2. `arcubase
|
|
183
|
+
2. `arcubase rows query-entity ...` succeeds against the saved table
|
|
184
184
|
|
|
185
185
|
If row probing still fails, the table is not ready.
|
|
186
186
|
|
|
@@ -16,7 +16,7 @@ These examples are not raw tenant exports.
|
|
|
16
16
|
|
|
17
17
|
- Keep table names and field names in English.
|
|
18
18
|
- Remove company-specific words.
|
|
19
|
-
- Remove region-specific words when they are not essential to the
|
|
19
|
+
- Remove region-specific words when they are not essential to the business process.
|
|
20
20
|
- Prefer neutral names such as `sku_code`, `sales_order`, `goods_receipt`, and `stock_issue`.
|
|
21
21
|
- When a save payload needs shell values, replace placeholder tokens such as `__APP_ID__`, `__ENTITY_ID__`, and `__SCHEMA_VERSION__` from the current shell returned by:
|
|
22
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OMS Example 01
|
|
2
2
|
|
|
3
|
-
This example set models a compact order-management
|
|
3
|
+
This example set models a compact order-management example.
|
|
4
4
|
|
|
5
5
|
It is intentionally generic:
|
|
6
6
|
|
|
@@ -70,7 +70,7 @@ This example rewrites them into generic names such as:
|
|
|
70
70
|
- `sales-order.selection.condition.json`
|
|
71
71
|
- `sales-order.bulk-update.json`
|
|
72
72
|
|
|
73
|
-
## Save
|
|
73
|
+
## Save sequence
|
|
74
74
|
|
|
75
75
|
For every `*.schema.json` file:
|
|
76
76
|
|
|
@@ -143,13 +143,13 @@ arcubase-admin entity admin-save-entity --app-id <app_id> --entity-id <entity_id
|
|
|
143
143
|
Insert one row:
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
|
-
arcubase
|
|
146
|
+
arcubase rows insert-entity --app-id <app_id> --entity-id <entity_id> --body-file sales-order.row.insert.json
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
Query rows:
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
arcubase
|
|
152
|
+
arcubase rows query-entity --app-id <app_id> --entity-id <entity_id> --body-file sales-order.query.json
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
## Placeholder rules
|
|
@@ -7,7 +7,7 @@ Use this page for direct row operations with `arcubase`.
|
|
|
7
7
|
Command:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
arcubase
|
|
10
|
+
arcubase rows insert-entity \
|
|
11
11
|
--app-id <app_id> \
|
|
12
12
|
--entity-id <entity_id> \
|
|
13
13
|
--body-file insert.json
|
|
@@ -46,7 +46,7 @@ The returned value is the new `row_id`.
|
|
|
46
46
|
Command:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
arcubase
|
|
49
|
+
arcubase rows query-entity \
|
|
50
50
|
--app-id <app_id> \
|
|
51
51
|
--entity-id <entity_id> \
|
|
52
52
|
--body-file query.json
|
|
@@ -85,7 +85,7 @@ Typical row shape:
|
|
|
85
85
|
Command:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
arcubase
|
|
88
|
+
arcubase rows get-entity-row \
|
|
89
89
|
--app-id <app_id> \
|
|
90
90
|
--entity-id <entity_id> \
|
|
91
91
|
--row-id <row_id>
|
|
@@ -98,7 +98,7 @@ Use this when you already know the row id and want the full record payload.
|
|
|
98
98
|
Command:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
arcubase
|
|
101
|
+
arcubase rows update-entity-row \
|
|
102
102
|
--app-id <app_id> \
|
|
103
103
|
--entity-id <entity_id> \
|
|
104
104
|
--row-id <row_id> \
|
|
@@ -139,7 +139,7 @@ The update command returns `true`, not the updated row body.
|
|
|
139
139
|
Command:
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
|
-
arcubase
|
|
142
|
+
arcubase rows delete-entity-row \
|
|
143
143
|
--app-id <app_id> \
|
|
144
144
|
--entity-id <entity_id> \
|
|
145
145
|
--body-file delete.json
|
|
@@ -167,7 +167,7 @@ Rules:
|
|
|
167
167
|
Command:
|
|
168
168
|
|
|
169
169
|
```bash
|
|
170
|
-
arcubase
|
|
170
|
+
arcubase rows restore-entity-row \
|
|
171
171
|
--app-id <app_id> \
|
|
172
172
|
--entity-id <entity_id> \
|
|
173
173
|
--body-file restore.json
|