@ductape/cli 0.3.19 → 0.3.21

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.
@@ -0,0 +1,8 @@
1
+ export type ComponentActionKind = 'graph' | 'vector';
2
+ export type ComponentActionVerb = 'create' | 'update' | 'get' | 'list' | 'delete' | 'execute';
3
+ export declare function runComponentActions(kind: ComponentActionKind, verb: ComponentActionVerb, opts: {
4
+ resource?: string;
5
+ action?: string;
6
+ file?: string;
7
+ json?: boolean;
8
+ }): Promise<void>;
@@ -0,0 +1,45 @@
1
+ import { readJsonBody } from '../lib/read-body.js';
2
+ import { getSdkProxy, requireSession } from '../lib/proxy/context.js';
3
+ import { printJson } from '../lib/output.js';
4
+ export async function runComponentActions(kind, verb, opts) {
5
+ const session = requireSession();
6
+ const body = readJsonBody(opts.file) ?? {};
7
+ const resourceKey = kind === 'graph' ? 'graph' : 'vector';
8
+ const resource = opts.resource ?? String(body[resourceKey] ?? body[`${resourceKey}Tag`] ?? '');
9
+ const action = opts.action ?? String(body.actionTag ?? body.action ?? '');
10
+ const base = { product: session.project.product_tag, ...body };
11
+ let method;
12
+ let params;
13
+ if (verb === 'create') {
14
+ if (!opts.file)
15
+ throw new Error(`${kind} actions create requires --action-file <file.json>`);
16
+ method = kind === 'graph' ? 'action.create' : 'actions.create';
17
+ params = [base];
18
+ }
19
+ else if (verb === 'update') {
20
+ if (!opts.file)
21
+ throw new Error(`${kind} actions update requires --action-file <file.json>`);
22
+ method = kind === 'graph' ? 'action.update' : 'actions.update';
23
+ params = [base];
24
+ }
25
+ else if (verb === 'list') {
26
+ if (!resource)
27
+ throw new Error(`${kind} actions list requires --${kind} <tag>`);
28
+ method = kind === 'graph' ? 'action.fetchAll' : 'actions.fetchAll';
29
+ params = [{ product: session.project.product_tag, [resourceKey]: resource }];
30
+ }
31
+ else if (verb === 'get' || verb === 'delete') {
32
+ if (!resource || !action)
33
+ throw new Error(`${kind} actions ${verb} requires <action> --${kind} <tag>`);
34
+ method = kind === 'graph' ? `action.${verb === 'get' ? 'fetch' : 'delete'}` : `actions.${verb === 'get' ? 'fetch' : 'delete'}`;
35
+ params = [{ product: session.project.product_tag, [resourceKey]: resource, actionTag: action }];
36
+ }
37
+ else {
38
+ if (!opts.file)
39
+ throw new Error(`${kind} actions execute requires --action-file <file.json>`);
40
+ method = kind === 'graph' ? 'action.execute' : 'actions.execute';
41
+ params = [{ product: session.project.product_tag, env: session.project.env_slug, ...body }];
42
+ }
43
+ const result = await getSdkProxy(session).execute(kind, method, params);
44
+ printJson(result, Boolean(opts.json));
45
+ }
@@ -259,6 +259,11 @@ export async function runDbSchemaGenerate(opts) {
259
259
  },
260
260
  ];
261
261
  for (const { fieldName, unique } of parsed.indexFields) {
262
+ // createCollection already materializes fields marked unique. Emitting a
263
+ // second named createIndex operation gives MongoDB the same key/options
264
+ // under a different name and fails with IndexOptionsConflict.
265
+ if (unique)
266
+ continue;
262
267
  up.push({
263
268
  type: 'createIndex',
264
269
  collection: tableName,
@@ -4,11 +4,10 @@ export declare function runDb(method: string, opts: {
4
4
  }): Promise<void>;
5
5
  /**
6
6
  * Database Actions — reusable, parameterized query/mutation definitions saved against a
7
- * database component (databases.action.* in the SDK). This is administrative metadata
8
- * (uses the access-key-backed sdk-proxy, same as `resources <type> create`), not a runtime
9
- * query it never connects to the live database. Required for any Feature step that calls
10
- * ctx.database.query/insert/update/delete, since those take an `event` tag pointing at one
11
- * of these, not a raw {table, where} shape.
7
+ * database component (databases.action.* in the SDK). Definition management is
8
+ * administrative metadata (uses the access-key-backed sdk-proxy, same as
9
+ * `resources <type> create`). Runtime execution uses databases.execute with an `action`
10
+ * tag. Direct query/insert/update/delete calls keep their raw {table, where, ...} shape.
12
11
  *
13
12
  * Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
14
13
  * qualified here (the SDK's 2-part fallback only works inside a single long-lived process
@@ -47,14 +47,13 @@ export async function runDb(method, opts) {
47
47
  }
48
48
  printJson(result, Boolean(opts.json));
49
49
  }
50
- const DB_ACTION_VERBS = ['create', 'update', 'get', 'list', 'delete'];
50
+ const DB_ACTION_VERBS = ['create', 'update', 'get', 'list', 'delete', 'execute', 'dispatch'];
51
51
  /**
52
52
  * Database Actions — reusable, parameterized query/mutation definitions saved against a
53
- * database component (databases.action.* in the SDK). This is administrative metadata
54
- * (uses the access-key-backed sdk-proxy, same as `resources <type> create`), not a runtime
55
- * query it never connects to the live database. Required for any Feature step that calls
56
- * ctx.database.query/insert/update/delete, since those take an `event` tag pointing at one
57
- * of these, not a raw {table, where} shape.
53
+ * database component (databases.action.* in the SDK). Definition management is
54
+ * administrative metadata (uses the access-key-backed sdk-proxy, same as
55
+ * `resources <type> create`). Runtime execution uses databases.execute with an `action`
56
+ * tag. Direct query/insert/update/delete calls keep their raw {table, where, ...} shape.
58
57
  *
59
58
  * Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
60
59
  * qualified here (the SDK's 2-part fallback only works inside a single long-lived process
@@ -109,6 +108,22 @@ export async function runDbActions(verb, opts, extraArgs) {
109
108
  method = 'action.delete';
110
109
  params = [tag];
111
110
  break;
111
+ case 'dispatch': {
112
+ const body = readJsonBody(opts.file);
113
+ if (!body || Object.keys(body).length === 0)
114
+ throw new Error('dispatch requires --action-file <file.json>');
115
+ method = 'action.dispatch';
116
+ params = [body];
117
+ break;
118
+ }
119
+ case 'execute': {
120
+ const body = readJsonBody(opts.file);
121
+ if (!body || Object.keys(body).length === 0)
122
+ throw new Error('execute requires --action-file <file.json>');
123
+ method = 'execute';
124
+ params = [body];
125
+ break;
126
+ }
112
127
  }
113
128
  const result = await proxy.execute('databases', method, params);
114
129
  printJson(result, Boolean(opts.json));
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { runStart, runStop, runStatus } from './commands/platform.js';
16
16
  import { runResourceCrud, runResourcesList, runEventTopicCrud, runNotificationMessageCrud } from './commands/resources.js';
17
17
  import { runCloud, runCloudPreflight } from './commands/cloud.js';
18
18
  import { runDb, runDbActions, runDbContext } from './commands/db.js';
19
+ import { runComponentActions } from './commands/component-actions.js';
19
20
  import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
20
21
  import { runFeaturesSync } from './commands/features-sync.js';
21
22
  import { runDbSchemaGenerate, runDbSchemaPush } from './commands/db-schema.js';
@@ -807,23 +808,23 @@ dbMigrate
807
808
  .action(wrap((opts) => runDbMigrateRollback({ env: opts.env, db: opts.db, n: Number(opts.n) })));
808
809
  const dbActions = db
809
810
  .command('actions')
810
- .description('Reusable database action definitions (saved parameterized queries). Required for any ' +
811
- 'Feature step that uses ctx.database.query/insert/update/delete those take an `event` ' +
812
- 'tag pointing at one of these, not a raw table/where shape. Administrative (access-key), ' +
813
- 'not a runtime query — create/update never touch the live database.');
811
+ .description('Reusable database action definitions (saved parameterized queries). Execute them with ' +
812
+ 'database.execute({ database, action, input }) in Features, databases.execute({ product, ' +
813
+ 'env, database, action, input }) in the SDK, or `db actions execute` in the CLI. Direct ' +
814
+ 'query/insert/update/delete calls keep their raw table/where/data shape.');
814
815
  dbActions
815
816
  .command('create')
816
817
  .description('Create a database action from a file: { tag: "product:database:action", name, tableName, operation, template }')
817
- .requiredOption('-f, --file <path>', 'JSON file with the action definition')
818
+ .requiredOption('--action-file <path>', 'JSON file with the action definition')
818
819
  .option('--json', 'JSON output')
819
- .action(wrap((opts) => runDbActions('create', { file: opts.file, json: opts.json }, [])));
820
+ .action(wrap((opts) => runDbActions('create', { file: opts.actionFile, json: opts.json }, [])));
820
821
  dbActions
821
822
  .command('update')
822
823
  .description('Update an existing database action')
823
824
  .requiredOption('-t, --tag <tag>', 'Action tag ("product:database:action")')
824
- .requiredOption('-f, --file <path>', 'JSON file with the fields to change')
825
+ .requiredOption('--action-file <path>', 'JSON file with the fields to change')
825
826
  .option('--json', 'JSON output')
826
- .action(wrap((opts) => runDbActions('update', { tag: opts.tag, file: opts.file, json: opts.json }, [])));
827
+ .action(wrap((opts) => runDbActions('update', { tag: opts.tag, file: opts.actionFile, json: opts.json }, [])));
827
828
  dbActions
828
829
  .command('get')
829
830
  .description('Fetch one database action')
@@ -842,6 +843,18 @@ dbActions
842
843
  .argument('<tag>', 'Action tag ("product:database:action")')
843
844
  .option('--json', 'JSON output')
844
845
  .action(wrap((tag, opts) => runDbActions('delete', { tag, json: opts.json }, [])));
846
+ dbActions
847
+ .command('dispatch')
848
+ .description('Dispatch a database action immediately or on a schedule')
849
+ .requiredOption('--action-file <path>', 'JSON body: { product, env, database, action, input, schedule? }')
850
+ .option('--json', 'JSON output')
851
+ .action(wrap((opts) => runDbActions('dispatch', { file: opts.actionFile, json: opts.json }, [])));
852
+ dbActions
853
+ .command('execute')
854
+ .description('Execute a saved database action synchronously')
855
+ .requiredOption('--action-file <path>', 'JSON body: { product, env, database, action, input }')
856
+ .option('--json', 'JSON output')
857
+ .action(wrap((opts) => runDbActions('execute', { file: opts.actionFile, json: opts.json }, [])));
845
858
  const dbSchema = db.command('schema').description('Schema management for ductape/database/schema.json');
846
859
  dbSchema
847
860
  .command('generate')
@@ -873,11 +886,56 @@ features
873
886
  .argument('[filter]', 'Optional substring passed through to the project\'s features:sync script')
874
887
  .action(wrap((filter) => runFeaturesSync({ filter })));
875
888
  const graph = program.command('graph').description('Graph runtime (graph-proxy)');
889
+ const graphActions = graph.command('actions').description('Reusable graph actions');
890
+ for (const verb of ['create', 'update', 'get', 'list', 'delete', 'execute']) {
891
+ const command = graphActions.command(verb);
892
+ if (verb === 'get' || verb === 'delete')
893
+ command.argument('<action>');
894
+ if (verb !== 'create' && verb !== 'update' && verb !== 'execute')
895
+ command.requiredOption('--graph <tag>');
896
+ if (verb === 'create' || verb === 'update' || verb === 'execute')
897
+ command.requiredOption('--action-file <path>');
898
+ command.option('--json').action(wrap((actionOrOpts, maybeOpts) => {
899
+ const hasArgument = verb === 'get' || verb === 'delete';
900
+ const opts = hasArgument ? maybeOpts : actionOrOpts;
901
+ return runComponentActions('graph', verb, {
902
+ action: hasArgument ? actionOrOpts : undefined,
903
+ resource: opts.graph,
904
+ file: opts.actionFile,
905
+ json: opts.json,
906
+ });
907
+ }));
908
+ }
876
909
  graph
877
- .argument('<verb>', 'connect | query | createAction | validateAction | updateAction | …')
910
+ .argument('[verb]', 'connect | query | createAction | validateAction | updateAction | …')
878
911
  .option('-f, --file <path>')
879
912
  .option('--json', 'JSON output')
880
- .action(wrap((verb, opts) => runGraph(verb, { file: opts.file, json: opts.json })));
913
+ .action(wrap((verb, opts) => {
914
+ if (!verb)
915
+ throw new Error('Specify a graph verb or use `graph actions --help`');
916
+ return runGraph(verb, { file: opts.file, json: opts.json });
917
+ }));
918
+ const vector = program.command('vector').description('Vector runtime and reusable actions');
919
+ const vectorActions = vector.command('actions').description('Reusable vector actions');
920
+ for (const verb of ['create', 'update', 'get', 'list', 'delete', 'execute']) {
921
+ const command = vectorActions.command(verb);
922
+ if (verb === 'get' || verb === 'delete')
923
+ command.argument('<action>');
924
+ if (verb !== 'create' && verb !== 'update' && verb !== 'execute')
925
+ command.requiredOption('--vector <tag>');
926
+ if (verb === 'create' || verb === 'update' || verb === 'execute')
927
+ command.requiredOption('--action-file <path>');
928
+ command.option('--json').action(wrap((actionOrOpts, maybeOpts) => {
929
+ const hasArgument = verb === 'get' || verb === 'delete';
930
+ const opts = hasArgument ? maybeOpts : actionOrOpts;
931
+ return runComponentActions('vector', verb, {
932
+ action: hasArgument ? actionOrOpts : undefined,
933
+ resource: opts.vector,
934
+ file: opts.actionFile,
935
+ json: opts.json,
936
+ });
937
+ }));
938
+ }
881
939
  program
882
940
  .command('secrets <verb>')
883
941
  .description('Workspace secrets CRUD')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "description": "Ductape CLI — local platform, login, link projects, and manage resources via the proxy (Workbench-compatible)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",