@happyvertical/smrt-core 0.40.48 → 0.40.50
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/generators.md +32 -0
- package/dist/generators/cli.d.ts +11 -3
- package/dist/generators/cli.d.ts.map +1 -1
- package/dist/generators/cli.js +54 -11
- package/dist/generators/cli.js.map +1 -1
- package/dist/generators/custom-action.d.ts +68 -0
- package/dist/generators/custom-action.d.ts.map +1 -0
- package/dist/generators/custom-action.js +128 -0
- package/dist/generators/custom-action.js.map +1 -0
- package/dist/generators/index.d.ts +1 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -1
- package/dist/generators/mcp-runtime-template.d.ts +11 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +40 -12
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +6 -0
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +157 -67
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/tool-schema.d.ts +3 -1
- package/dist/generators/tool-schema.d.ts.map +1 -1
- package/dist/generators/tool-schema.js +8 -17
- package/dist/generators/tool-schema.js.map +1 -1
- package/dist/generators.js +2 -1
- package/dist/index.js +2 -1
- package/dist/manifest/static-manifest.js +2 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
- package/dist/manifest/test-manifest-stub.js +405 -581
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +10 -4
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/smrt-knowledge.json +6 -7
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +6 -7
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/index.d.ts +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +2 -2
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +29 -5
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +7 -1
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/dist/vite-plugin.js +2 -2
- package/package.json +4 -5
- package/dist/__tests__/fixtures/advisor-test-classes.d.ts +0 -37
- package/dist/__tests__/fixtures/advisor-test-classes.d.ts.map +0 -1
- package/dist/mcp-advisor/index.d.ts +0 -499
- package/dist/mcp-advisor/index.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/add-ai-methods.d.ts +0 -6
- package/dist/mcp-advisor/tools/add-ai-methods.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/configure-decorators.d.ts +0 -6
- package/dist/mcp-advisor/tools/configure-decorators.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/generate-collection.d.ts +0 -6
- package/dist/mcp-advisor/tools/generate-collection.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/generate-field-definitions.d.ts +0 -6
- package/dist/mcp-advisor/tools/generate-field-definitions.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/generate-smrt-class.d.ts +0 -6
- package/dist/mcp-advisor/tools/generate-smrt-class.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/get-object-config.d.ts +0 -6
- package/dist/mcp-advisor/tools/get-object-config.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/get-object-schema.d.ts +0 -10
- package/dist/mcp-advisor/tools/get-object-schema.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/list-registered-objects.d.ts +0 -9
- package/dist/mcp-advisor/tools/list-registered-objects.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts +0 -10
- package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/preview-mcp-tools.d.ts +0 -9
- package/dist/mcp-advisor/tools/preview-mcp-tools.d.ts.map +0 -1
- package/dist/mcp-advisor/tools/validate-smrt-object.d.ts +0 -6
- package/dist/mcp-advisor/tools/validate-smrt-object.d.ts.map +0 -1
- package/dist/mcp-advisor/types.d.ts +0 -213
- package/dist/mcp-advisor/types.d.ts.map +0 -1
package/agents/generators.md
CHANGED
|
@@ -27,4 +27,36 @@ The web module also emits a build-time **`manifestHash`** constant (#1764): `com
|
|
|
27
27
|
|
|
28
28
|
Per-field web emission (#2046): `buildWebFieldDefinitions` carries `description` (from `@field({ description })`) and sanitized `ui` hints (from `@field({ ui: { basic, group, order, locked } })`, read off the manifest `_meta.ui` bag through per-key type guards) into each emitted field definition, and `buildWebToolDescriptors` threads the same `description` into browser MCP tool schemas. `sensitive`/`transient` fields are excluded from emission entirely, so their descriptions never ship. Both keys are conditional, so hint-less schemas emit byte-identical definitions (and hashes) as before; adding a description/ui hint changes the manifest hash — deliberate over-invalidation, harmless per the #1764 contract.
|
|
29
29
|
|
|
30
|
+
## Custom-action contract
|
|
31
|
+
|
|
32
|
+
`resolveCustomActionMetadata()` is the common discovery and invocation contract
|
|
33
|
+
for generated REST routes and API clients, MCP, CLI, WebMCP, and simple
|
|
34
|
+
REST-resource discovery. Receiver scope comes from the executable method, never a
|
|
35
|
+
configuration-only `api.routes[name].scope` override: instance model methods
|
|
36
|
+
are item-scoped and require `id`; static model methods and recognized
|
|
37
|
+
`SmrtCollection` methods are collection-scoped and do not accept `id`. Route
|
|
38
|
+
configuration may still choose its path and HTTP verb, but it cannot turn an
|
|
39
|
+
instance call into `ClassRef.action` or vice versa.
|
|
40
|
+
|
|
41
|
+
When scanner method metadata exists, discovery projects each named parameter
|
|
42
|
+
and its JSON-schema type, and invokers pass the values positionally in declared
|
|
43
|
+
order. The legacy single `options` bag remains compatible when metadata is
|
|
44
|
+
absent (or the declared method takes `options`). Do not infer this from runtime
|
|
45
|
+
function arity. An omitted typed `options` parameter remains `undefined`, so a
|
|
46
|
+
method's JavaScript default initializer continues to apply; an explicit `null`
|
|
47
|
+
remains `null`. Flat tool and CLI inputs reserve `id` for receiver parsing. If
|
|
48
|
+
an action declares an `id` parameter, its flat MCP/WebMCP field is `actionId`
|
|
49
|
+
(and CLI uses `--action-id`); REST keeps its independent path/body
|
|
50
|
+
namespaces. Typed CLI actions may use standard flag names such as `limit`,
|
|
51
|
+
`offset`, `where`, and `format` without those values being stripped as CRUD
|
|
52
|
+
flags.
|
|
53
|
+
|
|
54
|
+
Custom actions may return an explicit, domain-neutral failure object with
|
|
55
|
+
`ok: false`, `code`, and `message` plus optional `status`, `details`,
|
|
56
|
+
`retryable`, and `correlationId`. `normalizeCustomActionFailure()` redacts it;
|
|
57
|
+
generated REST returns `{ error: failure }` with the non-2xx status, while MCP
|
|
58
|
+
returns `isError: true` and `_meta['io.happyvertical/smrt']`. Opaque successful
|
|
59
|
+
objects (including `{ code, message }`) remain untouched; thrown exceptions are
|
|
60
|
+
not reclassified as domain failures.
|
|
61
|
+
|
|
30
62
|
Generated reads (`list`/`get`) on the REST and SvelteKit generators support conditional GET (helpers in `src/generators/conditional-get.ts`). ETag v2 (#1765): the validator is the table's change-feed version (`getTableVersion`) keyed by the request representation, so a **concrete** `If-None-Match` short-circuits into a 304 with an empty body **before** the collection query runs — an unchanged table revalidates with zero table scan. A wildcard `If-None-Match: *` is deferred until the payload builds (existence confirmed), so a missing item still returns 404, not a false 304. Tenant-scoped reads fold the active tenant into the representation (`resolveTenantEtagDiscriminator`) so one tenant's cached validator never satisfies another's read of the same URL. Routes whose GET renders via a **custom serializer** (which can load related tables the base-table version can't observe) keep the v1 body-hash ETag (`#1757`, query-first but correct); the default `toPublicJSON` path — all REST reads and non-serializer SvelteKit reads — uses v2. v2 is weakly consistent by design (the cost of not reading the data): a revalidation in the sub-statement window between a committed write and its feed append can return a stale 304 that self-heals on the next revalidation. The other v2 window — a deploy that changes the response shape WITHOUT a table write — is closed by the **#1764 ETag salt**: `computeTableVersionEtag(version, representation, manifestHash?)` folds the build's web-collection shape digest into the digest, so a shape-only redeploy busts every read validator (`undefined` reproduces the pre-#1764 unsalted value byte-for-byte for direct helper callers). The generated SvelteKit route bakes the digest in as a `MANIFEST_HASH` constant (via `generateConditionalGetRouteHelper`'s `manifestHash` option, sourced from `computeWebManifestHash(manifest)`) — automatic for the SvelteKit transport. The runtime `APIGenerator` auto-populates the same salt from the runtime registry with `computeRuntimeWebManifestHash()` when `APIConfig.manifestHash` is omitted; explicit `APIConfig.manifestHash` still wins for custom setups. The digest scope is get-OR-list (`selectWebEtagSaltEntries`), so **get-only** routes are salted too. Strong consistency still requires the v1 body-hash path. Cache-Control policy (unchanged from #1757): `private, no-cache` by default; public models may opt into shared caching via `@smrt({ api: { public: true | 'read', cache: { sMaxage } } })` → `public, max-age=0, s-maxage=<n>`; non-public models never emit shared-cache headers. Tenant-scoped models (any mode) never emit them either — bodies vary with session-cookie tenant context that URL-keyed shared caches cannot see; `sMaxage` is neutralized to `private, no-cache` with a one-time warning.
|
package/dist/generators/cli.d.ts
CHANGED
|
@@ -90,17 +90,25 @@ export declare class CLIGenerator {
|
|
|
90
90
|
*/
|
|
91
91
|
private executeAction;
|
|
92
92
|
/**
|
|
93
|
-
* Execute a custom method
|
|
94
|
-
*
|
|
93
|
+
* Execute a custom method against its manifest-declared item/collection
|
|
94
|
+
* target. The legacy collection fallback remains only for unavailable
|
|
95
|
+
* metadata, where target scope cannot be established safely.
|
|
95
96
|
*/
|
|
96
97
|
private executeCustomAction;
|
|
98
|
+
private hasCollectionReceiver;
|
|
97
99
|
/**
|
|
98
100
|
* Resolve the create/update payload: `--from-file <path>` (JSON) takes
|
|
99
101
|
* precedence; otherwise non-reserved `--field value` flags form the body.
|
|
100
102
|
* Reading from a file also keeps secrets off the process argv (#1556).
|
|
101
103
|
*/
|
|
102
104
|
private readPayload;
|
|
103
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Collect custom-action flags. Typed action parameters may deliberately use
|
|
107
|
+
* standard CLI names such as `limit`, `offset`, `where`, or `format`; retain
|
|
108
|
+
* those rather than silently dropping them as CRUD flags. Item actions keep
|
|
109
|
+
* `--id` for their receiver and accept an action parameter named `id` as
|
|
110
|
+
* `--action-id` to preserve separate namespaces.
|
|
111
|
+
*/
|
|
104
112
|
private customOptions;
|
|
105
113
|
/**
|
|
106
114
|
* Mass-assignment guard (#1540/#1556): strip framework/server-managed and
|
|
@@ -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;AA8BrD;;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;AAoCD;;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;IA+ClC;;;;;;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;YAmCzB,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,UAroBE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAyoBrD"}
|
package/dist/generators/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
1
2
|
import { ObjectRegistry } from "../registry.js";
|
|
3
|
+
import { SmrtCollection } from "../collection.js";
|
|
2
4
|
import { runWithTenantGate } from "./tenant-gate.js";
|
|
3
5
|
//#region src/generators/cli.ts
|
|
4
6
|
/** Standard CRUD verbs handled directly by the generator. */
|
|
@@ -232,27 +234,58 @@ var CLIGenerator = class {
|
|
|
232
234
|
message: `${objectName} deleted`
|
|
233
235
|
};
|
|
234
236
|
}
|
|
235
|
-
default: return this.executeCustomAction(collection, action, positional, flags);
|
|
237
|
+
default: return this.executeCustomAction(objectName, collection, action, positional, flags);
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
/**
|
|
239
|
-
* Execute a custom method
|
|
240
|
-
*
|
|
241
|
+
* Execute a custom method against its manifest-declared item/collection
|
|
242
|
+
* target. The legacy collection fallback remains only for unavailable
|
|
243
|
+
* metadata, where target scope cannot be established safely.
|
|
241
244
|
*/
|
|
242
|
-
async executeCustomAction(collection, action, positional, flags) {
|
|
243
|
-
const options = this.customOptions(flags);
|
|
245
|
+
async executeCustomAction(objectName, collection, action, positional, flags) {
|
|
244
246
|
const id = positional ?? this.flagString(flags.id);
|
|
247
|
+
const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(action);
|
|
248
|
+
const metadata = resolveCustomActionMetadata({
|
|
249
|
+
actionName: action,
|
|
250
|
+
method: methodDef,
|
|
251
|
+
apiConfig: ObjectRegistry.getConfig(objectName).api,
|
|
252
|
+
...this.hasCollectionReceiver(ObjectRegistry.getClass(objectName)) ? { defaultScope: "collection" } : {}
|
|
253
|
+
});
|
|
254
|
+
const args = this.customOptions(flags, metadata);
|
|
255
|
+
const methodArgs = buildCustomActionInvocationArgs(metadata, metadata.parameters?.length === 1 && metadata.parameters[0]?.name === "options" ? Object.keys(args).length > 0 ? { options: args } : {} : args);
|
|
256
|
+
if (methodDef && metadata.idRequired && !id) throw new Error(`An id is required for custom action '${action}'`);
|
|
257
|
+
if (methodDef && !metadata.idRequired && id) throw new Error(`Custom action '${action}' is collection-scoped and does not accept an id`);
|
|
245
258
|
if (id) {
|
|
246
259
|
const object = await collection.get(id);
|
|
247
260
|
if (!object) throw new Error("Object not found");
|
|
248
261
|
const candidate = object[action];
|
|
249
262
|
if (typeof candidate !== "function") throw new Error(`Method '${action}' not found on object instance`);
|
|
250
|
-
|
|
263
|
+
const result = await candidate.call(object, ...methodArgs);
|
|
264
|
+
const failure = normalizeCustomActionFailure(result);
|
|
265
|
+
if (failure) throw new Error(JSON.stringify({ error: failure }));
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
if (methodDef?.isStatic) {
|
|
269
|
+
const classInfo = ObjectRegistry.getClass(objectName);
|
|
270
|
+
const candidate = (classInfo?.constructor)?.[action];
|
|
271
|
+
if (typeof candidate !== "function") throw new Error(`Static method '${action}' not found on ${objectName}`);
|
|
272
|
+
const result = await candidate.call(classInfo?.constructor, ...methodArgs);
|
|
273
|
+
const failure = normalizeCustomActionFailure(result);
|
|
274
|
+
if (failure) throw new Error(JSON.stringify({ error: failure }));
|
|
275
|
+
return result;
|
|
251
276
|
}
|
|
252
277
|
const collectionCandidate = collection[action];
|
|
253
|
-
if (typeof collectionCandidate === "function")
|
|
278
|
+
if (typeof collectionCandidate === "function") {
|
|
279
|
+
const result = await collectionCandidate.call(collection, ...methodArgs);
|
|
280
|
+
const failure = normalizeCustomActionFailure(result);
|
|
281
|
+
if (failure) throw new Error(JSON.stringify({ error: failure }));
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
254
284
|
throw new Error(`Method '${action}' not found on collection. Provide an id for object-specific actions.`);
|
|
255
285
|
}
|
|
286
|
+
hasCollectionReceiver(classInfo) {
|
|
287
|
+
return !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection;
|
|
288
|
+
}
|
|
256
289
|
/**
|
|
257
290
|
* Resolve the create/update payload: `--from-file <path>` (JSON) takes
|
|
258
291
|
* precedence; otherwise non-reserved `--field value` flags form the body.
|
|
@@ -274,11 +307,21 @@ var CLIGenerator = class {
|
|
|
274
307
|
}
|
|
275
308
|
return data;
|
|
276
309
|
}
|
|
277
|
-
/**
|
|
278
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Collect custom-action flags. Typed action parameters may deliberately use
|
|
312
|
+
* standard CLI names such as `limit`, `offset`, `where`, or `format`; retain
|
|
313
|
+
* those rather than silently dropping them as CRUD flags. Item actions keep
|
|
314
|
+
* `--id` for their receiver and accept an action parameter named `id` as
|
|
315
|
+
* `--action-id` to preserve separate namespaces.
|
|
316
|
+
*/
|
|
317
|
+
customOptions(flags, metadata) {
|
|
318
|
+
const declaredInputs = new Set((metadata?.parameters ?? []).map((parameter) => customActionParameterInputName(metadata ?? { idRequired: false }, parameter.name)));
|
|
279
319
|
const options = {};
|
|
280
|
-
for (const [
|
|
281
|
-
|
|
320
|
+
for (const [rawKey, value] of Object.entries(flags)) {
|
|
321
|
+
const key = rawKey === "action-id" ? "actionId" : rawKey;
|
|
322
|
+
if (rawKey === "id") continue;
|
|
323
|
+
if (RESERVED_FLAGS.has(rawKey) && !declaredInputs.has(key)) continue;
|
|
324
|
+
if (rawKey === "action-id" && !declaredInputs.has(key)) continue;
|
|
282
325
|
options[key] = this.coerceValue(value);
|
|
283
326
|
}
|
|
284
327
|
return options;
|
|
@@ -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 type { 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 { 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 ((options: Record<string, 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(collection, action, positional, flags);\n }\n }\n\n /**\n * Execute a custom method on an instance (when an id is given) or on the\n * collection (singleton actions). Mirrors the MCP custom-action path.\n */\n private async executeCustomAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n positional: string | undefined,\n flags: Record<string, string | boolean>,\n ): Promise<unknown> {\n const options = this.customOptions(flags);\n const id = positional ?? this.flagString(flags.id);\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 return candidate.call(object, options);\n }\n\n const collectionCandidate = (collection as unknown as DynamicallyCallable)[\n action\n ];\n if (typeof collectionCandidate === 'function') {\n return collectionCandidate.call(collection, options);\n }\n throw new Error(\n `Method '${action}' not found on collection. Provide an id for object-specific actions.`,\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 /** Collect non-reserved flags as custom-action options. */\n private customOptions(\n flags: Record<string, string | boolean>,\n ): Record<string, unknown> {\n const options: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(flags)) {\n if (RESERVED_FLAGS.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 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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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MethodDefinition } from '../scanner/types.js';
|
|
2
|
+
export type CustomActionScope = 'item' | 'collection';
|
|
3
|
+
export interface CustomActionMetadata {
|
|
4
|
+
scope: CustomActionScope;
|
|
5
|
+
/** An item-targeted action requires its target identifier. */
|
|
6
|
+
idRequired: boolean;
|
|
7
|
+
/** Present only when scanner/manifest metadata is available. */
|
|
8
|
+
parameters?: MethodDefinition['parameters'];
|
|
9
|
+
/** Collection actions on a model class invoke its static method. */
|
|
10
|
+
isStatic: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Return the transport field for a method parameter. Flat tool and CLI
|
|
14
|
+
* transports reserve `id` for receiver parsing even when a collection action
|
|
15
|
+
* rejects it, so every action parameter named `id` is exposed as `actionId`.
|
|
16
|
+
* REST already has separate path/body namespaces.
|
|
17
|
+
*/
|
|
18
|
+
export declare function customActionParameterInputName(_metadata: Pick<CustomActionMetadata, 'idRequired'>, parameterName: string): string;
|
|
19
|
+
export interface ResolveCustomActionMetadataOptions {
|
|
20
|
+
actionName: string;
|
|
21
|
+
method?: {
|
|
22
|
+
isStatic?: boolean;
|
|
23
|
+
parameters?: MethodDefinition['parameters'];
|
|
24
|
+
};
|
|
25
|
+
apiConfig?: unknown;
|
|
26
|
+
/** Collection-class actions have a collection receiver even when non-static. */
|
|
27
|
+
defaultScope?: CustomActionScope;
|
|
28
|
+
}
|
|
29
|
+
type JsonSchema = Record<string, unknown>;
|
|
30
|
+
type ToolArgs = Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the target contract shared by MCP, generated REST, CLI discovery,
|
|
33
|
+
* and WebMCP. A missing manifest method retains the historical item-shaped
|
|
34
|
+
* schema; runtime callers may still provide their legacy collection fallback.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveCustomActionMetadata(options: ResolveCustomActionMetadataOptions): CustomActionMetadata;
|
|
37
|
+
/** Build the custom-action portion of an MCP/WebMCP JSON Schema. */
|
|
38
|
+
export declare function buildCustomActionInputSchema(metadata: CustomActionMetadata): JsonSchema;
|
|
39
|
+
/**
|
|
40
|
+
* Translate a transport object into the method's call arguments. Legacy
|
|
41
|
+
* actions retain their single options-bag invocation; scanner metadata enables
|
|
42
|
+
* an exact positional projection without changing legacy action behavior.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildCustomActionInvocationArgs(metadata: CustomActionMetadata, args: ToolArgs): unknown[];
|
|
45
|
+
/**
|
|
46
|
+
* Domain-neutral returned-failure convention for custom actions. The explicit
|
|
47
|
+
* `ok: false` marker prevents successful opaque values such as `{ code,
|
|
48
|
+
* message }` from being reclassified as failures by a transport.
|
|
49
|
+
*/
|
|
50
|
+
export interface CustomActionFailure {
|
|
51
|
+
ok: false;
|
|
52
|
+
code: string;
|
|
53
|
+
message: string;
|
|
54
|
+
status: number;
|
|
55
|
+
details?: unknown;
|
|
56
|
+
retryable?: boolean;
|
|
57
|
+
correlationId?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Stable MCP `_meta` member shared with the app discovery contract (#2181). */
|
|
60
|
+
export declare const SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY = "io.happyvertical/smrt";
|
|
61
|
+
/**
|
|
62
|
+
* Detect, validate, and redact an explicitly returned custom-action failure.
|
|
63
|
+
* Unknown return values remain opaque successes. `status` defaults to 400 so
|
|
64
|
+
* REST callers receive non-2xx semantics even when an adapter omits it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function normalizeCustomActionFailure(value: unknown): CustomActionFailure | undefined;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=custom-action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-action.d.ts","sourceRoot":"","sources":["../../src/generators/custom-action.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,YAAY,CAAC;AAEtD,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;CACnB;AAED;;;;;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,oBAAoB,CAqBtB;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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { convertTypeToJsonSchema } from "../tools/tool-generator.js";
|
|
2
|
+
//#region src/generators/custom-action.ts
|
|
3
|
+
/**
|
|
4
|
+
* Return the transport field for a method parameter. Flat tool and CLI
|
|
5
|
+
* transports reserve `id` for receiver parsing even when a collection action
|
|
6
|
+
* rejects it, so every action parameter named `id` is exposed as `actionId`.
|
|
7
|
+
* REST already has separate path/body namespaces.
|
|
8
|
+
*/
|
|
9
|
+
function customActionParameterInputName(_metadata, parameterName) {
|
|
10
|
+
return parameterName === "id" ? "actionId" : parameterName;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the target contract shared by MCP, generated REST, CLI discovery,
|
|
14
|
+
* and WebMCP. A missing manifest method retains the historical item-shaped
|
|
15
|
+
* schema; runtime callers may still provide their legacy collection fallback.
|
|
16
|
+
*/
|
|
17
|
+
function resolveCustomActionMetadata(options) {
|
|
18
|
+
const defaultScope = options.defaultScope ?? (options.method?.isStatic ? "collection" : "item");
|
|
19
|
+
const requestedScope = readConfiguredScope(options.apiConfig, options.actionName);
|
|
20
|
+
const scope = requestedScope === defaultScope ? requestedScope : defaultScope;
|
|
21
|
+
return {
|
|
22
|
+
scope,
|
|
23
|
+
idRequired: scope === "item",
|
|
24
|
+
...options.method?.parameters ? { parameters: options.method.parameters } : {},
|
|
25
|
+
isStatic: options.method?.isStatic === true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Build the custom-action portion of an MCP/WebMCP JSON Schema. */
|
|
29
|
+
function buildCustomActionInputSchema(metadata) {
|
|
30
|
+
const properties = {};
|
|
31
|
+
const required = [];
|
|
32
|
+
if (metadata.idRequired) {
|
|
33
|
+
properties.id = {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "ID of the object to execute action on"
|
|
36
|
+
};
|
|
37
|
+
required.push("id");
|
|
38
|
+
}
|
|
39
|
+
if (!metadata.parameters) properties.options = {
|
|
40
|
+
type: "object",
|
|
41
|
+
description: "Additional options for the custom action",
|
|
42
|
+
additionalProperties: true
|
|
43
|
+
};
|
|
44
|
+
else if (metadata.parameters.length === 1 && metadata.parameters[0]?.name === "options") {
|
|
45
|
+
const parameter = metadata.parameters[0];
|
|
46
|
+
properties.options = {
|
|
47
|
+
...convertTypeToJsonSchema(parameter.type),
|
|
48
|
+
description: "Options for the custom action",
|
|
49
|
+
...parameter.default !== void 0 ? { default: parameter.default } : {}
|
|
50
|
+
};
|
|
51
|
+
if (!parameter.optional) required.push("options");
|
|
52
|
+
} else for (const parameter of metadata.parameters) {
|
|
53
|
+
const inputName = customActionParameterInputName(metadata, parameter.name);
|
|
54
|
+
properties[inputName] = {
|
|
55
|
+
...convertTypeToJsonSchema(parameter.type),
|
|
56
|
+
...parameter.default !== void 0 ? { default: parameter.default } : {}
|
|
57
|
+
};
|
|
58
|
+
if (!parameter.optional) required.push(inputName);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties,
|
|
63
|
+
...required.length > 0 ? { required } : {}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Translate a transport object into the method's call arguments. Legacy
|
|
68
|
+
* actions retain their single options-bag invocation; scanner metadata enables
|
|
69
|
+
* an exact positional projection without changing legacy action behavior.
|
|
70
|
+
*/
|
|
71
|
+
function buildCustomActionInvocationArgs(metadata, args) {
|
|
72
|
+
const { id: _id, options, ...directArgs } = args;
|
|
73
|
+
if (!metadata.parameters) return [isRecord(options) && Object.keys(options).length > 0 ? options : directArgs];
|
|
74
|
+
if (metadata.parameters.length === 0) return [];
|
|
75
|
+
if (metadata.parameters.length === 1 && metadata.parameters[0]?.name === "options") return [options];
|
|
76
|
+
return metadata.parameters.map((parameter) => args[customActionParameterInputName(metadata, parameter.name)]);
|
|
77
|
+
}
|
|
78
|
+
/** Stable MCP `_meta` member shared with the app discovery contract (#2181). */
|
|
79
|
+
var SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY = "io.happyvertical/smrt";
|
|
80
|
+
/**
|
|
81
|
+
* Detect, validate, and redact an explicitly returned custom-action failure.
|
|
82
|
+
* Unknown return values remain opaque successes. `status` defaults to 400 so
|
|
83
|
+
* REST callers receive non-2xx semantics even when an adapter omits it.
|
|
84
|
+
*/
|
|
85
|
+
function normalizeCustomActionFailure(value) {
|
|
86
|
+
if (!isRecord(value) || value.ok !== false) return void 0;
|
|
87
|
+
if (typeof value.code !== "string" || typeof value.message !== "string") return;
|
|
88
|
+
const status = typeof value.status === "number" && Number.isInteger(value.status) && value.status >= 400 && value.status <= 599 ? value.status : 400;
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
code: value.code,
|
|
92
|
+
message: redactText(value.message),
|
|
93
|
+
status,
|
|
94
|
+
...Object.hasOwn(value, "details") ? { details: redactValue(value.details) } : {},
|
|
95
|
+
...typeof value.retryable === "boolean" ? { retryable: value.retryable } : {},
|
|
96
|
+
...typeof value.correlationId === "string" ? { correlationId: value.correlationId } : {}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function readConfiguredScope(apiConfig, actionName) {
|
|
100
|
+
if (!isRecord(apiConfig) || !isRecord(apiConfig.routes)) return void 0;
|
|
101
|
+
const route = apiConfig.routes[actionName];
|
|
102
|
+
return isRecord(route) && (route.scope === "item" || route.scope === "collection") ? route.scope : void 0;
|
|
103
|
+
}
|
|
104
|
+
function redactValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
105
|
+
if (typeof value === "string") return redactText(value);
|
|
106
|
+
if (value === null || typeof value !== "object") return value;
|
|
107
|
+
if (seen.has(value)) return "[REDACTED]";
|
|
108
|
+
seen.add(value);
|
|
109
|
+
if (Array.isArray(value)) return value.map((entry) => redactValue(entry, seen));
|
|
110
|
+
const prototype = Object.getPrototypeOf(value);
|
|
111
|
+
if (prototype !== Object.prototype && prototype !== null) return "[REDACTED]";
|
|
112
|
+
const result = {};
|
|
113
|
+
for (const [key, nested] of Object.entries(value)) result[key] = isSensitiveKey(key) ? "[REDACTED]" : redactValue(nested, seen);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function redactText(value) {
|
|
117
|
+
return value.replace(/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]").replace(/\b(token|secret|password|api[_-]?key)=([^\s&]+)/giu, "$1=[REDACTED]");
|
|
118
|
+
}
|
|
119
|
+
function isSensitiveKey(key) {
|
|
120
|
+
return /(?:token|secret|password|authorization|cookie|credential|api[_-]?key)/iu.test(key);
|
|
121
|
+
}
|
|
122
|
+
function isRecord(value) {
|
|
123
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=custom-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-action.js","names":[],"sources":["../../src/generators/custom-action.ts"],"sourcesContent":["/**\n * Canonical custom-action metadata and transport-safe result helpers.\n *\n * Custom actions are intentionally distinct from generated CRUD. Their target\n * is derived from method metadata: instance methods target an item and static\n * methods target the collection. API route configuration may shape an HTTP\n * route, but it cannot change a method's receiver.\n */\n\nimport type { MethodDefinition } from '../scanner/types.js';\nimport { convertTypeToJsonSchema } from '../tools/tool-generator.js';\n\nexport type CustomActionScope = 'item' | 'collection';\n\nexport interface CustomActionMetadata {\n scope: CustomActionScope;\n /** An item-targeted action requires its target identifier. */\n idRequired: boolean;\n /** Present only when scanner/manifest metadata is available. */\n parameters?: MethodDefinition['parameters'];\n /** Collection actions on a model class invoke its static method. */\n isStatic: boolean;\n}\n\n/**\n * Return the transport field for a method parameter. Flat tool and CLI\n * transports reserve `id` for receiver parsing even when a collection action\n * rejects it, so every action parameter named `id` is exposed as `actionId`.\n * REST already has separate path/body namespaces.\n */\nexport function customActionParameterInputName(\n _metadata: Pick<CustomActionMetadata, 'idRequired'>,\n parameterName: string,\n): string {\n return parameterName === 'id' ? 'actionId' : parameterName;\n}\n\nexport interface ResolveCustomActionMetadataOptions {\n actionName: string;\n method?: {\n isStatic?: boolean;\n parameters?: MethodDefinition['parameters'];\n };\n apiConfig?: unknown;\n /** Collection-class actions have a collection receiver even when non-static. */\n defaultScope?: CustomActionScope;\n}\n\ntype JsonSchema = Record<string, unknown>;\ntype ToolArgs = Record<string, unknown>;\n\n/**\n * Resolve the target contract shared by MCP, generated REST, CLI discovery,\n * and WebMCP. A missing manifest method retains the historical item-shaped\n * schema; runtime callers may still provide their legacy collection fallback.\n */\nexport function resolveCustomActionMetadata(\n options: ResolveCustomActionMetadataOptions,\n): CustomActionMetadata {\n const defaultScope =\n options.defaultScope ?? (options.method?.isStatic ? 'collection' : 'item');\n const requestedScope = readConfiguredScope(\n options.apiConfig,\n options.actionName,\n );\n // A route-only scope override cannot manufacture a receiver. A normal\n // instance method is always item-targeted; a static model method and a\n // recognized collection-class method are always collection-targeted. Keep\n // a matching explicit value for diagnostics/config round-tripping only.\n const scope = requestedScope === defaultScope ? requestedScope : defaultScope;\n\n return {\n scope,\n idRequired: scope === 'item',\n ...(options.method?.parameters\n ? { parameters: options.method.parameters }\n : {}),\n isStatic: options.method?.isStatic === true,\n };\n}\n\n/** Build the custom-action portion of an MCP/WebMCP JSON Schema. */\nexport function buildCustomActionInputSchema(\n metadata: CustomActionMetadata,\n): JsonSchema {\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n\n if (metadata.idRequired) {\n properties.id = {\n type: 'string',\n description: 'ID of the object to execute action on',\n };\n required.push('id');\n }\n\n // Absent metadata is the legacy options-bag contract. Do not infer direct\n // positional arguments from runtime function arity: it is lossy after\n // transpilation and would make discovery non-deterministic.\n if (!metadata.parameters) {\n properties.options = {\n type: 'object',\n description: 'Additional options for the custom action',\n additionalProperties: true,\n };\n } else if (\n metadata.parameters.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ) {\n const parameter = metadata.parameters[0];\n properties.options = {\n ...convertTypeToJsonSchema(parameter.type),\n description: 'Options for the custom action',\n ...(parameter.default !== undefined\n ? { default: parameter.default }\n : {}),\n };\n if (!parameter.optional) required.push('options');\n } else {\n for (const parameter of metadata.parameters) {\n const inputName = customActionParameterInputName(\n metadata,\n parameter.name,\n );\n properties[inputName] = {\n ...convertTypeToJsonSchema(parameter.type),\n ...(parameter.default !== undefined\n ? { default: parameter.default }\n : {}),\n };\n if (!parameter.optional) required.push(inputName);\n }\n }\n\n return {\n type: 'object',\n properties,\n ...(required.length > 0 ? { required } : {}),\n };\n}\n\n/**\n * Translate a transport object into the method's call arguments. Legacy\n * actions retain their single options-bag invocation; scanner metadata enables\n * an exact positional projection without changing legacy action behavior.\n */\nexport function buildCustomActionInvocationArgs(\n metadata: CustomActionMetadata,\n args: ToolArgs,\n): unknown[] {\n const { id: _id, options, ...directArgs } = args;\n\n if (!metadata.parameters) {\n return [\n isRecord(options) && Object.keys(options).length > 0\n ? options\n : directArgs,\n ];\n }\n if (metadata.parameters.length === 0) return [];\n if (\n metadata.parameters.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ) {\n // Preserve `undefined` (and an explicit `null`) so JavaScript default\n // parameter initializers and intentional null handling retain their native\n // semantics. Legacy options bags still receive an empty object below.\n return [options];\n }\n return metadata.parameters.map(\n (parameter) =>\n args[customActionParameterInputName(metadata, parameter.name)],\n );\n}\n\n/**\n * Domain-neutral returned-failure convention for custom actions. The explicit\n * `ok: false` marker prevents successful opaque values such as `{ code,\n * message }` from being reclassified as failures by a transport.\n */\nexport interface CustomActionFailure {\n ok: false;\n code: string;\n message: string;\n status: number;\n details?: unknown;\n retryable?: boolean;\n correlationId?: string;\n}\n\n/** Stable MCP `_meta` member shared with the app discovery contract (#2181). */\nexport const SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY = 'io.happyvertical/smrt';\n\n/**\n * Detect, validate, and redact an explicitly returned custom-action failure.\n * Unknown return values remain opaque successes. `status` defaults to 400 so\n * REST callers receive non-2xx semantics even when an adapter omits it.\n */\nexport function normalizeCustomActionFailure(\n value: unknown,\n): CustomActionFailure | undefined {\n if (!isRecord(value) || value.ok !== false) return undefined;\n if (typeof value.code !== 'string' || typeof value.message !== 'string') {\n return undefined;\n }\n\n const status =\n typeof value.status === 'number' &&\n Number.isInteger(value.status) &&\n value.status >= 400 &&\n value.status <= 599\n ? value.status\n : 400;\n\n return {\n ok: false,\n code: value.code,\n message: redactText(value.message),\n status,\n ...(Object.hasOwn(value, 'details')\n ? { details: redactValue(value.details) }\n : {}),\n ...(typeof value.retryable === 'boolean'\n ? { retryable: value.retryable }\n : {}),\n ...(typeof value.correlationId === 'string'\n ? { correlationId: value.correlationId }\n : {}),\n };\n}\n\nfunction readConfiguredScope(\n apiConfig: unknown,\n actionName: string,\n): CustomActionScope | undefined {\n if (!isRecord(apiConfig) || !isRecord(apiConfig.routes)) return undefined;\n const route = apiConfig.routes[actionName];\n return isRecord(route) &&\n (route.scope === 'item' || route.scope === 'collection')\n ? route.scope\n : undefined;\n}\n\nfunction redactValue(value: unknown, seen = new WeakSet<object>()): unknown {\n if (typeof value === 'string') return redactText(value);\n if (value === null || typeof value !== 'object') return value;\n if (seen.has(value)) return '[REDACTED]';\n seen.add(value);\n if (Array.isArray(value))\n return value.map((entry) => redactValue(entry, seen));\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return '[REDACTED]';\n const result: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value)) {\n result[key] = isSensitiveKey(key)\n ? '[REDACTED]'\n : redactValue(nested, seen);\n }\n return result;\n}\n\nfunction redactText(value: string): string {\n return value\n .replace(/\\bBearer\\s+[^\\s,;]+/giu, 'Bearer [REDACTED]')\n .replace(\n /\\b(token|secret|password|api[_-]?key)=([^\\s&]+)/giu,\n '$1=[REDACTED]',\n );\n}\n\nfunction isSensitiveKey(key: string): boolean {\n return /(?:token|secret|password|authorization|cookie|credential|api[_-]?key)/iu.test(\n key,\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export type { CLIConfig, CLIContext } from './cli';
|
|
5
5
|
export { CLIGenerator, getCLIHandler, setupCLI } from './cli';
|
|
6
6
|
export { canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, PRIVATE_READ_CACHE_CONTROL, type ReadCacheControlOptions, resolveReadCacheControl, resolveTenantEtagDiscriminator, versionConditionalResponse, warnIfSharedCacheNeutralized, } from './conditional-get';
|
|
7
|
+
export { buildCustomActionInputSchema, buildCustomActionInvocationArgs, type CustomActionFailure, type CustomActionMetadata, type CustomActionScope, customActionParameterInputName, normalizeCustomActionFailure, type ResolveCustomActionMetadataOptions, resolveCustomActionMetadata, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, } from './custom-action';
|
|
7
8
|
export { buildChangeEventStream, type ChangeEventStreamOptions, changeEventSubscribersAtCapacity, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, eventStreamCapacityExceededResponse, normalizeEventsMaxSubscribers, signalVisibleToTenant, tryReserveChangeEventSubscriberSlot, } from './events-route';
|
|
8
9
|
export type { MCPConfig, MCPContext, MCPRequest, MCPResponse, MCPTool, } from './mcp';
|
|
9
10
|
export { MCPGenerator } from './mcp';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,4BAA4B,EAC5B,+BAA+B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,8BAA8B,EAC9B,4BAA4B,EAC5B,KAAK,kCAAkC,EACvC,2BAA2B,EAC3B,qCAAqC,GACtC,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,EAC7B,qBAAqB,EACrB,mCAAmC,GACpC,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,GACR,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACrC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAEtE,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC"}
|