@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.
Files changed (49) hide show
  1. package/bundle/arcubase-admin.mjs +188 -210
  2. package/bundle/arcubase.mjs +188 -210
  3. package/dist/generated/command_registry.generated.d.ts +36 -112
  4. package/dist/generated/command_registry.generated.d.ts.map +1 -1
  5. package/dist/generated/command_registry.generated.js +40 -163
  6. package/dist/generated/type_index.generated.d.ts +0 -12
  7. package/dist/generated/type_index.generated.d.ts.map +1 -1
  8. package/dist/generated/type_index.generated.js +0 -12
  9. package/dist/generated/zod_registry.generated.d.ts +1 -19
  10. package/dist/generated/zod_registry.generated.d.ts.map +1 -1
  11. package/dist/generated/zod_registry.generated.js +0 -21
  12. package/dist/runtime/command_registry.d.ts +19 -1
  13. package/dist/runtime/command_registry.d.ts.map +1 -1
  14. package/dist/runtime/command_registry.js +19 -1
  15. package/dist/runtime/execute.d.ts.map +1 -1
  16. package/dist/runtime/execute.js +119 -9
  17. package/dist/runtime/zod_registry.d.ts.map +1 -1
  18. package/dist/runtime/zod_registry.js +24 -7
  19. package/dist/tests/command_registry.test.js +11 -4
  20. package/dist/tests/execute_validation.test.js +72 -17
  21. package/dist/tests/help.test.js +16 -9
  22. package/package.json +1 -1
  23. package/sdk-dist/api/admin/app.ts +8 -1
  24. package/sdk-dist/docs/runtime-reference/README.md +24 -16
  25. package/sdk-dist/docs/runtime-reference/app-discovery.md +68 -0
  26. package/sdk-dist/docs/runtime-reference/entity-schema/README.md +2 -2
  27. package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +2 -2
  28. package/sdk-dist/docs/runtime-reference/entity-schema.md +2 -2
  29. package/sdk-dist/docs/runtime-reference/examples/README.md +1 -1
  30. package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +4 -4
  31. package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +1 -1
  32. package/sdk-dist/docs/runtime-reference/row-crud.md +6 -6
  33. package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +5 -5
  34. package/sdk-dist/docs/runtime-reference/table-lifecycle.md +24 -8
  35. package/sdk-dist/docs/runtime-reference/uploads.md +1 -1
  36. package/sdk-dist/generated/command_registry.generated.ts +40 -163
  37. package/sdk-dist/generated/type_index.generated.ts +0 -12
  38. package/sdk-dist/generated/zod_registry.generated.ts +0 -36
  39. package/sdk-dist/types/app.ts +7 -0
  40. package/src/generated/command_registry.generated.ts +40 -163
  41. package/src/generated/type_index.generated.ts +0 -12
  42. package/src/generated/zod_registry.generated.ts +0 -36
  43. package/src/runtime/command_registry.ts +38 -2
  44. package/src/runtime/execute.ts +127 -9
  45. package/src/runtime/zod_registry.ts +25 -7
  46. package/src/tests/command_registry.test.ts +13 -4
  47. package/src/tests/execute_validation.test.ts +78 -17
  48. package/src/tests/help.test.ts +17 -9
  49. 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', ['workflow', 'delete-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', '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 }));
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', ['workflow', '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 }));
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', ['workflow', '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 }));
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', ['workflow', '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 }));
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', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
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', ['workflow', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async (url, init) => {
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', ['workflow', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
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', ['workflow', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
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', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'query-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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', ['workflow', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
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');
@@ -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', ['workflow', '--help'], undefined, async () => new Response('{}'));
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', ['workflow', 'delete-entity-row', '--help'], undefined, async () => new Response('{}'));
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', ['workflow', 'update-entity-row', '--help'], undefined, async () => new Response('{}'));
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', ['workflow', 'query-entity', '--help'], undefined, async () => new Response('{}'));
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 workflow insert-entity/);
67
- assert.match(out.text, /arcubase workflow query-entity/);
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 row operation, not an arcubase-admin command');
73
- assert.equal(error.details?.hint, 'use: arcubase workflow insert-entity');
74
- assert.ok((error.details?.suggestions ?? []).includes('arcubase workflow insert-entity'));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carthooks/arcubase-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Arcubase runtime CLI for admin and user command execution",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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 workflow insert-entity --app-id <app_id> --entity-id <entity_id> --body-file insert.json
58
- arcubase workflow query-entity --app-id <app_id> --entity-id <entity_id> --body-file query.json
59
- arcubase workflow update-entity-row --app-id <app_id> --entity-id <entity_id> --row-id <row_id> --body-file update.json
60
- arcubase workflow delete-entity-row --app-id <app_id> --entity-id <entity_id> --body-file delete.json
61
- arcubase workflow restore-entity-row --app-id <app_id> --entity-id <entity_id> --body-file restore.json
62
- arcubase workflow query-entity-selection --app-id <app_id> --entity-id <entity_id> --action archive --body-file selection.json
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 workflow insert-entity`
72
- - do not use `arcubase-admin workflow query-entity`
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 workflow query-entity \
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 `table-lifecycle.md`
144
- 2. read `entity-schema.md`
145
- 3. read `entity-schema/README.md`
146
- 4. open the exact field-type page you need
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 workflow delete-entity-row`
58
- - `arcubase workflow restore-entity-row`
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": { ... } }]`
@@ -43,8 +43,8 @@ Subform field.
43
43
 
44
44
  For row commands such as:
45
45
 
46
- - `arcubase workflow insert-entity`
47
- - `arcubase workflow update-entity-row`
46
+ - `arcubase rows insert-entity`
47
+ - `arcubase rows update-entity-row`
48
48
 
49
49
  the current writable row shape is:
50
50
 
@@ -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 workflow query-entity`
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 workflow query-entity ...` succeeds against the saved table
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 workflow.
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 workflow.
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 workflow
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 workflow insert-entity --app-id <app_id> --entity-id <entity_id> --body-file sales-order.row.insert.json
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 workflow query-entity --app-id <app_id> --entity-id <entity_id> --body-file sales-order.query.json
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
@@ -76,4 +76,4 @@ Purpose:
76
76
  - country-specific code naming
77
77
  - tenant-specific internal labels
78
78
  - region-specific channel names
79
- - company-specific workflow terms
79
+ - company-specific process terms
@@ -7,7 +7,7 @@ Use this page for direct row operations with `arcubase`.
7
7
  Command:
8
8
 
9
9
  ```bash
10
- arcubase workflow insert-entity \
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 workflow query-entity \
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 workflow get-entity-row \
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 workflow update-entity-row \
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 workflow delete-entity-row \
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 workflow restore-entity-row \
170
+ arcubase rows restore-entity-row \
171
171
  --app-id <app_id> \
172
172
  --entity-id <entity_id> \
173
173
  --body-file restore.json