@happyvertical/smrt-core 0.44.0 → 0.45.0
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/AGENTS.md +8 -8
- package/agents/change-feed.md +3 -2
- package/agents/generators.md +34 -0
- package/agents/revision-guard.md +66 -0
- package/dist/browser.js +2 -1
- package/dist/cascade.d.ts +5 -9
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +65 -30
- package/dist/cascade.js.map +1 -1
- package/dist/change-feed.d.ts +60 -1
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +464 -27
- package/dist/change-feed.js.map +1 -1
- package/dist/change-signals.d.ts.map +1 -1
- package/dist/change-signals.js +13 -11
- package/dist/change-signals.js.map +1 -1
- package/dist/embedded-write-queue.d.ts +8 -0
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +12 -2
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/cli.d.ts.map +1 -1
- package/dist/generators/cli.js +10 -18
- package/dist/generators/cli.js.map +1 -1
- package/dist/generators/custom-action.d.ts +216 -0
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +256 -1
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/generators/index.d.ts +2 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +3 -2
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +14 -39
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/preflight-route.d.ts +151 -0
- package/dist/generators/preflight-route.d.ts.map +1 -0
- package/dist/generators/preflight-route.js +194 -0
- package/dist/generators/preflight-route.js.map +1 -0
- package/dist/generators/rest.d.ts +12 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +14 -15
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/tool-schema.d.ts.map +1 -1
- package/dist/generators/tool-schema.js +2 -8
- package/dist/generators/tool-schema.js.map +1 -1
- package/dist/generators.js +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +283 -35
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +6 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +8 -2
- package/dist/object.d.ts +31 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +57 -9
- package/dist/object.js.map +1 -1
- package/dist/registry/framework-base-classes.d.ts +10 -0
- package/dist/registry/framework-base-classes.d.ts.map +1 -0
- package/dist/registry/framework-base-classes.js +92 -0
- package/dist/registry/framework-base-classes.js.map +1 -0
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +2 -0
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +27 -6
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -1
- package/dist/registry.js.map +1 -1
- package/dist/revision-guard.d.ts +84 -0
- package/dist/revision-guard.d.ts.map +1 -0
- package/dist/revision-guard.js +120 -0
- package/dist/revision-guard.js.map +1 -0
- package/dist/scanner/manifest-generator.d.ts +46 -16
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +198 -45
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/smrt-knowledge.json +18 -9
- package/dist/system/bootstrap.d.ts.map +1 -1
- package/dist/system/bootstrap.js +2 -1
- package/dist/system/bootstrap.js.map +1 -1
- package/dist/system/schema.d.ts +75 -2
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +326 -4
- package/dist/system/schema.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +48 -23
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +33 -7
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +88 -17
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +2 -0
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +28 -5
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedded-write-queue.js","names":[],"sources":["../src/embedded-write-queue.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\n/**\n * In-process write serialization for embedded database engines (#2360).\n *\n * File-backed SQLite (libsql) allows one writer at a time and the SDK opens a\n * NEW connection for every null-aware upsert attempt (`transaction(\"write\")`),\n * with no busy timeout on either connection. Any other write on the root\n * connection while such a transaction is open — most commonly the change-feed\n * append that follows every model save — collides as `SQLITE_BUSY`, and under\n * sustained concurrency (a `Promise.all` of creates) the two sides livelock\n * through every retry tier. Before the tenant-led conflict target (#2360) the\n * null-aware path was rare (custom conflict keys with NULL values, #1246);\n * with `tenant_id` leading every tenant-scoped default key, EVERY NULL-tenant\n * (global) create takes it, so the collision became the steady state.\n *\n * The queue makes the process a single writer per embedded database for:\n *\n * - a `save()` whose upsert conflict values contain NULL (the only shape the\n * SDK routes through a second-connection transaction), and\n * - the change-feed append (a root-connection write that follows every save).\n * - every model save and delete, so the embedded revision-CAS compare/upsert\n * fallback cannot race an ordinary save, natural-key create, or delete.\n * - a complete root `SmrtObject.withTransaction()` callback, with writes on\n * its bound handle re-entering the same hold instead of self-deadlocking.\n *\n * Ordinary save/delete holds contain database calls only — never an\n * interceptor or hook. Explicit transaction callbacks are caller-controlled,\n * so their bound model writes re-enter the existing hold. PostgreSQL is never\n * serialized here:\n * its null-aware upsert already arbitrates through advisory locks\n * server-side, and cross-process writers exist that an in-process queue could\n * not order anyway (which is also why this queue is a mitigation for embedded\n * engines' in-process writers, not a cross-process guarantee — embedded\n * engines have no cross-process writers in supported deployments).\n */\n\n/** Queue tail per database identity (URL string, or the handle itself). */\nconst urlQueues = new Map<string, Promise<unknown>>();\nconst handleQueues = new WeakMap<object, Promise<unknown>>();\nconst activeQueueKeys = new AsyncLocalStorage<ReadonlySet<string | object>>();\n\ninterface QueueableDatabase {\n url?: string;\n}\n\n/**\n * Whether `db` is an embedded engine (SQLite/DuckDB/JSON — anything that is\n * not PostgreSQL). Handles without a URL are treated as embedded: serializing\n * them is at worst a throughput cost, while not serializing an embedded\n * handle reintroduces the livelock.\n */\nexport function isEmbeddedDatabase(db: QueueableDatabase): boolean {\n return
|
|
1
|
+
{"version":3,"file":"embedded-write-queue.js","names":[],"sources":["../src/embedded-write-queue.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\n/**\n * In-process write serialization for embedded database engines (#2360).\n *\n * File-backed SQLite (libsql) allows one writer at a time and the SDK opens a\n * NEW connection for every null-aware upsert attempt (`transaction(\"write\")`),\n * with no busy timeout on either connection. Any other write on the root\n * connection while such a transaction is open — most commonly the change-feed\n * append that follows every model save — collides as `SQLITE_BUSY`, and under\n * sustained concurrency (a `Promise.all` of creates) the two sides livelock\n * through every retry tier. Before the tenant-led conflict target (#2360) the\n * null-aware path was rare (custom conflict keys with NULL values, #1246);\n * with `tenant_id` leading every tenant-scoped default key, EVERY NULL-tenant\n * (global) create takes it, so the collision became the steady state.\n *\n * The queue makes the process a single writer per embedded database for:\n *\n * - a `save()` whose upsert conflict values contain NULL (the only shape the\n * SDK routes through a second-connection transaction), and\n * - the change-feed append (a root-connection write that follows every save).\n * - every model save and delete, so the embedded revision-CAS compare/upsert\n * fallback cannot race an ordinary save, natural-key create, or delete.\n * - a complete root `SmrtObject.withTransaction()` callback, with writes on\n * its bound handle re-entering the same hold instead of self-deadlocking.\n *\n * Ordinary save/delete holds contain database calls only — never an\n * interceptor or hook. Explicit transaction callbacks are caller-controlled,\n * so their bound model writes re-enter the existing hold. PostgreSQL is never\n * serialized here:\n * its null-aware upsert already arbitrates through advisory locks\n * server-side, and cross-process writers exist that an in-process queue could\n * not order anyway (which is also why this queue is a mitigation for embedded\n * engines' in-process writers, not a cross-process guarantee — embedded\n * engines have no cross-process writers in supported deployments).\n */\n\n/** Queue tail per database identity (URL string, or the handle itself). */\nconst urlQueues = new Map<string, Promise<unknown>>();\nconst handleQueues = new WeakMap<object, Promise<unknown>>();\nconst activeQueueKeys = new AsyncLocalStorage<ReadonlySet<string | object>>();\n\ninterface QueueableDatabase {\n url?: string;\n}\n\n/**\n * Whether `db` is an embedded engine (SQLite/DuckDB/JSON — anything that is\n * not PostgreSQL). Handles without a URL are treated as embedded: serializing\n * them is at worst a throughput cost, while not serializing an embedded\n * handle reintroduces the livelock.\n */\nexport function isEmbeddedDatabase(db: QueueableDatabase): boolean {\n return !isPostgresDatabase(db);\n}\n\n/**\n * Whether `db` is a PostgreSQL handle.\n *\n * The revision compare-and-swap predicate is dialect-specific (#2620): only\n * PostgreSQL needs the millisecond-truncating, timezone-tolerant guard, and\n * only PostgreSQL can evaluate `date_trunc`.\n */\nexport function isPostgresDatabase(db: QueueableDatabase): boolean {\n return /^postgres(?:ql)?:/iu.test(db?.url ?? '');\n}\n\n/**\n * Whether revision compare-and-swap needs the process-local read/upsert path.\n *\n * DuckDB/JSON cannot express the timestamp predicate through the generic\n * update API, and local SQLite has only the process's writers in supported\n * deployments. Remote LibSQL/Turso is different: other processes may write\n * the same database, so it must use one conditional UPDATE at the server.\n */\nexport function usesEmbeddedRevisionFallback(db: QueueableDatabase): boolean {\n const url = db?.url ?? '';\n return isEmbeddedDatabase(db) && !/^(?:https?|libsql):\\/\\//iu.test(url);\n}\n\nfunction queueKey(db: QueueableDatabase): string | object {\n return typeof db?.url === 'string' && db.url.length > 0 ? db.url : db;\n}\n\n/**\n * Run `operation` after every previously queued write on the same database\n * has settled. When `serialize` is false the operation runs immediately.\n */\nexport async function withEmbeddedWriteQueue<T>(\n db: QueueableDatabase,\n serialize: boolean,\n operation: () => Promise<T>,\n): Promise<T> {\n if (!serialize) return operation();\n\n const key = queueKey(db);\n const active = activeQueueKeys.getStore();\n if (active?.has(key)) return operation();\n const previous =\n (typeof key === 'string' ? urlQueues.get(key) : handleQueues.get(key)) ??\n Promise.resolve();\n const run = () =>\n activeQueueKeys.run(new Set([...(active ?? []), key]), operation);\n const next = previous.then(run, run);\n // The stored tail must never reject, or one failed write would poison the\n // queue for every later writer.\n const tail = next.catch(() => undefined);\n if (typeof key === 'string') {\n urlQueues.set(key, tail);\n void tail.finally(() => {\n if (urlQueues.get(key) === tail) urlQueues.delete(key);\n });\n } else {\n handleQueues.set(key, tail);\n }\n return next;\n}\n\n/**\n * Serialize a complete embedded root transaction with ordinary model writes.\n * The transaction handle is registered as a reentrant alias so saves and\n * change-feed appends inside the callback do not queue behind their own root\n * transaction.\n */\nexport async function withEmbeddedWriteTransaction<T>(\n db: DatabaseInterface,\n serialize: boolean,\n operation: (transaction: DatabaseInterface) => Promise<T>,\n): Promise<T> {\n const transaction = db.transaction?.bind(db);\n if (!transaction) {\n throw new Error('Database transaction support is required');\n }\n return withEmbeddedWriteQueue(db, serialize, () =>\n transaction((bound) => {\n if (!serialize) return operation(bound);\n const active = activeQueueKeys.getStore();\n return activeQueueKeys.run(\n new Set([...(active ?? []), queueKey(bound)]),\n () => operation(bound),\n );\n }),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAM,4BAAY,IAAI,IAA8B;AACpD,IAAM,+BAAe,IAAI,QAAkC;AAC3D,IAAM,kBAAkB,IAAI,kBAAgD;;;;;;;AAY5E,SAAgB,mBAAmB,IAAgC;CACjE,OAAO,CAAC,mBAAmB,EAAE;AAC/B;;;;;;;;AASA,SAAgB,mBAAmB,IAAgC;CACjE,OAAO,sBAAsB,KAAK,IAAI,OAAO,EAAE;AACjD;;;;;;;;;AAUA,SAAgB,6BAA6B,IAAgC;CAC3E,MAAM,MAAM,IAAI,OAAO;CACvB,OAAO,mBAAmB,EAAE,KAAK,CAAC,4BAA4B,KAAK,GAAG;AACxE;AAEA,SAAS,SAAS,IAAwC;CACxD,OAAO,OAAO,IAAI,QAAQ,YAAY,GAAG,IAAI,SAAS,IAAI,GAAG,MAAM;AACrE;;;;;AAMA,eAAsB,uBACpB,IACA,WACA,WACY;CACZ,IAAI,CAAC,WAAW,OAAO,UAAU;CAEjC,MAAM,MAAM,SAAS,EAAE;CACvB,MAAM,SAAS,gBAAgB,SAAS;CACxC,IAAI,QAAQ,IAAI,GAAG,GAAG,OAAO,UAAU;CACvC,MAAM,YACH,OAAO,QAAQ,WAAW,UAAU,IAAI,GAAG,IAAI,aAAa,IAAI,GAAG,MACpE,QAAQ,QAAQ;CAClB,MAAM,YACJ,gBAAgB,oBAAI,IAAI,IAAI,CAAC,GAAI,UAAU,CAAC,GAAI,GAAG,CAAC,GAAG,SAAS;CAClE,MAAM,OAAO,SAAS,KAAK,KAAK,GAAG;CAGnC,MAAM,OAAO,KAAK,YAAY,KAAA,CAAS;CACvC,IAAI,OAAO,QAAQ,UAAU;EAC3B,UAAU,IAAI,KAAK,IAAI;EACvB,KAAU,cAAc;GACtB,IAAI,UAAU,IAAI,GAAG,MAAM,MAAM,UAAU,OAAO,GAAG;EACvD,CAAC;CACH,OACE,aAAa,IAAI,KAAK,IAAI;CAE5B,OAAO;AACT;;;;;;;AAQA,eAAsB,6BACpB,IACA,WACA,WACY;CACZ,MAAM,cAAc,GAAG,aAAa,KAAK,EAAE;CAC3C,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO,uBAAuB,IAAI,iBAChC,aAAa,UAAU;EACrB,IAAI,CAAC,WAAW,OAAO,UAAU,KAAK;EACtC,MAAM,SAAS,gBAAgB,SAAS;EACxC,OAAO,gBAAgB,oBACrB,IAAI,IAAI,CAAC,GAAI,UAAU,CAAC,GAAI,SAAS,KAAK,CAAC,CAAC,SACtC,UAAU,KAAK,CACvB;CACF,CAAC,CACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/generators/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/generators/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmCrD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,yCAAyC;IACzC,EAAE,CAAC,EAAE,eAAe,GAAG,QAAQ,CAAC;IAChC,6DAA6D;IAC7D,IAAI,CAAC,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;IACF,sDAAsD;IACtD,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAiCD;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAa;gBAEhB,MAAM,GAAE,SAAc,EAAE,OAAO,GAAE,UAAe;IAU5D,wBAAwB;IACxB,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED,mBAAmB;IACnB,IAAI,OAAO,IAAI,MAAM,GAAG,SAAS,CAEhC;IAED;;;;OAIG;IACH,eAAe,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC;IAMpD;;;;OAIG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CxC;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAkDjB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAWpB;;;;OAIG;YACW,oBAAoB;IA2DlC;;;;;;OAMG;YACW,aAAa;IAS3B;;OAEG;YACW,aAAa;IAwF3B;;;;OAIG;YACW,mBAAmB;IAuFjC,OAAO,CAAC,qBAAqB;IAM7B;;;;OAIG;YACW,WAAW;IAsBzB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAuBrB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA+C3B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IA2BpB,OAAO,CAAC,oBAAoB;IAI5B,qEAAqE;IAC/D,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YA0CzB,QAAQ;IAoBtB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IAIlB,2EAA2E;IAC3E,OAAO,CAAC,WAAW;CAiBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,MAAM,GAAE,SAAc,EAAE,OAAO,GAAE,UAAe;gBAGnD,MAAM,EAAE;;EAM7B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,GAAE,SAAc,EACtB,OAAO,GAAE,UAAe,UAxpBE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CA4pBrD"}
|
package/dist/generators/cli.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import { buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
1
|
+
import { CRUD_OPERATIONS, buildCustomActionInvocationArgs, customActionParameterInputName, isCrudOperation, isFrameworkLifecycleMethod, normalizeCustomActionFailure, resolveCustomActionMetadata, resolveCustomActionNames } from "./custom-action.js";
|
|
2
|
+
import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
|
|
2
3
|
import { ObjectRegistry } from "../registry.js";
|
|
3
4
|
import { SmrtCollection } from "../collection.js";
|
|
4
5
|
import { runWithTenantGate } from "./tenant-gate.js";
|
|
5
6
|
//#region src/generators/cli.ts
|
|
6
|
-
/** Standard CRUD verbs handled directly by the generator. */
|
|
7
|
-
var CRUD_OPERATIONS = [
|
|
8
|
-
"list",
|
|
9
|
-
"get",
|
|
10
|
-
"create",
|
|
11
|
-
"update",
|
|
12
|
-
"delete"
|
|
13
|
-
];
|
|
14
7
|
/**
|
|
15
8
|
* Flags consumed by the CLI itself — never treated as create/update field
|
|
16
9
|
* values or as custom-action options.
|
|
@@ -156,10 +149,11 @@ var CLIGenerator = class {
|
|
|
156
149
|
if (cliConfig === false) throw new Error(`CLI is disabled for ${objectName}`);
|
|
157
150
|
const included = typeof cliConfig === "object" ? cliConfig?.include : void 0;
|
|
158
151
|
if ((typeof cliConfig === "object" && cliConfig?.exclude ? cliConfig.exclude : []).includes(action)) throw new Error(`Command '${action}' is excluded for ${objectName}`);
|
|
159
|
-
if (
|
|
152
|
+
if (isCrudOperation(action)) {
|
|
160
153
|
if (included && !included.includes(action)) throw new Error(`Command '${action}' is not enabled for ${objectName}`);
|
|
161
154
|
return;
|
|
162
155
|
}
|
|
156
|
+
if (isFrameworkLifecycleMethod(action)) throw new Error(`Command '${action}' is a framework lifecycle method, not a custom action, on ${objectName}`);
|
|
163
157
|
const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(action);
|
|
164
158
|
if (!methodDef) throw new Error(`Unknown command '${action}' for ${objectName}`);
|
|
165
159
|
if (!methodDef.isPublic) throw new Error(`Command '${action}' is not public on ${objectName}`);
|
|
@@ -389,6 +383,7 @@ var CLIGenerator = class {
|
|
|
389
383
|
const commands = [];
|
|
390
384
|
for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {
|
|
391
385
|
const objectName = classInfo.name || key;
|
|
386
|
+
if (isFrameworkBaseClass(objectName, classInfo.packageName)) continue;
|
|
392
387
|
const cliConfig = ObjectRegistry.getConfig(objectName)?.cli;
|
|
393
388
|
if (cliConfig === false) continue;
|
|
394
389
|
const lower = objectName.toLowerCase();
|
|
@@ -396,14 +391,11 @@ var CLIGenerator = class {
|
|
|
396
391
|
const excluded = typeof cliConfig === "object" && cliConfig?.exclude ? cliConfig.exclude : [];
|
|
397
392
|
const expose = (cmd) => !excluded.includes(cmd) && (!included || included.includes(cmd));
|
|
398
393
|
for (const verb of CRUD_OPERATIONS) if (expose(verb)) commands.push(`${lower}:${verb}`);
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (included !== void 0 && !included.includes(methodName)) continue;
|
|
405
|
-
commands.push(`${lower}:${methodName}`);
|
|
406
|
-
}
|
|
394
|
+
const customActions = resolveCustomActionNames(await ObjectRegistry.getAllMethods(objectName), {
|
|
395
|
+
include: included,
|
|
396
|
+
exclude: excluded
|
|
397
|
+
}, CRUD_OPERATIONS);
|
|
398
|
+
for (const methodName of customActions) commands.push(`${lower}:${methodName}`);
|
|
407
399
|
}
|
|
408
400
|
return commands.sort();
|
|
409
401
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../../src/generators/cli.ts"],"sourcesContent":["/**\n * CLI command generator for smrt objects\n *\n * Exposes registered `@smrt()` objects as a runnable admin CLI: each object gets\n * `list`/`get`/`create`/`update`/`delete` commands plus its public custom\n * methods, dispatched as `objectname:action`.\n *\n * Security parity with the REST/MCP generators (#1540, #1547, #1554, #1556):\n * - **Mass-assignment guard** — create/update bodies are filtered through the\n * `@smrt({ api: { writable: [...] } })` allowlist, dropping server-managed and\n * `@field({ readonly })` fields ({@link CLIGenerator.applyWritablePolicy}).\n * - **Exhaustive include** — an `include` list is the COMPLETE allowlist for the\n * surface; custom methods are gated on `isPublic`.\n * - **Sensitive redaction** — command *output* is serialized through\n * {@link CLIGenerator.toPublicData} so `@field({ sensitive })` values never\n * print (input bodies are guarded by the writable allowlist above).\n * - **Fail-closed tenant context** — tenant-scoped reads/writes run inside the\n * tenancy gate; without `--tenant <id>` / `--all-tenants` (and with tenancy\n * enabled) the command throws rather than ranging across all tenants.\n */\n\nimport type { AIClient, AIClientOptions } from '@happyvertical/ai';\nimport { SmrtCollection } from '../collection';\nimport type { DatabaseConfig } from '../database.js';\nimport type { PublicJsonOptions, SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport type { RegisteredClass } from '../registry/types.js';\nimport {\n buildCustomActionInvocationArgs,\n customActionParameterInputName,\n normalizeCustomActionFailure,\n resolveCustomActionMetadata,\n} from './custom-action.js';\nimport { runWithTenantGate } from './tenant-gate.js';\n\n/**\n * Public-data view exposed by SMRT objects: `toPublicJSON()` strips\n * `@field({ sensitive })` values before serialization (#1540).\n */\ninterface PublicSerializable {\n toPublicJSON(options?: PublicJsonOptions): unknown;\n}\n\n/**\n * View of an instance/collection for dispatching custom methods by name. Each\n * key is potentially a callable taking the parsed CLI options; callers narrow\n * with `typeof === 'function'` before invoking.\n */\ntype DynamicallyCallable = Record<\n string,\n ((...args: unknown[]) => unknown) | unknown\n>;\n\n/**\n * Configuration for a generated CLI.\n */\nexport interface CLIConfig {\n /** CLI program name (used in help output). */\n name?: string;\n /** CLI version. */\n version?: string;\n /** CLI description. */\n description?: string;\n}\n\n/**\n * Per-invocation context for a generated CLI.\n */\nexport interface CLIContext {\n /** Database handle passed to collections. */\n db?: DatabaseConfig;\n /** AI provider passed to collections. */\n ai?: AIClientOptions | AIClient;\n /** Authenticated operator, when the host establishes one. */\n user?: {\n id: string;\n roles?: string[];\n };\n /** Resolved permission slugs held by the operator. */\n permissions?: Iterable<string>;\n /**\n * Default tenant for tenant-scoped commands when no `--tenant` flag is given.\n * Hosts that authenticate an operator may set this from the principal.\n */\n tenantId?: string;\n /**\n * Default cross-tenant opt-in when no `--all-tenants` flag is given. Hosts\n * may set this for trusted operator/admin shells.\n */\n allowCrossTenant?: boolean;\n}\n\n/** Standard CRUD verbs handled directly by the generator. */\nconst CRUD_OPERATIONS = ['list', 'get', 'create', 'update', 'delete'];\n\n/**\n * Flags consumed by the CLI itself — never treated as create/update field\n * values or as custom-action options.\n */\nconst RESERVED_FLAGS = new Set([\n 'tenant',\n 'all-tenants',\n 'from-file',\n 'limit',\n 'offset',\n 'order-by',\n 'orderBy',\n 'where',\n 'format',\n 'json',\n 'help',\n 'id',\n]);\n\n/** Parsed CLI invocation. */\ninterface ParsedInvocation {\n /** Object/collection segment (lowercased), e.g. `product`. */\n objectName: string;\n /** Action segment, e.g. `list` or a custom method name. */\n action: string;\n /** First positional argument after the command (typically an id). */\n positional?: string;\n /** Parsed flags. String for `--k v`/`--k=v`, `true` for bare `--flag`. */\n flags: Record<string, string | boolean>;\n}\n\n/**\n * Generate and run an admin CLI for the registered `@smrt()` objects.\n */\nexport class CLIGenerator {\n private config: CLIConfig;\n private context: CLIContext;\n\n constructor(config: CLIConfig = {}, context: CLIContext = {}) {\n this.config = {\n name: 'smrt-cli',\n version: '1.0.0',\n description: 'Auto-generated CLI from smrt objects',\n ...config,\n };\n this.context = context;\n }\n\n /** CLI program name. */\n get name(): string | undefined {\n return this.config.name;\n }\n\n /** CLI version. */\n get version(): string | undefined {\n return this.config.version;\n }\n\n /**\n * Return the argv handler invoked by the generated `setupCLI()` wrapper.\n *\n * @returns An async function accepting the post-`node script` argv slice.\n */\n generateHandler(): (args: string[]) => Promise<void> {\n return async (args: string[]) => {\n await this.run(args ?? []);\n };\n }\n\n /**\n * Parse and execute a single CLI invocation.\n *\n * @param args - argv slice (command + flags).\n */\n async run(args: string[]): Promise<void> {\n if (args.length === 0 || args[0] === '--help' || args[0] === 'help') {\n console.log(await this.helpText());\n return;\n }\n\n const parsed = this.parseArgs(args);\n if (!parsed) {\n console.log(await this.helpText());\n return;\n }\n\n const classInfo = this.resolveClass(parsed.objectName);\n if (!classInfo) {\n throw new Error(`Object type '${parsed.objectName}' not found`);\n }\n const objectName: string = classInfo.name || parsed.objectName;\n\n await this.assertCommandExposed(objectName, parsed.action);\n\n const tenantId =\n typeof parsed.flags.tenant === 'string'\n ? parsed.flags.tenant\n : this.context.tenantId;\n const allowCrossTenant =\n parsed.flags['all-tenants'] === true ||\n this.context.allowCrossTenant === true;\n\n // Tenant-scoping is resolved authoritatively inside tenancy (by class name),\n // matching the interceptor and covering both @TenantScoped and\n // @smrt({ tenantScoped }) registrations (#1554).\n const result = await runWithTenantGate(\n { className: objectName, tenantId, allowCrossTenant, surface: 'CLI' },\n async () => {\n const collection = await this.getCollection(objectName);\n return this.executeAction(collection, objectName, parsed);\n },\n );\n\n if (result !== undefined) {\n console.log(JSON.stringify(this.toPublicData(result), null, 2));\n }\n }\n\n /**\n * Parse argv into a command + flags. Supports `name:action` and\n * `name action` command forms, `--key value`, `--key=value`, and bare\n * `--flag` booleans.\n */\n private parseArgs(args: string[]): ParsedInvocation | null {\n const [command, ...rest] = args;\n if (!command || command.startsWith('-')) return null;\n\n let objectName: string;\n let action: string;\n let tail = rest;\n\n if (command.includes(':')) {\n const [obj, act] = command.split(':');\n objectName = obj.toLowerCase();\n action = act;\n } else if (rest.length > 0 && !rest[0].startsWith('-')) {\n objectName = command.toLowerCase();\n action = rest[0];\n tail = rest.slice(1);\n } else {\n // A bare object name with no action is not a runnable command.\n return null;\n }\n\n if (!objectName || !action) return null;\n\n const flags: Record<string, string | boolean> = {};\n let positional: string | undefined;\n\n for (let i = 0; i < tail.length; i++) {\n const token = tail[i];\n if (token.startsWith('--')) {\n const body = token.slice(2);\n const eq = body.indexOf('=');\n if (eq !== -1) {\n flags[body.slice(0, eq)] = body.slice(eq + 1);\n } else {\n const next = tail[i + 1];\n if (next !== undefined && !next.startsWith('--')) {\n flags[body] = next;\n i++;\n } else {\n flags[body] = true;\n }\n }\n } else if (positional === undefined) {\n positional = token;\n }\n }\n\n return { objectName, action, positional, flags };\n }\n\n /**\n * Resolve a registered class by simple name (case-insensitive), mirroring the\n * MCP generator's lookup.\n */\n private resolveClass(objectName: string): RegisteredClass | null {\n const registeredClasses = ObjectRegistry.getAllClasses();\n for (const [key, info] of registeredClasses) {\n const simpleName = info.name || key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n return info;\n }\n }\n return null;\n }\n\n /**\n * Throw if `action` is not exposed for `objectName` under its `@smrt({ cli })`\n * config. `cli: false` disables the object entirely; an `include` list is the\n * complete allowlist; custom methods must be `isPublic`.\n */\n private async assertCommandExposed(\n objectName: string,\n action: string,\n ): Promise<void> {\n const config = ObjectRegistry.getConfig(objectName);\n const cliConfig = config?.cli;\n\n if (cliConfig === false) {\n throw new Error(`CLI is disabled for ${objectName}`);\n }\n\n const included: string[] | undefined =\n typeof cliConfig === 'object' ? cliConfig?.include : undefined;\n const excluded: string[] =\n typeof cliConfig === 'object' && cliConfig?.exclude\n ? cliConfig.exclude\n : [];\n\n if (excluded.includes(action)) {\n throw new Error(`Command '${action}' is excluded for ${objectName}`);\n }\n\n const isCrud = CRUD_OPERATIONS.includes(action);\n\n if (isCrud) {\n // An include list, when present, is the complete allowlist for CRUD too.\n if (included && !included.includes(action)) {\n throw new Error(`Command '${action}' is not enabled for ${objectName}`);\n }\n return;\n }\n\n // Custom method: must be public, and — when an include list is present — it\n // is the COMPLETE allowlist (parity with MCP exhaustive-include, #1547).\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const methodDef = methods.get(action);\n if (!methodDef) {\n throw new Error(`Unknown command '${action}' for ${objectName}`);\n }\n if (!methodDef.isPublic) {\n throw new Error(`Command '${action}' is not public on ${objectName}`);\n }\n if (included !== undefined && !included.includes(action)) {\n throw new Error(`Command '${action}' is not enabled for ${objectName}`);\n }\n }\n\n /**\n * Get the collection for an object via the registry factory. Uses\n * `ObjectRegistry.getCollection`, which caches, initializes, and — for plain\n * `@smrt()` models without a hand-written collection class — auto-creates the\n * default collection. (A bare `collectionConstructor` check would wrongly\n * throw for those models even though CRUD is advertised.)\n */\n private async getCollection(\n objectName: string,\n ): Promise<SmrtCollection<SmrtObject>> {\n return ObjectRegistry.getCollection(objectName, {\n ai: this.context.ai,\n db: this.context.db,\n });\n }\n\n /**\n * Execute a parsed command against a collection.\n */\n private async executeAction(\n collection: SmrtCollection<SmrtObject>,\n objectName: string,\n parsed: ParsedInvocation,\n ): Promise<unknown> {\n const { action, positional, flags } = parsed;\n\n switch (action) {\n case 'list': {\n const listOptions: {\n limit: number;\n offset: number;\n orderBy?: string;\n where?: Record<string, unknown>;\n } = {\n limit: Math.min(this.toNumber(flags.limit, 50), 1000),\n offset: this.toNumber(flags.offset, 0),\n };\n const orderBy = flags['order-by'] ?? flags.orderBy;\n if (typeof orderBy === 'string') listOptions.orderBy = orderBy;\n if (typeof flags.where === 'string') {\n listOptions.where = JSON.parse(flags.where);\n }\n return collection.list(listOptions);\n }\n\n case 'get': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for get');\n const item = await collection.get(id);\n if (!item) throw new Error(`${objectName} not found`);\n return item;\n }\n\n case 'create': {\n const data = this.applyWritablePolicy(\n objectName,\n await this.readPayload(flags),\n );\n if (this.context.user) {\n data.created_by = this.context.user.id;\n data.owner_id = this.context.user.id;\n }\n const created = await collection.create(data);\n await created.save();\n return created;\n }\n\n case 'update': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for update');\n const existing = await collection.get(id);\n if (!existing) throw new Error(`${objectName} not found`);\n const data = this.applyWritablePolicy(\n objectName,\n await this.readPayload(flags),\n );\n Object.assign(existing, data);\n if (this.context.user) {\n // Stamp the server-managed audit column on the instance; this field\n // is not part of the public SmrtObject surface.\n (existing as unknown as Record<string, unknown>).updated_by =\n this.context.user.id;\n }\n await existing.save();\n return existing;\n }\n\n case 'delete': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for delete');\n const existing = await collection.get(id);\n if (!existing) throw new Error(`${objectName} not found`);\n await existing.delete();\n return { success: true, id, message: `${objectName} deleted` };\n }\n\n default:\n return this.executeCustomAction(\n objectName,\n collection,\n action,\n positional,\n flags,\n );\n }\n }\n\n /**\n * Execute a custom method against its manifest-declared item/collection\n * target. The legacy collection fallback remains only for unavailable\n * metadata, where target scope cannot be established safely.\n */\n private async executeCustomAction(\n objectName: string,\n collection: SmrtCollection<SmrtObject>,\n action: string,\n positional: string | undefined,\n flags: Record<string, string | boolean>,\n ): Promise<unknown> {\n const id = positional ?? this.flagString(flags.id);\n const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(\n action,\n );\n const metadata = resolveCustomActionMetadata({\n actionName: action,\n method: methodDef,\n apiConfig: ObjectRegistry.getConfig(objectName).api,\n ...(this.hasCollectionReceiver(ObjectRegistry.getClass(objectName))\n ? { defaultScope: 'collection' as const }\n : {}),\n });\n const args = this.customOptions(flags, metadata);\n const invocationArgs =\n metadata.parameters?.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ? Object.keys(args).length > 0\n ? { options: args }\n : {}\n : args;\n const methodArgs = buildCustomActionInvocationArgs(\n metadata,\n invocationArgs,\n );\n\n if (methodDef && metadata.idRequired && !id) {\n throw new Error(`An id is required for custom action '${action}'`);\n }\n if (methodDef && !metadata.idRequired && id) {\n throw new Error(\n `Custom action '${action}' is collection-scoped and does not accept an id`,\n );\n }\n\n if (id) {\n const object = await collection.get(id);\n if (!object) throw new Error('Object not found');\n // Custom methods are dispatched dynamically by name; index through a\n // callable-keyed view of the instance, narrowing before invoking.\n const candidate = (object as unknown as DynamicallyCallable)[action];\n if (typeof candidate !== 'function') {\n throw new Error(`Method '${action}' not found on object instance`);\n }\n const result = await candidate.call(object, ...methodArgs);\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n\n if (methodDef?.isStatic) {\n const classInfo = ObjectRegistry.getClass(objectName);\n const candidate = (\n classInfo?.constructor as unknown as DynamicallyCallable | undefined\n )?.[action];\n if (typeof candidate !== 'function') {\n throw new Error(`Static method '${action}' not found on ${objectName}`);\n }\n const result = await candidate.call(\n classInfo?.constructor,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n\n const collectionCandidate = (collection as unknown as DynamicallyCallable)[\n action\n ];\n if (typeof collectionCandidate === 'function') {\n const result = await collectionCandidate.call(collection, ...methodArgs);\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n throw new Error(\n `Method '${action}' not found on collection. Provide an id for object-specific actions.`,\n );\n }\n\n private hasCollectionReceiver(classInfo?: RegisteredClass): boolean {\n return (\n !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection\n );\n }\n\n /**\n * Resolve the create/update payload: `--from-file <path>` (JSON) takes\n * precedence; otherwise non-reserved `--field value` flags form the body.\n * Reading from a file also keeps secrets off the process argv (#1556).\n */\n private async readPayload(\n flags: Record<string, string | boolean>,\n ): Promise<Record<string, unknown>> {\n const fromFile = flags['from-file'];\n if (typeof fromFile === 'string' && fromFile) {\n const { readFile } = await import('node:fs/promises');\n const content = await readFile(fromFile, 'utf-8');\n const parsed = JSON.parse(content);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('--from-file must contain a JSON object');\n }\n return parsed as Record<string, unknown>;\n }\n\n const data: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(flags)) {\n if (RESERVED_FLAGS.has(key)) continue;\n data[key] = this.coerceValue(value);\n }\n return data;\n }\n\n /**\n * Collect custom-action flags. Typed action parameters may deliberately use\n * standard CLI names such as `limit`, `offset`, `where`, or `format`; retain\n * those rather than silently dropping them as CRUD flags. Item actions keep\n * `--id` for their receiver and accept an action parameter named `id` as\n * `--action-id` to preserve separate namespaces.\n */\n private customOptions(\n flags: Record<string, string | boolean>,\n metadata?: { parameters?: Array<{ name: string }>; idRequired: boolean },\n ): Record<string, unknown> {\n const declaredInputs = new Set(\n (metadata?.parameters ?? []).map((parameter) =>\n customActionParameterInputName(\n metadata ?? { idRequired: false },\n parameter.name,\n ),\n ),\n );\n const options: Record<string, unknown> = {};\n for (const [rawKey, value] of Object.entries(flags)) {\n const key = rawKey === 'action-id' ? 'actionId' : rawKey;\n if (rawKey === 'id') continue;\n if (RESERVED_FLAGS.has(rawKey) && !declaredInputs.has(key)) continue;\n if (rawKey === 'action-id' && !declaredInputs.has(key)) continue;\n options[key] = this.coerceValue(value);\n }\n return options;\n }\n\n /**\n * Mass-assignment guard (#1540/#1556): strip framework/server-managed and\n * `@field({ readonly })` fields, and — when an `@smrt({ api: { writable } })`\n * allowlist is set — intersect with it. Identical policy to the REST/MCP\n * generators so a CLI create/update cannot set fields the API forbids.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: unknown,\n ): Record<string, unknown> {\n if (!data || typeof data !== 'object') return {};\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Serialize command output, excluding `@field({ sensitive })` fields (#1540).\n * Recurses arrays/plain objects so a SmrtObject nested in a custom-action\n * result is stripped too; a cycle guard prevents infinite loops.\n */\n private toPublicData(\n value: unknown,\n seen: WeakSet<object> = new WeakSet(),\n options: PublicJsonOptions = this.getPublicJsonOptions(),\n ): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (\n typeof (value as Partial<PublicSerializable>).toPublicJSON === 'function'\n ) {\n return (value as PublicSerializable).toPublicJSON(options);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry) => this.toPublicData(entry, seen, options));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = this.toPublicData(entry, seen, options);\n }\n return out;\n }\n\n private getPublicJsonOptions(): PublicJsonOptions {\n return { permissions: this.context.permissions };\n }\n\n /** List the runnable commands grouped by object, for help output. */\n async listCommands(): Promise<string[]> {\n const commands: string[] = [];\n for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {\n const objectName = classInfo.name || key;\n const config = ObjectRegistry.getConfig(objectName);\n const cliConfig = config?.cli;\n if (cliConfig === false) continue;\n\n const lower = objectName.toLowerCase();\n const included: string[] | undefined =\n typeof cliConfig === 'object' ? cliConfig?.include : undefined;\n const excluded: string[] =\n typeof cliConfig === 'object' && cliConfig?.exclude\n ? cliConfig.exclude\n : [];\n\n const expose = (cmd: string) =>\n !excluded.includes(cmd) && (!included || included.includes(cmd));\n\n for (const verb of CRUD_OPERATIONS) {\n if (expose(verb)) commands.push(`${lower}:${verb}`);\n }\n\n const methods = await ObjectRegistry.getAllMethods(objectName);\n for (const [methodName, methodDef] of methods) {\n if (CRUD_OPERATIONS.includes(methodName)) continue;\n if (!methodDef.isPublic) continue;\n if (excluded.includes(methodName)) continue;\n if (included !== undefined && !included.includes(methodName)) continue;\n commands.push(`${lower}:${methodName}`);\n }\n }\n return commands.sort();\n }\n\n private async helpText(): Promise<string> {\n const commands = await this.listCommands();\n return [\n `${this.config.name} ${this.config.version}`,\n this.config.description ?? '',\n '',\n 'Usage: <object>:<action> [id] [--flags]',\n '',\n 'Global flags:',\n ' --tenant <id> run the command inside a specific tenant',\n ' --all-tenants allow cross-tenant access (operator opt-in)',\n ' --from-file <p> read create/update payload from a JSON file',\n ' --where <json> filter for list',\n ' --limit / --offset / --order-by',\n '',\n 'Commands:',\n ...commands.map((c) => ` ${c}`),\n ].join('\\n');\n }\n\n private toNumber(\n value: string | boolean | undefined,\n fallback: number,\n ): number {\n if (typeof value !== 'string') return fallback;\n const n = Number.parseInt(value, 10);\n return Number.isNaN(n) ? fallback : n;\n }\n\n private flagString(value: string | boolean | undefined): string | undefined {\n return typeof value === 'string' ? value : undefined;\n }\n\n /** Coerce a flag value to a JSON scalar/object where it parses cleanly. */\n private coerceValue(value: string | boolean): unknown {\n if (typeof value !== 'string') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value !== '' && !Number.isNaN(Number(value))) return Number(value);\n if (\n (value.startsWith('{') && value.endsWith('}')) ||\n (value.startsWith('[') && value.endsWith(']'))\n ) {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n return value;\n }\n}\n\n/**\n * Convenience runner used by the generated `setupCLI()` wrapper.\n *\n * @param config - CLI configuration.\n * @param context - Per-invocation context.\n * @returns A `{ run, generator }` pair; `run(argv)` strips the leading\n * `node script` entries before dispatching.\n */\nexport function setupCLI(config: CLIConfig = {}, context: CLIContext = {}) {\n const generator = new CLIGenerator(config, context);\n return {\n run: async (argv: string[]) => {\n const handler = generator.generateHandler();\n await handler(argv.slice(2));\n },\n generator,\n };\n}\n\n/**\n * Get a bare argv handler for a generated CLI.\n *\n * @param config - CLI configuration.\n * @param context - Per-invocation context.\n * @returns An async argv-slice handler.\n */\nexport function getCLIHandler(\n config: CLIConfig = {},\n context: CLIContext = {},\n) {\n const generator = new CLIGenerator(config, context);\n return generator.generateHandler();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../../src/generators/cli.ts"],"sourcesContent":["/**\n * CLI command generator for smrt objects\n *\n * Exposes registered `@smrt()` objects as a runnable admin CLI: each object gets\n * `list`/`get`/`create`/`update`/`delete` commands plus its public custom\n * methods, dispatched as `objectname:action`.\n *\n * Security parity with the REST/MCP generators (#1540, #1547, #1554, #1556):\n * - **Mass-assignment guard** — create/update bodies are filtered through the\n * `@smrt({ api: { writable: [...] } })` allowlist, dropping server-managed and\n * `@field({ readonly })` fields ({@link CLIGenerator.applyWritablePolicy}).\n * - **Exhaustive include** — an `include` list is the COMPLETE allowlist for the\n * surface; custom methods are gated on `isPublic`.\n * - **Sensitive redaction** — command *output* is serialized through\n * {@link CLIGenerator.toPublicData} so `@field({ sensitive })` values never\n * print (input bodies are guarded by the writable allowlist above).\n * - **Fail-closed tenant context** — tenant-scoped reads/writes run inside the\n * tenancy gate; without `--tenant <id>` / `--all-tenants` (and with tenancy\n * enabled) the command throws rather than ranging across all tenants.\n */\n\nimport type { AIClient, AIClientOptions } from '@happyvertical/ai';\nimport { SmrtCollection } from '../collection';\nimport type { DatabaseConfig } from '../database.js';\nimport type { PublicJsonOptions, SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport type { RegisteredClass } from '../registry/types.js';\nimport {\n buildCustomActionInvocationArgs,\n CRUD_OPERATIONS,\n customActionParameterInputName,\n isCrudOperation,\n isFrameworkLifecycleMethod,\n normalizeCustomActionFailure,\n resolveCustomActionMetadata,\n resolveCustomActionNames,\n} from './custom-action.js';\nimport { runWithTenantGate } from './tenant-gate.js';\n\n/**\n * Public-data view exposed by SMRT objects: `toPublicJSON()` strips\n * `@field({ sensitive })` values before serialization (#1540).\n */\ninterface PublicSerializable {\n toPublicJSON(options?: PublicJsonOptions): unknown;\n}\n\n/**\n * View of an instance/collection for dispatching custom methods by name. Each\n * key is potentially a callable taking the parsed CLI options; callers narrow\n * with `typeof === 'function'` before invoking.\n */\ntype DynamicallyCallable = Record<\n string,\n ((...args: unknown[]) => unknown) | unknown\n>;\n\n/**\n * Configuration for a generated CLI.\n */\nexport interface CLIConfig {\n /** CLI program name (used in help output). */\n name?: string;\n /** CLI version. */\n version?: string;\n /** CLI description. */\n description?: string;\n}\n\n/**\n * Per-invocation context for a generated CLI.\n */\nexport interface CLIContext {\n /** Database handle passed to collections. */\n db?: DatabaseConfig;\n /** AI provider passed to collections. */\n ai?: AIClientOptions | AIClient;\n /** Authenticated operator, when the host establishes one. */\n user?: {\n id: string;\n roles?: string[];\n };\n /** Resolved permission slugs held by the operator. */\n permissions?: Iterable<string>;\n /**\n * Default tenant for tenant-scoped commands when no `--tenant` flag is given.\n * Hosts that authenticate an operator may set this from the principal.\n */\n tenantId?: string;\n /**\n * Default cross-tenant opt-in when no `--all-tenants` flag is given. Hosts\n * may set this for trusted operator/admin shells.\n */\n allowCrossTenant?: boolean;\n}\n\n/**\n * Flags consumed by the CLI itself — never treated as create/update field\n * values or as custom-action options.\n */\nconst RESERVED_FLAGS = new Set([\n 'tenant',\n 'all-tenants',\n 'from-file',\n 'limit',\n 'offset',\n 'order-by',\n 'orderBy',\n 'where',\n 'format',\n 'json',\n 'help',\n 'id',\n]);\n\n/** Parsed CLI invocation. */\ninterface ParsedInvocation {\n /** Object/collection segment (lowercased), e.g. `product`. */\n objectName: string;\n /** Action segment, e.g. `list` or a custom method name. */\n action: string;\n /** First positional argument after the command (typically an id). */\n positional?: string;\n /** Parsed flags. String for `--k v`/`--k=v`, `true` for bare `--flag`. */\n flags: Record<string, string | boolean>;\n}\n\n/**\n * Generate and run an admin CLI for the registered `@smrt()` objects.\n */\nexport class CLIGenerator {\n private config: CLIConfig;\n private context: CLIContext;\n\n constructor(config: CLIConfig = {}, context: CLIContext = {}) {\n this.config = {\n name: 'smrt-cli',\n version: '1.0.0',\n description: 'Auto-generated CLI from smrt objects',\n ...config,\n };\n this.context = context;\n }\n\n /** CLI program name. */\n get name(): string | undefined {\n return this.config.name;\n }\n\n /** CLI version. */\n get version(): string | undefined {\n return this.config.version;\n }\n\n /**\n * Return the argv handler invoked by the generated `setupCLI()` wrapper.\n *\n * @returns An async function accepting the post-`node script` argv slice.\n */\n generateHandler(): (args: string[]) => Promise<void> {\n return async (args: string[]) => {\n await this.run(args ?? []);\n };\n }\n\n /**\n * Parse and execute a single CLI invocation.\n *\n * @param args - argv slice (command + flags).\n */\n async run(args: string[]): Promise<void> {\n if (args.length === 0 || args[0] === '--help' || args[0] === 'help') {\n console.log(await this.helpText());\n return;\n }\n\n const parsed = this.parseArgs(args);\n if (!parsed) {\n console.log(await this.helpText());\n return;\n }\n\n const classInfo = this.resolveClass(parsed.objectName);\n if (!classInfo) {\n throw new Error(`Object type '${parsed.objectName}' not found`);\n }\n const objectName: string = classInfo.name || parsed.objectName;\n\n await this.assertCommandExposed(objectName, parsed.action);\n\n const tenantId =\n typeof parsed.flags.tenant === 'string'\n ? parsed.flags.tenant\n : this.context.tenantId;\n const allowCrossTenant =\n parsed.flags['all-tenants'] === true ||\n this.context.allowCrossTenant === true;\n\n // Tenant-scoping is resolved authoritatively inside tenancy (by class name),\n // matching the interceptor and covering both @TenantScoped and\n // @smrt({ tenantScoped }) registrations (#1554).\n const result = await runWithTenantGate(\n { className: objectName, tenantId, allowCrossTenant, surface: 'CLI' },\n async () => {\n const collection = await this.getCollection(objectName);\n return this.executeAction(collection, objectName, parsed);\n },\n );\n\n if (result !== undefined) {\n console.log(JSON.stringify(this.toPublicData(result), null, 2));\n }\n }\n\n /**\n * Parse argv into a command + flags. Supports `name:action` and\n * `name action` command forms, `--key value`, `--key=value`, and bare\n * `--flag` booleans.\n */\n private parseArgs(args: string[]): ParsedInvocation | null {\n const [command, ...rest] = args;\n if (!command || command.startsWith('-')) return null;\n\n let objectName: string;\n let action: string;\n let tail = rest;\n\n if (command.includes(':')) {\n const [obj, act] = command.split(':');\n objectName = obj.toLowerCase();\n action = act;\n } else if (rest.length > 0 && !rest[0].startsWith('-')) {\n objectName = command.toLowerCase();\n action = rest[0];\n tail = rest.slice(1);\n } else {\n // A bare object name with no action is not a runnable command.\n return null;\n }\n\n if (!objectName || !action) return null;\n\n const flags: Record<string, string | boolean> = {};\n let positional: string | undefined;\n\n for (let i = 0; i < tail.length; i++) {\n const token = tail[i];\n if (token.startsWith('--')) {\n const body = token.slice(2);\n const eq = body.indexOf('=');\n if (eq !== -1) {\n flags[body.slice(0, eq)] = body.slice(eq + 1);\n } else {\n const next = tail[i + 1];\n if (next !== undefined && !next.startsWith('--')) {\n flags[body] = next;\n i++;\n } else {\n flags[body] = true;\n }\n }\n } else if (positional === undefined) {\n positional = token;\n }\n }\n\n return { objectName, action, positional, flags };\n }\n\n /**\n * Resolve a registered class by simple name (case-insensitive), mirroring the\n * MCP generator's lookup.\n */\n private resolveClass(objectName: string): RegisteredClass | null {\n const registeredClasses = ObjectRegistry.getAllClasses();\n for (const [key, info] of registeredClasses) {\n const simpleName = info.name || key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n return info;\n }\n }\n return null;\n }\n\n /**\n * Throw if `action` is not exposed for `objectName` under its `@smrt({ cli })`\n * config. `cli: false` disables the object entirely; an `include` list is the\n * complete allowlist; custom methods must be `isPublic`.\n */\n private async assertCommandExposed(\n objectName: string,\n action: string,\n ): Promise<void> {\n const config = ObjectRegistry.getConfig(objectName);\n const cliConfig = config?.cli;\n\n if (cliConfig === false) {\n throw new Error(`CLI is disabled for ${objectName}`);\n }\n\n const included: string[] | undefined =\n typeof cliConfig === 'object' ? cliConfig?.include : undefined;\n const excluded: string[] =\n typeof cliConfig === 'object' && cliConfig?.exclude\n ? cliConfig.exclude\n : [];\n\n if (excluded.includes(action)) {\n throw new Error(`Command '${action}' is excluded for ${objectName}`);\n }\n\n const isCrud = isCrudOperation(action);\n\n if (isCrud) {\n // An include list, when present, is the complete allowlist for CRUD too.\n if (included && !included.includes(action)) {\n throw new Error(`Command '${action}' is not enabled for ${objectName}`);\n }\n return;\n }\n\n // A framework lifecycle method (save/initialize/... — see\n // FRAMEWORK_LIFECYCLE_METHOD_NAMES) is never a custom action, even when\n // this class declares its own override: it is the mechanism behind\n // generated CRUD, not a distinct operation, and listCommands() never\n // advertises it. Reject it here too so an advertised command and an\n // invokable one cannot disagree (#2638).\n if (isFrameworkLifecycleMethod(action)) {\n throw new Error(\n `Command '${action}' is a framework lifecycle method, not a custom action, on ${objectName}`,\n );\n }\n\n // Custom method: must be public, and — when an include list is present — it\n // is the COMPLETE allowlist (parity with MCP exhaustive-include, #1547).\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const methodDef = methods.get(action);\n if (!methodDef) {\n throw new Error(`Unknown command '${action}' for ${objectName}`);\n }\n if (!methodDef.isPublic) {\n throw new Error(`Command '${action}' is not public on ${objectName}`);\n }\n if (included !== undefined && !included.includes(action)) {\n throw new Error(`Command '${action}' is not enabled for ${objectName}`);\n }\n }\n\n /**\n * Get the collection for an object via the registry factory. Uses\n * `ObjectRegistry.getCollection`, which caches, initializes, and — for plain\n * `@smrt()` models without a hand-written collection class — auto-creates the\n * default collection. (A bare `collectionConstructor` check would wrongly\n * throw for those models even though CRUD is advertised.)\n */\n private async getCollection(\n objectName: string,\n ): Promise<SmrtCollection<SmrtObject>> {\n return ObjectRegistry.getCollection(objectName, {\n ai: this.context.ai,\n db: this.context.db,\n });\n }\n\n /**\n * Execute a parsed command against a collection.\n */\n private async executeAction(\n collection: SmrtCollection<SmrtObject>,\n objectName: string,\n parsed: ParsedInvocation,\n ): Promise<unknown> {\n const { action, positional, flags } = parsed;\n\n switch (action) {\n case 'list': {\n const listOptions: {\n limit: number;\n offset: number;\n orderBy?: string;\n where?: Record<string, unknown>;\n } = {\n limit: Math.min(this.toNumber(flags.limit, 50), 1000),\n offset: this.toNumber(flags.offset, 0),\n };\n const orderBy = flags['order-by'] ?? flags.orderBy;\n if (typeof orderBy === 'string') listOptions.orderBy = orderBy;\n if (typeof flags.where === 'string') {\n listOptions.where = JSON.parse(flags.where);\n }\n return collection.list(listOptions);\n }\n\n case 'get': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for get');\n const item = await collection.get(id);\n if (!item) throw new Error(`${objectName} not found`);\n return item;\n }\n\n case 'create': {\n const data = this.applyWritablePolicy(\n objectName,\n await this.readPayload(flags),\n );\n if (this.context.user) {\n data.created_by = this.context.user.id;\n data.owner_id = this.context.user.id;\n }\n const created = await collection.create(data);\n await created.save();\n return created;\n }\n\n case 'update': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for update');\n const existing = await collection.get(id);\n if (!existing) throw new Error(`${objectName} not found`);\n const data = this.applyWritablePolicy(\n objectName,\n await this.readPayload(flags),\n );\n Object.assign(existing, data);\n if (this.context.user) {\n // Stamp the server-managed audit column on the instance; this field\n // is not part of the public SmrtObject surface.\n (existing as unknown as Record<string, unknown>).updated_by =\n this.context.user.id;\n }\n await existing.save();\n return existing;\n }\n\n case 'delete': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for delete');\n const existing = await collection.get(id);\n if (!existing) throw new Error(`${objectName} not found`);\n await existing.delete();\n return { success: true, id, message: `${objectName} deleted` };\n }\n\n default:\n return this.executeCustomAction(\n objectName,\n collection,\n action,\n positional,\n flags,\n );\n }\n }\n\n /**\n * Execute a custom method against its manifest-declared item/collection\n * target. The legacy collection fallback remains only for unavailable\n * metadata, where target scope cannot be established safely.\n */\n private async executeCustomAction(\n objectName: string,\n collection: SmrtCollection<SmrtObject>,\n action: string,\n positional: string | undefined,\n flags: Record<string, string | boolean>,\n ): Promise<unknown> {\n const id = positional ?? this.flagString(flags.id);\n const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(\n action,\n );\n const metadata = resolveCustomActionMetadata({\n actionName: action,\n method: methodDef,\n apiConfig: ObjectRegistry.getConfig(objectName).api,\n ...(this.hasCollectionReceiver(ObjectRegistry.getClass(objectName))\n ? { defaultScope: 'collection' as const }\n : {}),\n });\n const args = this.customOptions(flags, metadata);\n const invocationArgs =\n metadata.parameters?.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ? Object.keys(args).length > 0\n ? { options: args }\n : {}\n : args;\n const methodArgs = buildCustomActionInvocationArgs(\n metadata,\n invocationArgs,\n );\n\n if (methodDef && metadata.idRequired && !id) {\n throw new Error(`An id is required for custom action '${action}'`);\n }\n if (methodDef && !metadata.idRequired && id) {\n throw new Error(\n `Custom action '${action}' is collection-scoped and does not accept an id`,\n );\n }\n\n if (id) {\n const object = await collection.get(id);\n if (!object) throw new Error('Object not found');\n // Custom methods are dispatched dynamically by name; index through a\n // callable-keyed view of the instance, narrowing before invoking.\n const candidate = (object as unknown as DynamicallyCallable)[action];\n if (typeof candidate !== 'function') {\n throw new Error(`Method '${action}' not found on object instance`);\n }\n const result = await candidate.call(object, ...methodArgs);\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n\n if (methodDef?.isStatic) {\n const classInfo = ObjectRegistry.getClass(objectName);\n const candidate = (\n classInfo?.constructor as unknown as DynamicallyCallable | undefined\n )?.[action];\n if (typeof candidate !== 'function') {\n throw new Error(`Static method '${action}' not found on ${objectName}`);\n }\n const result = await candidate.call(\n classInfo?.constructor,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n\n const collectionCandidate = (collection as unknown as DynamicallyCallable)[\n action\n ];\n if (typeof collectionCandidate === 'function') {\n const result = await collectionCandidate.call(collection, ...methodArgs);\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n throw new Error(\n `Method '${action}' not found on collection. Provide an id for object-specific actions.`,\n );\n }\n\n private hasCollectionReceiver(classInfo?: RegisteredClass): boolean {\n return (\n !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection\n );\n }\n\n /**\n * Resolve the create/update payload: `--from-file <path>` (JSON) takes\n * precedence; otherwise non-reserved `--field value` flags form the body.\n * Reading from a file also keeps secrets off the process argv (#1556).\n */\n private async readPayload(\n flags: Record<string, string | boolean>,\n ): Promise<Record<string, unknown>> {\n const fromFile = flags['from-file'];\n if (typeof fromFile === 'string' && fromFile) {\n const { readFile } = await import('node:fs/promises');\n const content = await readFile(fromFile, 'utf-8');\n const parsed = JSON.parse(content);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('--from-file must contain a JSON object');\n }\n return parsed as Record<string, unknown>;\n }\n\n const data: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(flags)) {\n if (RESERVED_FLAGS.has(key)) continue;\n data[key] = this.coerceValue(value);\n }\n return data;\n }\n\n /**\n * Collect custom-action flags. Typed action parameters may deliberately use\n * standard CLI names such as `limit`, `offset`, `where`, or `format`; retain\n * those rather than silently dropping them as CRUD flags. Item actions keep\n * `--id` for their receiver and accept an action parameter named `id` as\n * `--action-id` to preserve separate namespaces.\n */\n private customOptions(\n flags: Record<string, string | boolean>,\n metadata?: { parameters?: Array<{ name: string }>; idRequired: boolean },\n ): Record<string, unknown> {\n const declaredInputs = new Set(\n (metadata?.parameters ?? []).map((parameter) =>\n customActionParameterInputName(\n metadata ?? { idRequired: false },\n parameter.name,\n ),\n ),\n );\n const options: Record<string, unknown> = {};\n for (const [rawKey, value] of Object.entries(flags)) {\n const key = rawKey === 'action-id' ? 'actionId' : rawKey;\n if (rawKey === 'id') continue;\n if (RESERVED_FLAGS.has(rawKey) && !declaredInputs.has(key)) continue;\n if (rawKey === 'action-id' && !declaredInputs.has(key)) continue;\n options[key] = this.coerceValue(value);\n }\n return options;\n }\n\n /**\n * Mass-assignment guard (#1540/#1556): strip framework/server-managed and\n * `@field({ readonly })` fields, and — when an `@smrt({ api: { writable } })`\n * allowlist is set — intersect with it. Identical policy to the REST/MCP\n * generators so a CLI create/update cannot set fields the API forbids.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: unknown,\n ): Record<string, unknown> {\n if (!data || typeof data !== 'object') return {};\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Serialize command output, excluding `@field({ sensitive })` fields (#1540).\n * Recurses arrays/plain objects so a SmrtObject nested in a custom-action\n * result is stripped too; a cycle guard prevents infinite loops.\n */\n private toPublicData(\n value: unknown,\n seen: WeakSet<object> = new WeakSet(),\n options: PublicJsonOptions = this.getPublicJsonOptions(),\n ): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (\n typeof (value as Partial<PublicSerializable>).toPublicJSON === 'function'\n ) {\n return (value as PublicSerializable).toPublicJSON(options);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry) => this.toPublicData(entry, seen, options));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = this.toPublicData(entry, seen, options);\n }\n return out;\n }\n\n private getPublicJsonOptions(): PublicJsonOptions {\n return { permissions: this.context.permissions };\n }\n\n /** List the runnable commands grouped by object, for help output. */\n async listCommands(): Promise<string[]> {\n const commands: string[] = [];\n for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {\n const objectName = classInfo.name || key;\n\n // The framework's own abstract base classes are scaffolding, not\n // resources — never expose an `objectname:list`/`:create`/... command\n // for them, regardless of config (#2642).\n if (isFrameworkBaseClass(objectName, classInfo.packageName)) continue;\n\n const config = ObjectRegistry.getConfig(objectName);\n const cliConfig = config?.cli;\n if (cliConfig === false) continue;\n\n const lower = objectName.toLowerCase();\n const included: string[] | undefined =\n typeof cliConfig === 'object' ? cliConfig?.include : undefined;\n const excluded: string[] =\n typeof cliConfig === 'object' && cliConfig?.exclude\n ? cliConfig.exclude\n : [];\n\n const expose = (cmd: string) =>\n !excluded.includes(cmd) && (!included || included.includes(cmd));\n\n for (const verb of CRUD_OPERATIONS) {\n if (expose(verb)) commands.push(`${lower}:${verb}`);\n }\n\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const customActions = resolveCustomActionNames(\n methods,\n { include: included, exclude: excluded },\n CRUD_OPERATIONS,\n );\n for (const methodName of customActions) {\n commands.push(`${lower}:${methodName}`);\n }\n }\n return commands.sort();\n }\n\n private async helpText(): Promise<string> {\n const commands = await this.listCommands();\n return [\n `${this.config.name} ${this.config.version}`,\n this.config.description ?? '',\n '',\n 'Usage: <object>:<action> [id] [--flags]',\n '',\n 'Global flags:',\n ' --tenant <id> run the command inside a specific tenant',\n ' --all-tenants allow cross-tenant access (operator opt-in)',\n ' --from-file <p> read create/update payload from a JSON file',\n ' --where <json> filter for list',\n ' --limit / --offset / --order-by',\n '',\n 'Commands:',\n ...commands.map((c) => ` ${c}`),\n ].join('\\n');\n }\n\n private toNumber(\n value: string | boolean | undefined,\n fallback: number,\n ): number {\n if (typeof value !== 'string') return fallback;\n const n = Number.parseInt(value, 10);\n return Number.isNaN(n) ? fallback : n;\n }\n\n private flagString(value: string | boolean | undefined): string | undefined {\n return typeof value === 'string' ? value : undefined;\n }\n\n /** Coerce a flag value to a JSON scalar/object where it parses cleanly. */\n private coerceValue(value: string | boolean): unknown {\n if (typeof value !== 'string') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value !== '' && !Number.isNaN(Number(value))) return Number(value);\n if (\n (value.startsWith('{') && value.endsWith('}')) ||\n (value.startsWith('[') && value.endsWith(']'))\n ) {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n return value;\n }\n}\n\n/**\n * Convenience runner used by the generated `setupCLI()` wrapper.\n *\n * @param config - CLI configuration.\n * @param context - Per-invocation context.\n * @returns A `{ run, generator }` pair; `run(argv)` strips the leading\n * `node script` entries before dispatching.\n */\nexport function setupCLI(config: CLIConfig = {}, context: CLIContext = {}) {\n const generator = new CLIGenerator(config, context);\n return {\n run: async (argv: string[]) => {\n const handler = generator.generateHandler();\n await handler(argv.slice(2));\n },\n generator,\n };\n}\n\n/**\n * Get a bare argv handler for a generated CLI.\n *\n * @param config - CLI configuration.\n * @param context - Per-invocation context.\n * @returns An async argv-slice handler.\n */\nexport function getCLIHandler(\n config: CLIConfig = {},\n context: CLIContext = {},\n) {\n const generator = new CLIGenerator(config, context);\n return generator.generateHandler();\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,222 @@ import { ToolEffect } from '../registry/types.js';
|
|
|
2
2
|
import { MethodDefinition } from '../scanner/types.js';
|
|
3
3
|
export type CustomActionScope = 'item' | 'collection';
|
|
4
4
|
export type { ToolEffect } from '../registry/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Public method names declared directly on `SmrtObject`/`SmrtClass`
|
|
7
|
+
* (`src/object.ts`, `src/class.ts`) that form the constructor → `initialize()`
|
|
8
|
+
* → `save()`/`delete()`/`loadFromId()` lifecycle and its immediate supporting
|
|
9
|
+
* mechanism: identity/persistence bookkeeping, transaction binding, and
|
|
10
|
+
* serialization. `save()` is what generated `create`/`update` call;
|
|
11
|
+
* `initialize()` is what `get`/`list` hydration calls; `loadFromId()`/
|
|
12
|
+
* `loadFromSlug()` are what `get()` calls; `toJSON()`/`toPublicJSON()` are
|
|
13
|
+
* what every read serializes through. None of these are a subclass-specific
|
|
14
|
+
* operation, so none is exposed as a generated CLI/MCP custom action --
|
|
15
|
+
* even when a subclass declares its own override (e.g. `User.save()` at
|
|
16
|
+
* `packages/users/src/models/User.ts`). An override is still the same
|
|
17
|
+
* lifecycle operation, not a new one (#2638). `delete` itself is a CRUD verb
|
|
18
|
+
* `CLIGenerator`/`MCPGenerator` already special-case, so it is not repeated
|
|
19
|
+
* here.
|
|
20
|
+
*
|
|
21
|
+
* Scope is deliberately narrower than "every public method on
|
|
22
|
+
* SmrtObject/SmrtClass/SmrtCollection":
|
|
23
|
+
*
|
|
24
|
+
* - AI operations `is()`/`do()`/`describe()` are declared on `SmrtObject`
|
|
25
|
+
* but are explicitly designed to be overridden with domain-specific
|
|
26
|
+
* behavior and exposed as a distinct action -- confirmed by existing,
|
|
27
|
+
* intentional coverage (`generators/cli-commands.spec.ts`'s
|
|
28
|
+
* `describe()` custom-action fixture,
|
|
29
|
+
* `vite-plugin/generated-client-integration.test.ts`'s `ArtCollection.
|
|
30
|
+
* describe(tone)` with its own declared API route). Excluding them here
|
|
31
|
+
* would regress real, working behavior.
|
|
32
|
+
* - Relationship loading (`loadRelated`/`loadRelatedMany`/`getRelated`/
|
|
33
|
+
* `isRelatedLoaded`), memory (`remember`/`recall`/`recallAll`/`forget`/
|
|
34
|
+
* `forgetScope`), embeddings (`generateEmbeddings`/`getEmbedding`/
|
|
35
|
+
* `hasStaleEmbeddings`/`clearEmbeddings`), and AI-usage introspection
|
|
36
|
+
* (`getAiUsageSnapshot`/`resetAiUsage`/`listAiUsage`/`summarizeAiUsage`)
|
|
37
|
+
* are generic capabilities a class may legitimately want to trigger or
|
|
38
|
+
* report on as a distinct action (e.g. "regenerate this record's
|
|
39
|
+
* embeddings"), not "the mechanism behind CRUD" the way `save`/
|
|
40
|
+
* `initialize` are. Nothing in the measured #2638 residual touches them,
|
|
41
|
+
* so blanket-excluding them is a separate, unevidenced call this fix does
|
|
42
|
+
* not make.
|
|
43
|
+
* - `SmrtCollection`'s own surface (`count`, `facets`, `query`, `findOne`,
|
|
44
|
+
* `generateMissingEmbeddings`, ...) is excluded entirely for the same
|
|
45
|
+
* reason: the measured residual is 100% item-class overrides (`User`,
|
|
46
|
+
* `Invoice`, `Payment`, ...), never a collection-class override, and
|
|
47
|
+
* `count`/`facets` read as genuinely distinct query capabilities in
|
|
48
|
+
* existing fixtures elsewhere in the repo (e.g.
|
|
49
|
+
* `packages/content/src/server/content-list-actions.test.ts`). Extending
|
|
50
|
+
* this rule to `SmrtCollection` needs its own review, not a ride-along
|
|
51
|
+
* here.
|
|
52
|
+
*
|
|
53
|
+
* This can only be a METHOD-NAME list, not a reuse of the existing
|
|
54
|
+
* class-name sets (`FRAMEWORK_METHOD_BASE_NAMES` in
|
|
55
|
+
* `scanner/manifest-generator.ts`, the registry's own by-name skip in
|
|
56
|
+
* `registry/inheritance-resolver.ts`, or the broader 8-class
|
|
57
|
+
* `FRAMEWORK_BASE_CLASSES` those two modules also cross-reference for the
|
|
58
|
+
* unrelated schema-field-merge question): those sets answer "is this
|
|
59
|
+
* ancestor one of the excluded classes", which only ever decides whether to
|
|
60
|
+
* MERGE an ancestor's methods onto a subclass that does not declare them.
|
|
61
|
+
* A locally declared override -- the #2638 case -- IS the subclass's own
|
|
62
|
+
* method; there is no ancestor lookup to skip, because the method that
|
|
63
|
+
* needs excluding was never inherited in the first place. The same
|
|
64
|
+
* plumbing-vs-operation judgment #2624 made at the class level has to be
|
|
65
|
+
* re-expressed as a method-name list to reach the override case too, which
|
|
66
|
+
* is why this is a fifth name list rather than a reuse of one of the four.
|
|
67
|
+
*
|
|
68
|
+
* A static, hand-maintained list (not runtime introspection of the actual
|
|
69
|
+
* `SmrtObject`/`SmrtClass` prototypes) matches the existing pattern for all
|
|
70
|
+
* four sibling lists above, and keeps this transport-neutral module usable
|
|
71
|
+
* from a pure manifest/AST build path (`vite-plugin/sveltekit-generator.ts`)
|
|
72
|
+
* that has no live class registry to introspect. Re-derive this list from
|
|
73
|
+
* those two files' own public method signatures if they change.
|
|
74
|
+
*/
|
|
75
|
+
export declare const FRAMEWORK_LIFECYCLE_METHOD_NAMES: ReadonlySet<string>;
|
|
76
|
+
/**
|
|
77
|
+
* True when `methodName` is one of the universal `SmrtObject`/`SmrtClass`
|
|
78
|
+
* lifecycle methods above — the mechanism behind generated CRUD, never a
|
|
79
|
+
* subclass-specific custom action, even when the subclass declares its own
|
|
80
|
+
* override.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isFrameworkLifecycleMethod(methodName: string): boolean;
|
|
83
|
+
/** Minimal shape `resolveCustomActionNames` needs from a method entry. */
|
|
84
|
+
export interface ResolvableMethod {
|
|
85
|
+
isPublic: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the effective set of custom (non-CRUD) command/tool names exposed
|
|
89
|
+
* for an object, given its transport config's `include`/`exclude` and its
|
|
90
|
+
* method map: every public method, minus CRUD verbs, minus framework
|
|
91
|
+
* lifecycle methods, restricted to `include` when present and always minus
|
|
92
|
+
* `exclude`. Reused by three callers, so they agree with each other and
|
|
93
|
+
* with what `listCommands()` advertises:
|
|
94
|
+
*
|
|
95
|
+
* - `CLIGenerator.listCommands()` (over the live `ObjectRegistry`).
|
|
96
|
+
* - `findCliApiCoherenceViolations`'s bare-`cli: true`/`cli: {}` branch
|
|
97
|
+
* (over the static manifest, no explicit `include`) -- see
|
|
98
|
+
* `resolveCliActionSet` in `vite-plugin/sveltekit-generator.ts`.
|
|
99
|
+
* - `generateCLIModule()` in `vite-plugin/index.ts`, which generates the
|
|
100
|
+
* `smrt:cli` virtual module's static command metadata. It layers one
|
|
101
|
+
* additional filter on top of this function's result: a leading `_` on
|
|
102
|
+
* the method name, because the manifest's `isPublic` is unreliable there
|
|
103
|
+
* (see that call site's own comment) and this function has no other
|
|
104
|
+
* signal to exclude an internal-by-convention method.
|
|
105
|
+
*
|
|
106
|
+
* NOT the one universal resolution, and deliberately not reused by every
|
|
107
|
+
* caller that resolves a CLI command set:
|
|
108
|
+
*
|
|
109
|
+
* - `CLIGenerator.assertCommandExposed()` does not call this for its custom-
|
|
110
|
+
* method branch — it checks `isFrameworkLifecycleMethod()` directly plus
|
|
111
|
+
* its own inline public/include/exclude logic, so it can give a distinct
|
|
112
|
+
* error message per failure reason (unknown vs. not public vs. not
|
|
113
|
+
* enabled vs. lifecycle method) rather than a single boolean membership
|
|
114
|
+
* test.
|
|
115
|
+
* - `findCliApiCoherenceViolations`'s EXPLICIT-`cli.include` branch
|
|
116
|
+
* deliberately bypasses this function too: an `include` entry naming a
|
|
117
|
+
* typo, a getter, or a private/protected method must still surface as
|
|
118
|
+
* "unreachable" at build time (the pre-#2638 behavior), and this function
|
|
119
|
+
* can only ever return names that exist in the manifest's `methods` map —
|
|
120
|
+
* it would silently drop such an entry instead of flagging it. See
|
|
121
|
+
* `resolveCliActionSet` in `vite-plugin/sveltekit-generator.ts` for the
|
|
122
|
+
* full rationale; do not "simplify" that branch onto this function.
|
|
123
|
+
*
|
|
124
|
+
* `crudActionNames` stays a parameter because the callers do not agree on one
|
|
125
|
+
* list. `CLIGenerator` now passes {@link CRUD_OPERATIONS}, which #2646 moved
|
|
126
|
+
* into this module — so for that caller the argument is a round trip and could
|
|
127
|
+
* be defaulted. The SvelteKit generator still passes its own
|
|
128
|
+
* `STANDARD_API_ACTIONS` and `vite-plugin/index.ts` an inline literal, so the
|
|
129
|
+
* parameter cannot be removed until those copies are retired (#2665).
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveCustomActionNames(methods: Iterable<[string, ResolvableMethod]>, config: {
|
|
132
|
+
include?: string[];
|
|
133
|
+
exclude?: string[];
|
|
134
|
+
} | undefined, crudActionNames: readonly string[]): Set<string>;
|
|
135
|
+
/**
|
|
136
|
+
* The CRUD verbs a generated surface emits directly. A method whose name
|
|
137
|
+
* collides with one of these may not be exposed as a custom action under that
|
|
138
|
+
* name: the generated operation already claims it, so a second command/tool
|
|
139
|
+
* would land on a name that is taken.
|
|
140
|
+
*
|
|
141
|
+
* This is a NAMESPACE rule, independent of where the method came from — a
|
|
142
|
+
* class's own `list()` collides exactly as a merged ancestor's does (#2646).
|
|
143
|
+
*
|
|
144
|
+
* What a collision means differs by EMITTER, so consult the one you are
|
|
145
|
+
* changing rather than assuming a single rule. The reservation lives at each
|
|
146
|
+
* emission site, not here, and several emitters still keep their own inline
|
|
147
|
+
* verb array — find them with:
|
|
148
|
+
*
|
|
149
|
+
* grep -rn "'update', 'delete'" packages --include='*.ts' | grep -v include:
|
|
150
|
+
*
|
|
151
|
+
* (the `include:` filter drops decorator config, which names the same verbs)
|
|
152
|
+
*
|
|
153
|
+
* The sites this rule was audited against (#2646), NOT an exhaustive
|
|
154
|
+
* inventory:
|
|
155
|
+
*
|
|
156
|
+
* - `generators/mcp.ts` — unconditional, case-folded. `executeAction` switches
|
|
157
|
+
* on the verb parsed out of the tool id, so `${object}_list` runs the
|
|
158
|
+
* built-in list whichever branch emitted it: the class's method could never
|
|
159
|
+
* run, and emitting one would hand the caller an operation `include` never
|
|
160
|
+
* named.
|
|
161
|
+
* - `generators/cli.ts` (`CLIGenerator`) — unconditional, exact.
|
|
162
|
+
* `assertCommandExposed` returns inside its `isCrud` branch, so a CRUD-named
|
|
163
|
+
* action never reaches custom-method resolution, and `listCommands` skips
|
|
164
|
+
* those names outright.
|
|
165
|
+
* - `packages/cli/src/cli-generator.ts`, behind the shipped `smrt` object
|
|
166
|
+
* commands — reserves only where the CRUD command is actually emitted, and
|
|
167
|
+
* reserves the command NAMES AND THEIR ALIASES (`ls`, `show`, `new`, `edit`,
|
|
168
|
+
* `rm`), because lookup matches aliases too (#2648). The set is derived from
|
|
169
|
+
* the commands actually pushed, so it cannot drift from those aliases. Each
|
|
170
|
+
* command carries its own handler invoking the class's method, so with
|
|
171
|
+
* `cli: { include: ['list', 'get'] }` neither a public `create()` nor an
|
|
172
|
+
* `edit()` collides with anything and both stay reachable.
|
|
173
|
+
* - `vite-plugin/sveltekit-generator.ts` — unconditional, exact, from its own
|
|
174
|
+
* `STANDARD_API_ACTIONS` copy.
|
|
175
|
+
* - `vite-plugin/web-collections.ts` + `tool-schema.ts` — case-folded (ids are
|
|
176
|
+
* lowercased whole like MCP's), CONDITIONAL, and scoped PER COLLECTION.
|
|
177
|
+
* Applied in both of `selectWebMcpToolEntries`' loops and at the shared
|
|
178
|
+
* `buildWebToolDescriptorsForHost` choke point, which the legacy
|
|
179
|
+
* per-collection descriptor export also passes through (#2648).
|
|
180
|
+
*
|
|
181
|
+
* Conditional, unlike `generators/mcp.ts`: MCP dispatch parses the verb out
|
|
182
|
+
* of the tool id, so `${obj}_list` can only ever run the built-in list, but a
|
|
183
|
+
* WebMCP descriptor carries its own `route` from `resolveApiActionRouteConfig`
|
|
184
|
+
* — with `api: { include: ['List'] }`, `product_list` dispatches to
|
|
185
|
+
* `/products/List` and is the only tool for that id, so reserving it would
|
|
186
|
+
* make a custom-action-only model undiscoverable.
|
|
187
|
+
*
|
|
188
|
+
* Per collection, not per host: the id prefix is the OWNER's `className`, so
|
|
189
|
+
* every host mapping to one collection shares the namespace. A host-local
|
|
190
|
+
* check lets a model that excludes `list` but declares `List()` claim
|
|
191
|
+
* `product_list`, after which a sibling's real `list` is dropped by the
|
|
192
|
+
* fold-dedupe. The emitted-verb set is the union over the collection's hosts.
|
|
193
|
+
*
|
|
194
|
+
* `resolveApiActionSet` stays exact-match: REST routes keep declared casing,
|
|
195
|
+
* so `/products/List` is genuinely distinct from `/products`.
|
|
196
|
+
*
|
|
197
|
+
* Still carrying their own verb copies, tracked on #2665:
|
|
198
|
+
* `vite-plugin/api-client-entries.ts`, `vite-plugin/index.ts` and
|
|
199
|
+
* `vite-plugin/templates/default-ui.ts`.
|
|
200
|
+
*
|
|
201
|
+
* Read the list through {@link isCrudOperation} or {@link isCrudToolAction}
|
|
202
|
+
* rather than re-declaring it; the two differ only in case folding, because the
|
|
203
|
+
* transports namespace differently (see below).
|
|
204
|
+
*/
|
|
205
|
+
export declare const CRUD_OPERATIONS: readonly ["list", "get", "create", "update", "delete"];
|
|
206
|
+
/**
|
|
207
|
+
* Exact-match test for a case-SENSITIVE surface. The CLI keeps a method's
|
|
208
|
+
* declared casing in its command name (`${object}:${methodName}`) and resolves
|
|
209
|
+
* an action by exact match, so `foo:List` is a distinct, callable custom
|
|
210
|
+
* command that must not be folded into `foo:list`.
|
|
211
|
+
*/
|
|
212
|
+
export declare function isCrudOperation(name: string): boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Case-FOLDED test for a lowercase tool namespace. MCP tool identifiers are
|
|
215
|
+
* lowercased whole (`` `${object}_${methodName}`.toLowerCase() ``) for a stable
|
|
216
|
+
* protocol vocabulary, so a method named `List` lands on the identifier
|
|
217
|
+
* `object_list` that the generated CRUD tool already owns. The namespace key is
|
|
218
|
+
* the lowercased name, so the collision test has to be too (#2646).
|
|
219
|
+
*/
|
|
220
|
+
export declare function isCrudToolAction(name: string): boolean;
|
|
5
221
|
export interface CustomActionMetadata {
|
|
6
222
|
scope: CustomActionScope;
|
|
7
223
|
/** An item-targeted action requires its target identifier. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-action.d.ts","sourceRoot":"","sources":["../../src/generators/custom-action.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,YAAY,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,iBAAiB,CAAC;IACzB,8DAA8D;IAC9D,UAAU,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,+EAA+E;AAC/E,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,GAC7D,QAAQ,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC;AAE9E;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,IAAI,CAAC,oBAAoB,EAAE,YAAY,CAAC,EACnD,aAAa,EAAE,MAAM,GACpB,MAAM,CAER;AAED,MAAM,WAAW,kCAAkC;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;KAC7C,CAAC;IACF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gFAAgF;IAChF,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAED,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1C,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,4BAA4B,CAmC9B;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,oBAAoB,GAC7B,UAAU,CAuDZ;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,oBAAoB,EAC9B,IAAI,EAAE,QAAQ,GACb,OAAO,EAAE,CAwBX;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,0BAA0B,CAAC;AAE7E;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,OAAO,GACb,mBAAmB,GAAG,SAAS,CA6BjC"}
|
|
1
|
+
{"version":3,"file":"custom-action.d.ts","sourceRoot":"","sources":["../../src/generators/custom-action.ts"],"names":[],"mappings":"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"}
|