@ductape/cli 0.3.20 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -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
|
+
}
|
package/dist/commands/db.d.ts
CHANGED
|
@@ -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).
|
|
8
|
-
* (uses the access-key-backed sdk-proxy, same as
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
package/dist/commands/db.js
CHANGED
|
@@ -47,14 +47,29 @@ 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
|
+
const DB_ACTION_OPERATIONS = ['query', 'insert', 'update', 'delete', 'aggregate', 'count'];
|
|
52
|
+
function validateDatabaseActionCreateBody(body) {
|
|
53
|
+
const operation = String(body.operation ?? '');
|
|
54
|
+
if (!DB_ACTION_OPERATIONS.includes(operation)) {
|
|
55
|
+
throw new Error(`Invalid database action operation "${operation}". Use one of: ${DB_ACTION_OPERATIONS.join(', ')}. ` +
|
|
56
|
+
'For MongoDB find/findOne actions use operation "query".');
|
|
57
|
+
}
|
|
58
|
+
const template = body.template;
|
|
59
|
+
if (!template || (typeof template !== 'object' && typeof template !== 'string')) {
|
|
60
|
+
throw new Error('template is required and must be an object for MongoDB or a SQL string.');
|
|
61
|
+
}
|
|
62
|
+
if (JSON.stringify(template).includes('$Input{')) {
|
|
63
|
+
throw new Error('Database action placeholders use {{name}}, not $Input{name}. ' +
|
|
64
|
+
'Example MongoDB query template: { "where": { "email": "{{email}}" } }.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
51
67
|
/**
|
|
52
68
|
* Database Actions — reusable, parameterized query/mutation definitions saved against a
|
|
53
|
-
* database component (databases.action.* in the SDK).
|
|
54
|
-
* (uses the access-key-backed sdk-proxy, same as
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* of these, not a raw {table, where} shape.
|
|
69
|
+
* database component (databases.action.* in the SDK). Definition management is
|
|
70
|
+
* administrative metadata (uses the access-key-backed sdk-proxy, same as
|
|
71
|
+
* `resources <type> create`). Runtime execution uses databases.execute with an `action`
|
|
72
|
+
* tag. Direct query/insert/update/delete calls keep their raw {table, where, ...} shape.
|
|
58
73
|
*
|
|
59
74
|
* Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
|
|
60
75
|
* qualified here (the SDK's 2-part fallback only works inside a single long-lived process
|
|
@@ -76,6 +91,7 @@ export async function runDbActions(verb, opts, extraArgs) {
|
|
|
76
91
|
throw new Error('create requires a body: { tag: "product:database:action", name, tableName, ' +
|
|
77
92
|
'operation, template, description?, filterTemplate? } (pass -f <file.json>).');
|
|
78
93
|
}
|
|
94
|
+
validateDatabaseActionCreateBody(body);
|
|
79
95
|
method = 'action.create';
|
|
80
96
|
params = [body];
|
|
81
97
|
break;
|
|
@@ -109,6 +125,22 @@ export async function runDbActions(verb, opts, extraArgs) {
|
|
|
109
125
|
method = 'action.delete';
|
|
110
126
|
params = [tag];
|
|
111
127
|
break;
|
|
128
|
+
case 'dispatch': {
|
|
129
|
+
const body = readJsonBody(opts.file);
|
|
130
|
+
if (!body || Object.keys(body).length === 0)
|
|
131
|
+
throw new Error('dispatch requires --action-file <file.json>');
|
|
132
|
+
method = 'action.dispatch';
|
|
133
|
+
params = [body];
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case 'execute': {
|
|
137
|
+
const body = readJsonBody(opts.file);
|
|
138
|
+
if (!body || Object.keys(body).length === 0)
|
|
139
|
+
throw new Error('execute requires --action-file <file.json>');
|
|
140
|
+
method = 'execute';
|
|
141
|
+
params = [body];
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
112
144
|
}
|
|
113
145
|
const result = await proxy.execute('databases', method, params);
|
|
114
146
|
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).
|
|
811
|
-
'
|
|
812
|
-
'
|
|
813
|
-
'
|
|
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
|
-
.description('Create a database action
|
|
817
|
-
.requiredOption('-
|
|
817
|
+
.description('Create a saved database action; MongoDB query example uses operation "query" and template { "where": { "email": "{{email}}" } }')
|
|
818
|
+
.requiredOption('--action-file <path>', 'JSON definition with tag, name, description, tableName, operation, and template')
|
|
818
819
|
.option('--json', 'JSON output')
|
|
819
|
-
.action(wrap((opts) => runDbActions('create', { file: opts.
|
|
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('-
|
|
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.
|
|
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('
|
|
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) =>
|
|
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