@ductape/cli 0.3.18 → 0.3.20

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.
@@ -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,
@@ -2,4 +2,22 @@ export declare function runDb(method: string, opts: {
2
2
  file?: string;
3
3
  json?: boolean;
4
4
  }): Promise<void>;
5
+ /**
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.
12
+ *
13
+ * Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
14
+ * qualified here (the SDK's 2-part fallback only works inside a single long-lived process
15
+ * that already has that product's builder cached, which a fresh CLI invocation never has).
16
+ */
17
+ export declare function runDbActions(verb: string, opts: {
18
+ tag?: string;
19
+ database?: string;
20
+ file?: string;
21
+ json?: boolean;
22
+ }, extraArgs: string[]): Promise<void>;
5
23
  export declare function runDbContext(json: boolean): void;
@@ -1,7 +1,7 @@
1
1
  import { readJsonBody } from '../lib/read-body.js';
2
2
  import { setDatabaseContext } from '../lib/context-store.js';
3
3
  import { DB_PROXY_METHODS } from '../lib/db-methods.js';
4
- import { getDbContext, getDbProxy, requireSession } from '../lib/proxy/context.js';
4
+ import { getDbContext, getDbProxy, getSdkProxy, requireSession } from '../lib/proxy/context.js';
5
5
  import { loadRuntimeContext } from '../lib/context-store.js';
6
6
  import { printJson } from '../lib/output.js';
7
7
  export async function runDb(method, opts) {
@@ -47,6 +47,72 @@ 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'];
51
+ /**
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.
58
+ *
59
+ * Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
60
+ * qualified here (the SDK's 2-part fallback only works inside a single long-lived process
61
+ * that already has that product's builder cached, which a fresh CLI invocation never has).
62
+ */
63
+ export async function runDbActions(verb, opts, extraArgs) {
64
+ const v = verb.toLowerCase();
65
+ if (!DB_ACTION_VERBS.includes(v)) {
66
+ throw new Error(`Unknown db actions verb "${verb}". Use: ${DB_ACTION_VERBS.join(', ')}`);
67
+ }
68
+ const proxy = getSdkProxy();
69
+ const tag = opts.tag ?? extraArgs[0];
70
+ let method;
71
+ let params;
72
+ switch (v) {
73
+ case 'create': {
74
+ const body = readJsonBody(opts.file);
75
+ if (!body || Object.keys(body).length === 0) {
76
+ throw new Error('create requires a body: { tag: "product:database:action", name, tableName, ' +
77
+ 'operation, template, description?, filterTemplate? } (pass -f <file.json>).');
78
+ }
79
+ method = 'action.create';
80
+ params = [body];
81
+ break;
82
+ }
83
+ case 'update': {
84
+ if (!tag)
85
+ throw new Error('update requires the action tag ("product:database:action")');
86
+ const body = readJsonBody(opts.file) ?? {};
87
+ method = 'action.update';
88
+ params = [{ tag, ...body }];
89
+ break;
90
+ }
91
+ case 'get':
92
+ if (!tag)
93
+ throw new Error('get requires the action tag ("product:database:action")');
94
+ method = 'action.fetch';
95
+ params = [tag];
96
+ break;
97
+ case 'list': {
98
+ const databaseTag = opts.database ?? tag;
99
+ if (!databaseTag) {
100
+ throw new Error('list requires --database <product_tag:database_tag>');
101
+ }
102
+ method = 'action.fetchAll';
103
+ params = [databaseTag];
104
+ break;
105
+ }
106
+ case 'delete':
107
+ if (!tag)
108
+ throw new Error('delete requires the action tag ("product:database:action")');
109
+ method = 'action.delete';
110
+ params = [tag];
111
+ break;
112
+ }
113
+ const result = await proxy.execute('databases', method, params);
114
+ printJson(result, Boolean(opts.json));
115
+ }
50
116
  export function runDbContext(json) {
51
117
  const session = requireSession();
52
118
  const ctx = getDbContext(session);
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { runInstall } from './commands/install.js';
15
15
  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
- import { runDb, runDbContext } from './commands/db.js';
18
+ import { runDb, runDbActions, runDbContext } from './commands/db.js';
19
19
  import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
20
20
  import { runFeaturesSync } from './commands/features-sync.js';
21
21
  import { runDbSchemaGenerate, runDbSchemaPush } from './commands/db-schema.js';
@@ -805,6 +805,43 @@ dbMigrate
805
805
  .option('--db <tag>', 'Limit to one db entry')
806
806
  .option('-n <count>', 'Number of migrations to roll back', '1')
807
807
  .action(wrap((opts) => runDbMigrateRollback({ env: opts.env, db: opts.db, n: Number(opts.n) })));
808
+ const dbActions = db
809
+ .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.');
814
+ dbActions
815
+ .command('create')
816
+ .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
+ .option('--json', 'JSON output')
819
+ .action(wrap((opts) => runDbActions('create', { file: opts.file, json: opts.json }, [])));
820
+ dbActions
821
+ .command('update')
822
+ .description('Update an existing database action')
823
+ .requiredOption('-t, --tag <tag>', 'Action tag ("product:database:action")')
824
+ .requiredOption('-f, --file <path>', 'JSON file with the fields to change')
825
+ .option('--json', 'JSON output')
826
+ .action(wrap((opts) => runDbActions('update', { tag: opts.tag, file: opts.file, json: opts.json }, [])));
827
+ dbActions
828
+ .command('get')
829
+ .description('Fetch one database action')
830
+ .argument('<tag>', 'Action tag ("product:database:action")')
831
+ .option('--json', 'JSON output')
832
+ .action(wrap((tag, opts) => runDbActions('get', { tag, json: opts.json }, [])));
833
+ dbActions
834
+ .command('list')
835
+ .description('List all actions on a database')
836
+ .requiredOption('-d, --database <tag>', 'Database tag ("product_tag:database_tag")')
837
+ .option('--json', 'JSON output')
838
+ .action(wrap((opts) => runDbActions('list', { database: opts.database, json: opts.json }, [])));
839
+ dbActions
840
+ .command('delete')
841
+ .description('Delete a database action')
842
+ .argument('<tag>', 'Action tag ("product:database:action")')
843
+ .option('--json', 'JSON output')
844
+ .action(wrap((tag, opts) => runDbActions('delete', { tag, json: opts.json }, [])));
808
845
  const dbSchema = db.command('schema').description('Schema management for ductape/database/schema.json');
809
846
  dbSchema
810
847
  .command('generate')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
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",