@happyvertical/smrt-core 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -8
- package/agents/change-feed.md +3 -2
- package/agents/generators.md +34 -0
- package/agents/revision-guard.md +66 -0
- package/dist/browser.js +2 -1
- package/dist/cascade.d.ts +5 -9
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +65 -30
- package/dist/cascade.js.map +1 -1
- package/dist/change-feed.d.ts +60 -1
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +464 -27
- package/dist/change-feed.js.map +1 -1
- package/dist/change-signals.d.ts.map +1 -1
- package/dist/change-signals.js +13 -11
- package/dist/change-signals.js.map +1 -1
- package/dist/embedded-write-queue.d.ts +8 -0
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +12 -2
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/cli.d.ts.map +1 -1
- package/dist/generators/cli.js +10 -18
- package/dist/generators/cli.js.map +1 -1
- package/dist/generators/custom-action.d.ts +216 -0
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +256 -1
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/generators/index.d.ts +2 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +3 -2
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +14 -39
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/preflight-route.d.ts +151 -0
- package/dist/generators/preflight-route.d.ts.map +1 -0
- package/dist/generators/preflight-route.js +194 -0
- package/dist/generators/preflight-route.js.map +1 -0
- package/dist/generators/rest.d.ts +12 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +14 -15
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/tool-schema.d.ts.map +1 -1
- package/dist/generators/tool-schema.js +2 -8
- package/dist/generators/tool-schema.js.map +1 -1
- package/dist/generators.js +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +283 -35
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +6 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +8 -2
- package/dist/object.d.ts +31 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +57 -9
- package/dist/object.js.map +1 -1
- package/dist/registry/framework-base-classes.d.ts +10 -0
- package/dist/registry/framework-base-classes.d.ts.map +1 -0
- package/dist/registry/framework-base-classes.js +92 -0
- package/dist/registry/framework-base-classes.js.map +1 -0
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +2 -0
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +27 -6
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -1
- package/dist/registry.js.map +1 -1
- package/dist/revision-guard.d.ts +84 -0
- package/dist/revision-guard.d.ts.map +1 -0
- package/dist/revision-guard.js +120 -0
- package/dist/revision-guard.js.map +1 -0
- package/dist/scanner/manifest-generator.d.ts +46 -16
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +198 -45
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/smrt-knowledge.json +18 -9
- package/dist/system/bootstrap.d.ts.map +1 -1
- package/dist/system/bootstrap.js +2 -1
- package/dist/system/bootstrap.js.map +1 -1
- package/dist/system/schema.d.ts +75 -2
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +326 -4
- package/dist/system/schema.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +48 -23
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +33 -7
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +88 -17
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +2 -0
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +28 -5
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,6 +1,261 @@
|
|
|
1
1
|
import { convertTypeToJsonSchema } from "../tools/tool-generator.js";
|
|
2
2
|
//#region src/generators/custom-action.ts
|
|
3
3
|
/**
|
|
4
|
+
* Public method names declared directly on `SmrtObject`/`SmrtClass`
|
|
5
|
+
* (`src/object.ts`, `src/class.ts`) that form the constructor → `initialize()`
|
|
6
|
+
* → `save()`/`delete()`/`loadFromId()` lifecycle and its immediate supporting
|
|
7
|
+
* mechanism: identity/persistence bookkeeping, transaction binding, and
|
|
8
|
+
* serialization. `save()` is what generated `create`/`update` call;
|
|
9
|
+
* `initialize()` is what `get`/`list` hydration calls; `loadFromId()`/
|
|
10
|
+
* `loadFromSlug()` are what `get()` calls; `toJSON()`/`toPublicJSON()` are
|
|
11
|
+
* what every read serializes through. None of these are a subclass-specific
|
|
12
|
+
* operation, so none is exposed as a generated CLI/MCP custom action --
|
|
13
|
+
* even when a subclass declares its own override (e.g. `User.save()` at
|
|
14
|
+
* `packages/users/src/models/User.ts`). An override is still the same
|
|
15
|
+
* lifecycle operation, not a new one (#2638). `delete` itself is a CRUD verb
|
|
16
|
+
* `CLIGenerator`/`MCPGenerator` already special-case, so it is not repeated
|
|
17
|
+
* here.
|
|
18
|
+
*
|
|
19
|
+
* Scope is deliberately narrower than "every public method on
|
|
20
|
+
* SmrtObject/SmrtClass/SmrtCollection":
|
|
21
|
+
*
|
|
22
|
+
* - AI operations `is()`/`do()`/`describe()` are declared on `SmrtObject`
|
|
23
|
+
* but are explicitly designed to be overridden with domain-specific
|
|
24
|
+
* behavior and exposed as a distinct action -- confirmed by existing,
|
|
25
|
+
* intentional coverage (`generators/cli-commands.spec.ts`'s
|
|
26
|
+
* `describe()` custom-action fixture,
|
|
27
|
+
* `vite-plugin/generated-client-integration.test.ts`'s `ArtCollection.
|
|
28
|
+
* describe(tone)` with its own declared API route). Excluding them here
|
|
29
|
+
* would regress real, working behavior.
|
|
30
|
+
* - Relationship loading (`loadRelated`/`loadRelatedMany`/`getRelated`/
|
|
31
|
+
* `isRelatedLoaded`), memory (`remember`/`recall`/`recallAll`/`forget`/
|
|
32
|
+
* `forgetScope`), embeddings (`generateEmbeddings`/`getEmbedding`/
|
|
33
|
+
* `hasStaleEmbeddings`/`clearEmbeddings`), and AI-usage introspection
|
|
34
|
+
* (`getAiUsageSnapshot`/`resetAiUsage`/`listAiUsage`/`summarizeAiUsage`)
|
|
35
|
+
* are generic capabilities a class may legitimately want to trigger or
|
|
36
|
+
* report on as a distinct action (e.g. "regenerate this record's
|
|
37
|
+
* embeddings"), not "the mechanism behind CRUD" the way `save`/
|
|
38
|
+
* `initialize` are. Nothing in the measured #2638 residual touches them,
|
|
39
|
+
* so blanket-excluding them is a separate, unevidenced call this fix does
|
|
40
|
+
* not make.
|
|
41
|
+
* - `SmrtCollection`'s own surface (`count`, `facets`, `query`, `findOne`,
|
|
42
|
+
* `generateMissingEmbeddings`, ...) is excluded entirely for the same
|
|
43
|
+
* reason: the measured residual is 100% item-class overrides (`User`,
|
|
44
|
+
* `Invoice`, `Payment`, ...), never a collection-class override, and
|
|
45
|
+
* `count`/`facets` read as genuinely distinct query capabilities in
|
|
46
|
+
* existing fixtures elsewhere in the repo (e.g.
|
|
47
|
+
* `packages/content/src/server/content-list-actions.test.ts`). Extending
|
|
48
|
+
* this rule to `SmrtCollection` needs its own review, not a ride-along
|
|
49
|
+
* here.
|
|
50
|
+
*
|
|
51
|
+
* This can only be a METHOD-NAME list, not a reuse of the existing
|
|
52
|
+
* class-name sets (`FRAMEWORK_METHOD_BASE_NAMES` in
|
|
53
|
+
* `scanner/manifest-generator.ts`, the registry's own by-name skip in
|
|
54
|
+
* `registry/inheritance-resolver.ts`, or the broader 8-class
|
|
55
|
+
* `FRAMEWORK_BASE_CLASSES` those two modules also cross-reference for the
|
|
56
|
+
* unrelated schema-field-merge question): those sets answer "is this
|
|
57
|
+
* ancestor one of the excluded classes", which only ever decides whether to
|
|
58
|
+
* MERGE an ancestor's methods onto a subclass that does not declare them.
|
|
59
|
+
* A locally declared override -- the #2638 case -- IS the subclass's own
|
|
60
|
+
* method; there is no ancestor lookup to skip, because the method that
|
|
61
|
+
* needs excluding was never inherited in the first place. The same
|
|
62
|
+
* plumbing-vs-operation judgment #2624 made at the class level has to be
|
|
63
|
+
* re-expressed as a method-name list to reach the override case too, which
|
|
64
|
+
* is why this is a fifth name list rather than a reuse of one of the four.
|
|
65
|
+
*
|
|
66
|
+
* A static, hand-maintained list (not runtime introspection of the actual
|
|
67
|
+
* `SmrtObject`/`SmrtClass` prototypes) matches the existing pattern for all
|
|
68
|
+
* four sibling lists above, and keeps this transport-neutral module usable
|
|
69
|
+
* from a pure manifest/AST build path (`vite-plugin/sveltekit-generator.ts`)
|
|
70
|
+
* that has no live class registry to introspect. Re-derive this list from
|
|
71
|
+
* those two files' own public method signatures if they change.
|
|
72
|
+
*/
|
|
73
|
+
var FRAMEWORK_LIFECYCLE_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
74
|
+
"destroy",
|
|
75
|
+
"withDatabase",
|
|
76
|
+
"initialize",
|
|
77
|
+
"loadDataFromDb",
|
|
78
|
+
"getFields",
|
|
79
|
+
"toJSON",
|
|
80
|
+
"toPlainObject",
|
|
81
|
+
"toPublicJSON",
|
|
82
|
+
"getId",
|
|
83
|
+
"getSlug",
|
|
84
|
+
"getSavedId",
|
|
85
|
+
"isSaved",
|
|
86
|
+
"save",
|
|
87
|
+
"claimRevision",
|
|
88
|
+
"classifyConstraintError",
|
|
89
|
+
"loadFromId",
|
|
90
|
+
"loadFromSlug",
|
|
91
|
+
"markAsPersisted",
|
|
92
|
+
"requireInsertOnSave",
|
|
93
|
+
"withTransaction"
|
|
94
|
+
]);
|
|
95
|
+
/**
|
|
96
|
+
* True when `methodName` is one of the universal `SmrtObject`/`SmrtClass`
|
|
97
|
+
* lifecycle methods above — the mechanism behind generated CRUD, never a
|
|
98
|
+
* subclass-specific custom action, even when the subclass declares its own
|
|
99
|
+
* override.
|
|
100
|
+
*/
|
|
101
|
+
function isFrameworkLifecycleMethod(methodName) {
|
|
102
|
+
return FRAMEWORK_LIFECYCLE_METHOD_NAMES.has(methodName);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the effective set of custom (non-CRUD) command/tool names exposed
|
|
106
|
+
* for an object, given its transport config's `include`/`exclude` and its
|
|
107
|
+
* method map: every public method, minus CRUD verbs, minus framework
|
|
108
|
+
* lifecycle methods, restricted to `include` when present and always minus
|
|
109
|
+
* `exclude`. Reused by three callers, so they agree with each other and
|
|
110
|
+
* with what `listCommands()` advertises:
|
|
111
|
+
*
|
|
112
|
+
* - `CLIGenerator.listCommands()` (over the live `ObjectRegistry`).
|
|
113
|
+
* - `findCliApiCoherenceViolations`'s bare-`cli: true`/`cli: {}` branch
|
|
114
|
+
* (over the static manifest, no explicit `include`) -- see
|
|
115
|
+
* `resolveCliActionSet` in `vite-plugin/sveltekit-generator.ts`.
|
|
116
|
+
* - `generateCLIModule()` in `vite-plugin/index.ts`, which generates the
|
|
117
|
+
* `smrt:cli` virtual module's static command metadata. It layers one
|
|
118
|
+
* additional filter on top of this function's result: a leading `_` on
|
|
119
|
+
* the method name, because the manifest's `isPublic` is unreliable there
|
|
120
|
+
* (see that call site's own comment) and this function has no other
|
|
121
|
+
* signal to exclude an internal-by-convention method.
|
|
122
|
+
*
|
|
123
|
+
* NOT the one universal resolution, and deliberately not reused by every
|
|
124
|
+
* caller that resolves a CLI command set:
|
|
125
|
+
*
|
|
126
|
+
* - `CLIGenerator.assertCommandExposed()` does not call this for its custom-
|
|
127
|
+
* method branch — it checks `isFrameworkLifecycleMethod()` directly plus
|
|
128
|
+
* its own inline public/include/exclude logic, so it can give a distinct
|
|
129
|
+
* error message per failure reason (unknown vs. not public vs. not
|
|
130
|
+
* enabled vs. lifecycle method) rather than a single boolean membership
|
|
131
|
+
* test.
|
|
132
|
+
* - `findCliApiCoherenceViolations`'s EXPLICIT-`cli.include` branch
|
|
133
|
+
* deliberately bypasses this function too: an `include` entry naming a
|
|
134
|
+
* typo, a getter, or a private/protected method must still surface as
|
|
135
|
+
* "unreachable" at build time (the pre-#2638 behavior), and this function
|
|
136
|
+
* can only ever return names that exist in the manifest's `methods` map —
|
|
137
|
+
* it would silently drop such an entry instead of flagging it. See
|
|
138
|
+
* `resolveCliActionSet` in `vite-plugin/sveltekit-generator.ts` for the
|
|
139
|
+
* full rationale; do not "simplify" that branch onto this function.
|
|
140
|
+
*
|
|
141
|
+
* `crudActionNames` stays a parameter because the callers do not agree on one
|
|
142
|
+
* list. `CLIGenerator` now passes {@link CRUD_OPERATIONS}, which #2646 moved
|
|
143
|
+
* into this module — so for that caller the argument is a round trip and could
|
|
144
|
+
* be defaulted. The SvelteKit generator still passes its own
|
|
145
|
+
* `STANDARD_API_ACTIONS` and `vite-plugin/index.ts` an inline literal, so the
|
|
146
|
+
* parameter cannot be removed until those copies are retired (#2665).
|
|
147
|
+
*/
|
|
148
|
+
function resolveCustomActionNames(methods, config, crudActionNames) {
|
|
149
|
+
const included = config?.include;
|
|
150
|
+
const excluded = config?.exclude ?? [];
|
|
151
|
+
const result = /* @__PURE__ */ new Set();
|
|
152
|
+
for (const [name, method] of methods) {
|
|
153
|
+
if (crudActionNames.includes(name)) continue;
|
|
154
|
+
if (isFrameworkLifecycleMethod(name)) continue;
|
|
155
|
+
if (!method.isPublic) continue;
|
|
156
|
+
if (excluded.includes(name)) continue;
|
|
157
|
+
if (included !== void 0 && !included.includes(name)) continue;
|
|
158
|
+
result.add(name);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The CRUD verbs a generated surface emits directly. A method whose name
|
|
164
|
+
* collides with one of these may not be exposed as a custom action under that
|
|
165
|
+
* name: the generated operation already claims it, so a second command/tool
|
|
166
|
+
* would land on a name that is taken.
|
|
167
|
+
*
|
|
168
|
+
* This is a NAMESPACE rule, independent of where the method came from — a
|
|
169
|
+
* class's own `list()` collides exactly as a merged ancestor's does (#2646).
|
|
170
|
+
*
|
|
171
|
+
* What a collision means differs by EMITTER, so consult the one you are
|
|
172
|
+
* changing rather than assuming a single rule. The reservation lives at each
|
|
173
|
+
* emission site, not here, and several emitters still keep their own inline
|
|
174
|
+
* verb array — find them with:
|
|
175
|
+
*
|
|
176
|
+
* grep -rn "'update', 'delete'" packages --include='*.ts' | grep -v include:
|
|
177
|
+
*
|
|
178
|
+
* (the `include:` filter drops decorator config, which names the same verbs)
|
|
179
|
+
*
|
|
180
|
+
* The sites this rule was audited against (#2646), NOT an exhaustive
|
|
181
|
+
* inventory:
|
|
182
|
+
*
|
|
183
|
+
* - `generators/mcp.ts` — unconditional, case-folded. `executeAction` switches
|
|
184
|
+
* on the verb parsed out of the tool id, so `${object}_list` runs the
|
|
185
|
+
* built-in list whichever branch emitted it: the class's method could never
|
|
186
|
+
* run, and emitting one would hand the caller an operation `include` never
|
|
187
|
+
* named.
|
|
188
|
+
* - `generators/cli.ts` (`CLIGenerator`) — unconditional, exact.
|
|
189
|
+
* `assertCommandExposed` returns inside its `isCrud` branch, so a CRUD-named
|
|
190
|
+
* action never reaches custom-method resolution, and `listCommands` skips
|
|
191
|
+
* those names outright.
|
|
192
|
+
* - `packages/cli/src/cli-generator.ts`, behind the shipped `smrt` object
|
|
193
|
+
* commands — reserves only where the CRUD command is actually emitted, and
|
|
194
|
+
* reserves the command NAMES AND THEIR ALIASES (`ls`, `show`, `new`, `edit`,
|
|
195
|
+
* `rm`), because lookup matches aliases too (#2648). The set is derived from
|
|
196
|
+
* the commands actually pushed, so it cannot drift from those aliases. Each
|
|
197
|
+
* command carries its own handler invoking the class's method, so with
|
|
198
|
+
* `cli: { include: ['list', 'get'] }` neither a public `create()` nor an
|
|
199
|
+
* `edit()` collides with anything and both stay reachable.
|
|
200
|
+
* - `vite-plugin/sveltekit-generator.ts` — unconditional, exact, from its own
|
|
201
|
+
* `STANDARD_API_ACTIONS` copy.
|
|
202
|
+
* - `vite-plugin/web-collections.ts` + `tool-schema.ts` — case-folded (ids are
|
|
203
|
+
* lowercased whole like MCP's), CONDITIONAL, and scoped PER COLLECTION.
|
|
204
|
+
* Applied in both of `selectWebMcpToolEntries`' loops and at the shared
|
|
205
|
+
* `buildWebToolDescriptorsForHost` choke point, which the legacy
|
|
206
|
+
* per-collection descriptor export also passes through (#2648).
|
|
207
|
+
*
|
|
208
|
+
* Conditional, unlike `generators/mcp.ts`: MCP dispatch parses the verb out
|
|
209
|
+
* of the tool id, so `${obj}_list` can only ever run the built-in list, but a
|
|
210
|
+
* WebMCP descriptor carries its own `route` from `resolveApiActionRouteConfig`
|
|
211
|
+
* — with `api: { include: ['List'] }`, `product_list` dispatches to
|
|
212
|
+
* `/products/List` and is the only tool for that id, so reserving it would
|
|
213
|
+
* make a custom-action-only model undiscoverable.
|
|
214
|
+
*
|
|
215
|
+
* Per collection, not per host: the id prefix is the OWNER's `className`, so
|
|
216
|
+
* every host mapping to one collection shares the namespace. A host-local
|
|
217
|
+
* check lets a model that excludes `list` but declares `List()` claim
|
|
218
|
+
* `product_list`, after which a sibling's real `list` is dropped by the
|
|
219
|
+
* fold-dedupe. The emitted-verb set is the union over the collection's hosts.
|
|
220
|
+
*
|
|
221
|
+
* `resolveApiActionSet` stays exact-match: REST routes keep declared casing,
|
|
222
|
+
* so `/products/List` is genuinely distinct from `/products`.
|
|
223
|
+
*
|
|
224
|
+
* Still carrying their own verb copies, tracked on #2665:
|
|
225
|
+
* `vite-plugin/api-client-entries.ts`, `vite-plugin/index.ts` and
|
|
226
|
+
* `vite-plugin/templates/default-ui.ts`.
|
|
227
|
+
*
|
|
228
|
+
* Read the list through {@link isCrudOperation} or {@link isCrudToolAction}
|
|
229
|
+
* rather than re-declaring it; the two differ only in case folding, because the
|
|
230
|
+
* transports namespace differently (see below).
|
|
231
|
+
*/
|
|
232
|
+
var CRUD_OPERATIONS = [
|
|
233
|
+
"list",
|
|
234
|
+
"get",
|
|
235
|
+
"create",
|
|
236
|
+
"update",
|
|
237
|
+
"delete"
|
|
238
|
+
];
|
|
239
|
+
/**
|
|
240
|
+
* Exact-match test for a case-SENSITIVE surface. The CLI keeps a method's
|
|
241
|
+
* declared casing in its command name (`${object}:${methodName}`) and resolves
|
|
242
|
+
* an action by exact match, so `foo:List` is a distinct, callable custom
|
|
243
|
+
* command that must not be folded into `foo:list`.
|
|
244
|
+
*/
|
|
245
|
+
function isCrudOperation(name) {
|
|
246
|
+
return CRUD_OPERATIONS.includes(name);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Case-FOLDED test for a lowercase tool namespace. MCP tool identifiers are
|
|
250
|
+
* lowercased whole (`` `${object}_${methodName}`.toLowerCase() ``) for a stable
|
|
251
|
+
* protocol vocabulary, so a method named `List` lands on the identifier
|
|
252
|
+
* `object_list` that the generated CRUD tool already owns. The namespace key is
|
|
253
|
+
* the lowercased name, so the collision test has to be too (#2646).
|
|
254
|
+
*/
|
|
255
|
+
function isCrudToolAction(name) {
|
|
256
|
+
return isCrudOperation(name.toLowerCase());
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
4
259
|
* Return the transport field for a method parameter. Flat tool and CLI
|
|
5
260
|
* transports reserve `id` for receiver parsing even when a collection action
|
|
6
261
|
* rejects it, so every action parameter named `id` is exposed as `actionId`.
|
|
@@ -140,6 +395,6 @@ function isRecord(value) {
|
|
|
140
395
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
141
396
|
}
|
|
142
397
|
//#endregion
|
|
143
|
-
export { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata };
|
|
398
|
+
export { CRUD_OPERATIONS, FRAMEWORK_LIFECYCLE_METHOD_NAMES, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, customActionParameterInputName, isCrudOperation, isCrudToolAction, isFrameworkLifecycleMethod, normalizeCustomActionFailure, resolveCustomActionMetadata, resolveCustomActionNames };
|
|
144
399
|
|
|
145
400
|
//# sourceMappingURL=custom-action.js.map
|
|
@@ -1 +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 { ToolEffect } from '../registry/types.js';\nimport type { MethodDefinition } from '../scanner/types.js';\nimport { convertTypeToJsonSchema } from '../tools/tool-generator.js';\n\nexport type CustomActionScope = 'item' | 'collection';\nexport type { ToolEffect } from '../registry/types.js';\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 /** Browser/agent-visible effect. Omitted declarations fail closed. */\n effect?: ToolEffect;\n /** Whether repeating the action with the same arguments is safe. */\n idempotent?: boolean;\n /** Whether the opaque action may interact outside the SMRT application. */\n openWorld?: boolean;\n}\n\n/** Fully resolved metadata returned by {@link resolveCustomActionMetadata}. */\nexport type ResolvedCustomActionMetadata = CustomActionMetadata &\n Required<Pick<CustomActionMetadata, 'effect' | 'idempotent' | 'openWorld'>>;\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): ResolvedCustomActionMetadata {\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 const configured = readConfiguredToolMetadata(\n options.apiConfig,\n options.actionName,\n );\n const effect = configured.effect ?? 'destructive';\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 effect,\n // Per-field fail-closed default (#2587, CapabilityDeclaration in\n // @happyvertical/smrt-types): an omitted `idempotent` resolves to\n // `false` regardless of the declared `effect` — a declared 'read'\n // action is not guaranteed idempotent (e.g. a dequeue-shaped read that\n // advances state), so a caller who wants the idempotent hint must\n // declare it explicitly.\n idempotent: configured.idempotent ?? false,\n openWorld: configured.openWorld ?? 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 readConfiguredToolMetadata(\n apiConfig: unknown,\n actionName: string,\n): {\n effect?: ToolEffect;\n idempotent?: boolean;\n openWorld?: boolean;\n} {\n if (!isRecord(apiConfig) || !isRecord(apiConfig.routes)) return {};\n const route = apiConfig.routes[actionName];\n if (!isRecord(route)) return {};\n const effect =\n route.effect === 'read' ||\n route.effect === 'write' ||\n route.effect === 'destructive'\n ? route.effect\n : undefined;\n if (\n effect === 'read' &&\n (route.method === 'PUT' ||\n route.method === 'PATCH' ||\n route.method === 'DELETE')\n ) {\n throw new Error(\n `Custom action ${actionName} cannot declare a read effect for a ${route.method} route`,\n );\n }\n return {\n ...(effect ? { effect } : {}),\n ...(typeof route.idempotent === 'boolean'\n ? { idempotent: route.idempotent }\n : {}),\n ...(typeof route.openWorld === 'boolean'\n ? { openWorld: route.openWorld }\n : {}),\n };\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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"}
|
|
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 { ToolEffect } from '../registry/types.js';\nimport type { MethodDefinition } from '../scanner/types.js';\nimport { convertTypeToJsonSchema } from '../tools/tool-generator.js';\n\nexport type CustomActionScope = 'item' | 'collection';\nexport type { ToolEffect } from '../registry/types.js';\n\n/**\n * Public method names declared directly on `SmrtObject`/`SmrtClass`\n * (`src/object.ts`, `src/class.ts`) that form the constructor → `initialize()`\n * → `save()`/`delete()`/`loadFromId()` lifecycle and its immediate supporting\n * mechanism: identity/persistence bookkeeping, transaction binding, and\n * serialization. `save()` is what generated `create`/`update` call;\n * `initialize()` is what `get`/`list` hydration calls; `loadFromId()`/\n * `loadFromSlug()` are what `get()` calls; `toJSON()`/`toPublicJSON()` are\n * what every read serializes through. None of these are a subclass-specific\n * operation, so none is exposed as a generated CLI/MCP custom action --\n * even when a subclass declares its own override (e.g. `User.save()` at\n * `packages/users/src/models/User.ts`). An override is still the same\n * lifecycle operation, not a new one (#2638). `delete` itself is a CRUD verb\n * `CLIGenerator`/`MCPGenerator` already special-case, so it is not repeated\n * here.\n *\n * Scope is deliberately narrower than \"every public method on\n * SmrtObject/SmrtClass/SmrtCollection\":\n *\n * - AI operations `is()`/`do()`/`describe()` are declared on `SmrtObject`\n * but are explicitly designed to be overridden with domain-specific\n * behavior and exposed as a distinct action -- confirmed by existing,\n * intentional coverage (`generators/cli-commands.spec.ts`'s\n * `describe()` custom-action fixture,\n * `vite-plugin/generated-client-integration.test.ts`'s `ArtCollection.\n * describe(tone)` with its own declared API route). Excluding them here\n * would regress real, working behavior.\n * - Relationship loading (`loadRelated`/`loadRelatedMany`/`getRelated`/\n * `isRelatedLoaded`), memory (`remember`/`recall`/`recallAll`/`forget`/\n * `forgetScope`), embeddings (`generateEmbeddings`/`getEmbedding`/\n * `hasStaleEmbeddings`/`clearEmbeddings`), and AI-usage introspection\n * (`getAiUsageSnapshot`/`resetAiUsage`/`listAiUsage`/`summarizeAiUsage`)\n * are generic capabilities a class may legitimately want to trigger or\n * report on as a distinct action (e.g. \"regenerate this record's\n * embeddings\"), not \"the mechanism behind CRUD\" the way `save`/\n * `initialize` are. Nothing in the measured #2638 residual touches them,\n * so blanket-excluding them is a separate, unevidenced call this fix does\n * not make.\n * - `SmrtCollection`'s own surface (`count`, `facets`, `query`, `findOne`,\n * `generateMissingEmbeddings`, ...) is excluded entirely for the same\n * reason: the measured residual is 100% item-class overrides (`User`,\n * `Invoice`, `Payment`, ...), never a collection-class override, and\n * `count`/`facets` read as genuinely distinct query capabilities in\n * existing fixtures elsewhere in the repo (e.g.\n * `packages/content/src/server/content-list-actions.test.ts`). Extending\n * this rule to `SmrtCollection` needs its own review, not a ride-along\n * here.\n *\n * This can only be a METHOD-NAME list, not a reuse of the existing\n * class-name sets (`FRAMEWORK_METHOD_BASE_NAMES` in\n * `scanner/manifest-generator.ts`, the registry's own by-name skip in\n * `registry/inheritance-resolver.ts`, or the broader 8-class\n * `FRAMEWORK_BASE_CLASSES` those two modules also cross-reference for the\n * unrelated schema-field-merge question): those sets answer \"is this\n * ancestor one of the excluded classes\", which only ever decides whether to\n * MERGE an ancestor's methods onto a subclass that does not declare them.\n * A locally declared override -- the #2638 case -- IS the subclass's own\n * method; there is no ancestor lookup to skip, because the method that\n * needs excluding was never inherited in the first place. The same\n * plumbing-vs-operation judgment #2624 made at the class level has to be\n * re-expressed as a method-name list to reach the override case too, which\n * is why this is a fifth name list rather than a reuse of one of the four.\n *\n * A static, hand-maintained list (not runtime introspection of the actual\n * `SmrtObject`/`SmrtClass` prototypes) matches the existing pattern for all\n * four sibling lists above, and keeps this transport-neutral module usable\n * from a pure manifest/AST build path (`vite-plugin/sveltekit-generator.ts`)\n * that has no live class registry to introspect. Re-derive this list from\n * those two files' own public method signatures if they change.\n */\nexport const FRAMEWORK_LIFECYCLE_METHOD_NAMES: ReadonlySet<string> = new Set([\n // SmrtClass (src/class.ts) — resource/transaction plumbing.\n 'destroy',\n 'withDatabase',\n // SmrtObject (src/object.ts) — identity/persistence lifecycle mechanism.\n 'initialize',\n 'loadDataFromDb',\n 'getFields',\n 'toJSON',\n 'toPlainObject',\n 'toPublicJSON',\n 'getId',\n 'getSlug',\n 'getSavedId',\n 'isSaved',\n 'save',\n 'claimRevision',\n 'classifyConstraintError',\n 'loadFromId',\n 'loadFromSlug',\n 'markAsPersisted',\n 'requireInsertOnSave',\n 'withTransaction',\n]);\n\n/**\n * True when `methodName` is one of the universal `SmrtObject`/`SmrtClass`\n * lifecycle methods above — the mechanism behind generated CRUD, never a\n * subclass-specific custom action, even when the subclass declares its own\n * override.\n */\nexport function isFrameworkLifecycleMethod(methodName: string): boolean {\n return FRAMEWORK_LIFECYCLE_METHOD_NAMES.has(methodName);\n}\n\n/** Minimal shape `resolveCustomActionNames` needs from a method entry. */\nexport interface ResolvableMethod {\n isPublic: boolean;\n}\n\n/**\n * Resolve the effective set of custom (non-CRUD) command/tool names exposed\n * for an object, given its transport config's `include`/`exclude` and its\n * method map: every public method, minus CRUD verbs, minus framework\n * lifecycle methods, restricted to `include` when present and always minus\n * `exclude`. Reused by three callers, so they agree with each other and\n * with what `listCommands()` advertises:\n *\n * - `CLIGenerator.listCommands()` (over the live `ObjectRegistry`).\n * - `findCliApiCoherenceViolations`'s bare-`cli: true`/`cli: {}` branch\n * (over the static manifest, no explicit `include`) -- see\n * `resolveCliActionSet` in `vite-plugin/sveltekit-generator.ts`.\n * - `generateCLIModule()` in `vite-plugin/index.ts`, which generates the\n * `smrt:cli` virtual module's static command metadata. It layers one\n * additional filter on top of this function's result: a leading `_` on\n * the method name, because the manifest's `isPublic` is unreliable there\n * (see that call site's own comment) and this function has no other\n * signal to exclude an internal-by-convention method.\n *\n * NOT the one universal resolution, and deliberately not reused by every\n * caller that resolves a CLI command set:\n *\n * - `CLIGenerator.assertCommandExposed()` does not call this for its custom-\n * method branch — it checks `isFrameworkLifecycleMethod()` directly plus\n * its own inline public/include/exclude logic, so it can give a distinct\n * error message per failure reason (unknown vs. not public vs. not\n * enabled vs. lifecycle method) rather than a single boolean membership\n * test.\n * - `findCliApiCoherenceViolations`'s EXPLICIT-`cli.include` branch\n * deliberately bypasses this function too: an `include` entry naming a\n * typo, a getter, or a private/protected method must still surface as\n * \"unreachable\" at build time (the pre-#2638 behavior), and this function\n * can only ever return names that exist in the manifest's `methods` map —\n * it would silently drop such an entry instead of flagging it. See\n * `resolveCliActionSet` in `vite-plugin/sveltekit-generator.ts` for the\n * full rationale; do not \"simplify\" that branch onto this function.\n *\n * `crudActionNames` stays a parameter because the callers do not agree on one\n * list. `CLIGenerator` now passes {@link CRUD_OPERATIONS}, which #2646 moved\n * into this module — so for that caller the argument is a round trip and could\n * be defaulted. The SvelteKit generator still passes its own\n * `STANDARD_API_ACTIONS` and `vite-plugin/index.ts` an inline literal, so the\n * parameter cannot be removed until those copies are retired (#2665).\n */\nexport function resolveCustomActionNames(\n methods: Iterable<[string, ResolvableMethod]>,\n config: { include?: string[]; exclude?: string[] } | undefined,\n crudActionNames: readonly string[],\n): Set<string> {\n const included = config?.include;\n const excluded = config?.exclude ?? [];\n const result = new Set<string>();\n for (const [name, method] of methods) {\n if (crudActionNames.includes(name)) continue;\n if (isFrameworkLifecycleMethod(name)) continue;\n if (!method.isPublic) continue;\n if (excluded.includes(name)) continue;\n if (included !== undefined && !included.includes(name)) continue;\n result.add(name);\n }\n return result;\n}\n\n/**\n * The CRUD verbs a generated surface emits directly. A method whose name\n * collides with one of these may not be exposed as a custom action under that\n * name: the generated operation already claims it, so a second command/tool\n * would land on a name that is taken.\n *\n * This is a NAMESPACE rule, independent of where the method came from — a\n * class's own `list()` collides exactly as a merged ancestor's does (#2646).\n *\n * What a collision means differs by EMITTER, so consult the one you are\n * changing rather than assuming a single rule. The reservation lives at each\n * emission site, not here, and several emitters still keep their own inline\n * verb array — find them with:\n *\n * grep -rn \"'update', 'delete'\" packages --include='*.ts' | grep -v include:\n *\n * (the `include:` filter drops decorator config, which names the same verbs)\n *\n * The sites this rule was audited against (#2646), NOT an exhaustive\n * inventory:\n *\n * - `generators/mcp.ts` — unconditional, case-folded. `executeAction` switches\n * on the verb parsed out of the tool id, so `${object}_list` runs the\n * built-in list whichever branch emitted it: the class's method could never\n * run, and emitting one would hand the caller an operation `include` never\n * named.\n * - `generators/cli.ts` (`CLIGenerator`) — unconditional, exact.\n * `assertCommandExposed` returns inside its `isCrud` branch, so a CRUD-named\n * action never reaches custom-method resolution, and `listCommands` skips\n * those names outright.\n * - `packages/cli/src/cli-generator.ts`, behind the shipped `smrt` object\n * commands — reserves only where the CRUD command is actually emitted, and\n * reserves the command NAMES AND THEIR ALIASES (`ls`, `show`, `new`, `edit`,\n * `rm`), because lookup matches aliases too (#2648). The set is derived from\n * the commands actually pushed, so it cannot drift from those aliases. Each\n * command carries its own handler invoking the class's method, so with\n * `cli: { include: ['list', 'get'] }` neither a public `create()` nor an\n * `edit()` collides with anything and both stay reachable.\n * - `vite-plugin/sveltekit-generator.ts` — unconditional, exact, from its own\n * `STANDARD_API_ACTIONS` copy.\n * - `vite-plugin/web-collections.ts` + `tool-schema.ts` — case-folded (ids are\n * lowercased whole like MCP's), CONDITIONAL, and scoped PER COLLECTION.\n * Applied in both of `selectWebMcpToolEntries`' loops and at the shared\n * `buildWebToolDescriptorsForHost` choke point, which the legacy\n * per-collection descriptor export also passes through (#2648).\n *\n * Conditional, unlike `generators/mcp.ts`: MCP dispatch parses the verb out\n * of the tool id, so `${obj}_list` can only ever run the built-in list, but a\n * WebMCP descriptor carries its own `route` from `resolveApiActionRouteConfig`\n * — with `api: { include: ['List'] }`, `product_list` dispatches to\n * `/products/List` and is the only tool for that id, so reserving it would\n * make a custom-action-only model undiscoverable.\n *\n * Per collection, not per host: the id prefix is the OWNER's `className`, so\n * every host mapping to one collection shares the namespace. A host-local\n * check lets a model that excludes `list` but declares `List()` claim\n * `product_list`, after which a sibling's real `list` is dropped by the\n * fold-dedupe. The emitted-verb set is the union over the collection's hosts.\n *\n * `resolveApiActionSet` stays exact-match: REST routes keep declared casing,\n * so `/products/List` is genuinely distinct from `/products`.\n *\n * Still carrying their own verb copies, tracked on #2665:\n * `vite-plugin/api-client-entries.ts`, `vite-plugin/index.ts` and\n * `vite-plugin/templates/default-ui.ts`.\n *\n * Read the list through {@link isCrudOperation} or {@link isCrudToolAction}\n * rather than re-declaring it; the two differ only in case folding, because the\n * transports namespace differently (see below).\n */\nexport const CRUD_OPERATIONS = [\n 'list',\n 'get',\n 'create',\n 'update',\n 'delete',\n] as const;\n\n/**\n * Exact-match test for a case-SENSITIVE surface. The CLI keeps a method's\n * declared casing in its command name (`${object}:${methodName}`) and resolves\n * an action by exact match, so `foo:List` is a distinct, callable custom\n * command that must not be folded into `foo:list`.\n */\nexport function isCrudOperation(name: string): boolean {\n return (CRUD_OPERATIONS as readonly string[]).includes(name);\n}\n\n/**\n * Case-FOLDED test for a lowercase tool namespace. MCP tool identifiers are\n * lowercased whole (`` `${object}_${methodName}`.toLowerCase() ``) for a stable\n * protocol vocabulary, so a method named `List` lands on the identifier\n * `object_list` that the generated CRUD tool already owns. The namespace key is\n * the lowercased name, so the collision test has to be too (#2646).\n */\nexport function isCrudToolAction(name: string): boolean {\n return isCrudOperation(name.toLowerCase());\n}\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 /** Browser/agent-visible effect. Omitted declarations fail closed. */\n effect?: ToolEffect;\n /** Whether repeating the action with the same arguments is safe. */\n idempotent?: boolean;\n /** Whether the opaque action may interact outside the SMRT application. */\n openWorld?: boolean;\n}\n\n/** Fully resolved metadata returned by {@link resolveCustomActionMetadata}. */\nexport type ResolvedCustomActionMetadata = CustomActionMetadata &\n Required<Pick<CustomActionMetadata, 'effect' | 'idempotent' | 'openWorld'>>;\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): ResolvedCustomActionMetadata {\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 const configured = readConfiguredToolMetadata(\n options.apiConfig,\n options.actionName,\n );\n const effect = configured.effect ?? 'destructive';\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 effect,\n // Per-field fail-closed default (#2587, CapabilityDeclaration in\n // @happyvertical/smrt-types): an omitted `idempotent` resolves to\n // `false` regardless of the declared `effect` — a declared 'read'\n // action is not guaranteed idempotent (e.g. a dequeue-shaped read that\n // advances state), so a caller who wants the idempotent hint must\n // declare it explicitly.\n idempotent: configured.idempotent ?? false,\n openWorld: configured.openWorld ?? 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 readConfiguredToolMetadata(\n apiConfig: unknown,\n actionName: string,\n): {\n effect?: ToolEffect;\n idempotent?: boolean;\n openWorld?: boolean;\n} {\n if (!isRecord(apiConfig) || !isRecord(apiConfig.routes)) return {};\n const route = apiConfig.routes[actionName];\n if (!isRecord(route)) return {};\n const effect =\n route.effect === 'read' ||\n route.effect === 'write' ||\n route.effect === 'destructive'\n ? route.effect\n : undefined;\n if (\n effect === 'read' &&\n (route.method === 'PUT' ||\n route.method === 'PATCH' ||\n route.method === 'DELETE')\n ) {\n throw new Error(\n `Custom action ${actionName} cannot declare a read effect for a ${route.method} route`,\n );\n }\n return {\n ...(effect ? { effect } : {}),\n ...(typeof route.idempotent === 'boolean'\n ? { idempotent: route.idempotent }\n : {}),\n ...(typeof route.openWorld === 'boolean'\n ? { openWorld: route.openWorld }\n : {}),\n };\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,IAAa,mDAAwD,IAAI,IAAI;CAE3E;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;AAQD,SAAgB,2BAA2B,YAA6B;CACtE,OAAO,iCAAiC,IAAI,UAAU;AACxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAgB,yBACd,SACA,QACA,iBACa;CACb,MAAM,WAAW,QAAQ;CACzB,MAAM,WAAW,QAAQ,WAAW,CAAC;CACrC,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,CAAC,MAAM,WAAW,SAAS;EACpC,IAAI,gBAAgB,SAAS,IAAI,GAAG;EACpC,IAAI,2BAA2B,IAAI,GAAG;EACtC,IAAI,CAAC,OAAO,UAAU;EACtB,IAAI,SAAS,SAAS,IAAI,GAAG;EAC7B,IAAI,aAAa,KAAA,KAAa,CAAC,SAAS,SAAS,IAAI,GAAG;EACxD,OAAO,IAAI,IAAI;CACjB;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,IAAa,kBAAkB;CAC7B;CACA;CACA;CACA;CACA;AACF;;;;;;;AAQA,SAAgB,gBAAgB,MAAuB;CACrD,OAAQ,gBAAsC,SAAS,IAAI;AAC7D;;;;;;;;AASA,SAAgB,iBAAiB,MAAuB;CACtD,OAAO,gBAAgB,KAAK,YAAY,CAAC;AAC3C;;;;;;;AA4BA,SAAgB,+BACd,WACA,eACQ;CACR,OAAO,kBAAkB,OAAO,aAAa;AAC/C;;;;;;AAqBA,SAAgB,4BACd,SAC8B;CAC9B,MAAM,eACJ,QAAQ,iBAAiB,QAAQ,QAAQ,WAAW,eAAe;CACrE,MAAM,iBAAiB,oBACrB,QAAQ,WACR,QAAQ,UACV;CAKA,MAAM,QAAQ,mBAAmB,eAAe,iBAAiB;CACjE,MAAM,aAAa,2BACjB,QAAQ,WACR,QAAQ,UACV;CACA,MAAM,SAAS,WAAW,UAAU;CAEpC,OAAO;EACL;EACA,YAAY,UAAU;EACtB,GAAI,QAAQ,QAAQ,aAChB,EAAE,YAAY,QAAQ,OAAO,WAAW,IACxC,CAAC;EACL,UAAU,QAAQ,QAAQ,aAAa;EACvC;EAOA,YAAY,WAAW,cAAc;EACrC,WAAW,WAAW,aAAa;CACrC;AACF;;AAGA,SAAgB,6BACd,UACY;CACZ,MAAM,aAAyC,CAAC;CAChD,MAAM,WAAqB,CAAC;CAE5B,IAAI,SAAS,YAAY;EACvB,WAAW,KAAK;GACd,MAAM;GACN,aAAa;EACf;EACA,SAAS,KAAK,IAAI;CACpB;CAKA,IAAI,CAAC,SAAS,YACZ,WAAW,UAAU;EACnB,MAAM;EACN,aAAa;EACb,sBAAsB;CACxB;MACK,IACL,SAAS,WAAW,WAAW,KAC/B,SAAS,WAAW,EAAE,EAAE,SAAS,WACjC;EACA,MAAM,YAAY,SAAS,WAAW;EACtC,WAAW,UAAU;GACnB,GAAG,wBAAwB,UAAU,IAAI;GACzC,aAAa;GACb,GAAI,UAAU,YAAY,KAAA,IACtB,EAAE,SAAS,UAAU,QAAQ,IAC7B,CAAC;EACP;EACA,IAAI,CAAC,UAAU,UAAU,SAAS,KAAK,SAAS;CAClD,OACE,KAAK,MAAM,aAAa,SAAS,YAAY;EAC3C,MAAM,YAAY,+BAChB,UACA,UAAU,IACZ;EACA,WAAW,aAAa;GACtB,GAAG,wBAAwB,UAAU,IAAI;GACzC,GAAI,UAAU,YAAY,KAAA,IACtB,EAAE,SAAS,UAAU,QAAQ,IAC7B,CAAC;EACP;EACA,IAAI,CAAC,UAAU,UAAU,SAAS,KAAK,SAAS;CAClD;CAGF,OAAO;EACL,MAAM;EACN;EACA,GAAI,SAAS,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;CAC5C;AACF;;;;;;AAOA,SAAgB,gCACd,UACA,MACW;CACX,MAAM,EAAE,IAAI,KAAK,SAAS,GAAG,eAAe;CAE5C,IAAI,CAAC,SAAS,YACZ,OAAO,CACL,SAAS,OAAO,KAAK,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAC/C,UACA,UACN;CAEF,IAAI,SAAS,WAAW,WAAW,GAAG,OAAO,CAAC;CAC9C,IACE,SAAS,WAAW,WAAW,KAC/B,SAAS,WAAW,EAAE,EAAE,SAAS,WAKjC,OAAO,CAAC,OAAO;CAEjB,OAAO,SAAS,WAAW,KACxB,cACC,KAAK,+BAA+B,UAAU,UAAU,IAAI,EAChE;AACF;;AAkBA,IAAa,wCAAwC;;;;;;AAOrD,SAAgB,6BACd,OACiC;CACjC,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,OAAO,OAAO,OAAO,KAAA;CACnD,IAAI,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,YAAY,UAC7D;CAGF,MAAM,SACJ,OAAO,MAAM,WAAW,YACxB,OAAO,UAAU,MAAM,MAAM,KAC7B,MAAM,UAAU,OAChB,MAAM,UAAU,MACZ,MAAM,SACN;CAEN,OAAO;EACL,IAAI;EACJ,MAAM,MAAM;EACZ,SAAS,WAAW,MAAM,OAAO;EACjC;EACA,GAAI,OAAO,OAAO,OAAO,SAAS,IAC9B,EAAE,SAAS,YAAY,MAAM,OAAO,EAAE,IACtC,CAAC;EACL,GAAI,OAAO,MAAM,cAAc,YAC3B,EAAE,WAAW,MAAM,UAAU,IAC7B,CAAC;EACL,GAAI,OAAO,MAAM,kBAAkB,WAC/B,EAAE,eAAe,MAAM,cAAc,IACrC,CAAC;CACP;AACF;AAEA,SAAS,oBACP,WACA,YAC+B;CAC/B,IAAI,CAAC,SAAS,SAAS,KAAK,CAAC,SAAS,UAAU,MAAM,GAAG,OAAO,KAAA;CAChE,MAAM,QAAQ,UAAU,OAAO;CAC/B,OAAO,SAAS,KAAK,MAClB,MAAM,UAAU,UAAU,MAAM,UAAU,gBACzC,MAAM,QACN,KAAA;AACN;AAEA,SAAS,2BACP,WACA,YAKA;CACA,IAAI,CAAC,SAAS,SAAS,KAAK,CAAC,SAAS,UAAU,MAAM,GAAG,OAAO,CAAC;CACjE,MAAM,QAAQ,UAAU,OAAO;CAC/B,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,CAAC;CAC9B,MAAM,SACJ,MAAM,WAAW,UACjB,MAAM,WAAW,WACjB,MAAM,WAAW,gBACb,MAAM,SACN,KAAA;CACN,IACE,WAAW,WACV,MAAM,WAAW,SAChB,MAAM,WAAW,WACjB,MAAM,WAAW,WAEnB,MAAM,IAAI,MACR,iBAAiB,WAAW,sCAAsC,MAAM,OAAO,OACjF;CAEF,OAAO;EACL,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,OAAO,MAAM,eAAe,YAC5B,EAAE,YAAY,MAAM,WAAW,IAC/B,CAAC;EACL,GAAI,OAAO,MAAM,cAAc,YAC3B,EAAE,WAAW,MAAM,UAAU,IAC7B,CAAC;CACP;AACF;AAEA,SAAS,YAAY,OAAgB,uBAAO,IAAI,QAAgB,GAAY;CAC1E,IAAI,OAAO,UAAU,UAAU,OAAO,WAAW,KAAK;CACtD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,IAAI,KAAK,IAAI,KAAK,GAAG,OAAO;CAC5B,KAAK,IAAI,KAAK;CACd,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,UAAU,YAAY,OAAO,IAAI,CAAC;CACtD,MAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,IAAI,cAAc,OAAO,aAAa,cAAc,MAAM,OAAO;CACjE,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,KAAK,GAC9C,OAAO,OAAO,eAAe,GAAG,IAC5B,eACA,YAAY,QAAQ,IAAI;CAE9B,OAAO;AACT;AAEA,SAAS,WAAW,OAAuB;CACzC,OAAO,MACJ,QAAQ,0BAA0B,mBAAmB,CAAC,CACtD,QACC,sDACA,eACF;AACJ;AAEA,SAAS,eAAe,KAAsB;CAC5C,OAAO,0EAA0E,KAC/E,GACF;AACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E"}
|
|
@@ -4,10 +4,11 @@
|
|
|
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, type ResolvedCustomActionMetadata, resolveCustomActionMetadata, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, type ToolEffect, } from './custom-action';
|
|
7
|
+
export { buildCustomActionInputSchema, buildCustomActionInvocationArgs, CRUD_OPERATIONS, type CustomActionFailure, type CustomActionMetadata, type CustomActionScope, customActionParameterInputName, isCrudOperation, isCrudToolAction, normalizeCustomActionFailure, type ResolveCustomActionMetadataOptions, type ResolvedCustomActionMetadata, resolveCustomActionMetadata, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, type ToolEffect, } from './custom-action';
|
|
8
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';
|
|
9
9
|
export type { MCPConfig, MCPContext, MCPRequest, MCPResponse, MCPTool, MCPToolListCacheHint, MCPToolListCacheOptions, } from './mcp';
|
|
10
10
|
export { MCP_STABLE_CATALOG_TTL_MS, MCPGenerator, resolveMCPToolListCacheHint, sortMCPTools, } from './mcp';
|
|
11
|
+
export { handlePlaybookPreflightRoute, isApiActionEnabledForObject, isRestActionRoutable, isRestRoutePublic, PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, type PlaybookPreflightProvider, type PlaybookPreflightRouteOptions, type PlaybookPreflightRouteRequest, resolveRegisteredObjectName, restFieldReadPermissions, restMethodForApiAction, } from './preflight-route';
|
|
11
12
|
export type { APIConfig, APIContext, RestServerConfig } from './rest';
|
|
12
13
|
export { APIGenerator, computeRuntimeWebManifestHash, createRestServer, startRestServer, } from './rest';
|
|
13
14
|
export type { OpenAPIConfig } from './swagger';
|
|
@@ -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;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,KAAK,4BAA4B,EACjC,2BAA2B,EAC3B,qCAAqC,EACrC,KAAK,UAAU,GAChB,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,EACP,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,2BAA2B,EAC3B,YAAY,GACb,MAAM,OAAO,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,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,8BAA8B,EAC9B,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,2BAA2B,EAC3B,qCAAqC,EACrC,KAAK,UAAU,GAChB,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,EACP,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,2BAA2B,EAC3B,YAAY,GACb,MAAM,OAAO,CAAC;AAIf,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,oBAAoB,EACpB,iBAAiB,EACjB,6BAA6B,EAC7B,gCAAgC,EAChC,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,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;AAEvB,OAAO,EACL,uBAAuB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
package/dist/generators/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
1
|
+
import { CRUD_OPERATIONS, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, customActionParameterInputName, isCrudOperation, isCrudToolAction, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
2
2
|
import { runWithTenantGate, setTenantEntryPointRunner } from "./tenant-gate.js";
|
|
3
3
|
import { CLIGenerator, getCLIHandler, setupCLI } from "./cli.js";
|
|
4
4
|
import { PRIVATE_READ_CACHE_CONTROL, canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, resolveReadCacheControl, resolveTenantEtagDiscriminator, versionConditionalResponse, warnIfSharedCacheNeutralized } from "./conditional-get.js";
|
|
5
5
|
import { DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, buildChangeEventStream, changeEventSubscribersAtCapacity, eventStreamCapacityExceededResponse, normalizeEventsMaxSubscribers, signalVisibleToTenant, tryReserveChangeEventSubscriberSlot } from "./events-route.js";
|
|
6
6
|
import { MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, resolveMCPToolListCacheHint, sortMCPTools } from "./mcp.js";
|
|
7
|
+
import { PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, handlePlaybookPreflightRoute, isApiActionEnabledForObject, isRestActionRoutable, isRestRoutePublic, resolveRegisteredObjectName, restFieldReadPermissions, restMethodForApiAction } from "./preflight-route.js";
|
|
7
8
|
import { normalizeTypedHttpError } from "./typed-http-error.js";
|
|
8
9
|
import { APIGenerator, computeRuntimeWebManifestHash, createRestServer, startRestServer } from "./rest.js";
|
|
9
10
|
import { generateOpenAPISpec, setupSwaggerUI } from "./swagger.js";
|
|
10
|
-
export { APIGenerator, CLIGenerator, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, PRIVATE_READ_CACHE_CONTROL, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, canonicalReadRepresentation, changeEventSubscribersAtCapacity, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, createRestServer, customActionParameterInputName, eventStreamCapacityExceededResponse, generateOpenAPISpec, getCLIHandler, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, normalizeCustomActionFailure, normalizeEventsMaxSubscribers, normalizeTypedHttpError, resolveCustomActionMetadata, resolveMCPToolListCacheHint, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, setTenantEntryPointRunner, setupCLI, setupSwaggerUI, signalVisibleToTenant, sortMCPTools, startRestServer, tryReserveChangeEventSubscriberSlot, versionConditionalResponse, warnIfSharedCacheNeutralized };
|
|
11
|
+
export { APIGenerator, CLIGenerator, CRUD_OPERATIONS, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, PRIVATE_READ_CACHE_CONTROL, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, canonicalReadRepresentation, changeEventSubscribersAtCapacity, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, createRestServer, customActionParameterInputName, eventStreamCapacityExceededResponse, generateOpenAPISpec, getCLIHandler, handlePlaybookPreflightRoute, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, isApiActionEnabledForObject, isCrudOperation, isCrudToolAction, isRestActionRoutable, isRestRoutePublic, normalizeCustomActionFailure, normalizeEventsMaxSubscribers, normalizeTypedHttpError, resolveCustomActionMetadata, resolveMCPToolListCacheHint, resolveReadCacheControl, resolveRegisteredObjectName, resolveTenantEtagDiscriminator, restFieldReadPermissions, restMethodForApiAction, runWithTenantGate, setTenantEntryPointRunner, setupCLI, setupSwaggerUI, signalVisibleToTenant, sortMCPTools, startRestServer, tryReserveChangeEventSubscriberSlot, versionConditionalResponse, warnIfSharedCacheNeutralized };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/generators/mcp.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/generators/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/generators/mcp.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { buildDefaultListOrderBy, resolveListLimit, resolveListOffset } from "../query-bounds.js";
|
|
2
|
-
import { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInvocationArgs, customActionParameterInputName, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
2
|
+
import { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInvocationArgs, customActionParameterInputName, isCrudOperation, isCrudToolAction, normalizeCustomActionFailure, resolveCustomActionMetadata } from "./custom-action.js";
|
|
3
|
+
import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
|
|
3
4
|
import { ObjectRegistry } from "../registry.js";
|
|
4
5
|
import { SmrtCollection } from "../collection.js";
|
|
5
6
|
import { runWithTenantGate } from "./tenant-gate.js";
|
|
6
7
|
import { generatedSiblingExtension, renderGeneratedSource, resolveGeneratedSourceLanguage } from "./mcp-emit.js";
|
|
7
8
|
import { generateClaudeConfig, generateMCPDocumentation, generateMCPScript, generateRuntimeBootstrap } from "./mcp-runtime-template.js";
|
|
8
|
-
import { buildToolInputSchema, fieldTypeToJsonSchema, finalizeMcpJsonSchema } from "./tool-schema.js";
|
|
9
|
+
import { buildToolInputSchema, fieldTypeToJsonSchema, finalizeMcpJsonSchema, isCrudAction } from "./tool-schema.js";
|
|
9
10
|
import { dirname, resolve } from "node:path";
|
|
10
11
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
11
12
|
//#region src/generators/mcp.ts
|
|
@@ -131,6 +132,7 @@ var MCPGenerator = class {
|
|
|
131
132
|
const registeredClasses = ObjectRegistry.getAllClasses();
|
|
132
133
|
for (const [key, classInfo] of registeredClasses) {
|
|
133
134
|
const simpleName = classInfo.name || key;
|
|
135
|
+
if (isFrameworkBaseClass(simpleName, classInfo.packageName)) continue;
|
|
134
136
|
const mcpConfig = ObjectRegistry.getConfig(simpleName).mcp;
|
|
135
137
|
if (mcpConfig === false) continue;
|
|
136
138
|
const excluded = typeof mcpConfig === "object" && mcpConfig?.exclude ? mcpConfig.exclude : [];
|
|
@@ -187,14 +189,11 @@ var MCPGenerator = class {
|
|
|
187
189
|
const mcpConfig = ObjectRegistry.getConfig(objectName).mcp;
|
|
188
190
|
const included = typeof mcpConfig === "object" ? mcpConfig?.include : void 0;
|
|
189
191
|
const excluded = typeof mcpConfig === "object" && mcpConfig?.exclude ? mcpConfig.exclude : [];
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"delete"
|
|
196
|
-
];
|
|
197
|
-
const customMethodsInInclude = included?.filter((item) => !crudOperations.includes(item)) || [];
|
|
192
|
+
const customMethodsInInclude = included?.filter((item) => {
|
|
193
|
+
if (!isCrudToolAction(item)) return true;
|
|
194
|
+
if (!isCrudOperation(item)) console.warn(`Warning: MCP include entry '${item}' for ${objectName} resolves to the reserved CRUD tool '${lowerName}_${item.toLowerCase()}' and was ignored; use '${item.toLowerCase()}' to expose that operation`);
|
|
195
|
+
return false;
|
|
196
|
+
}) || [];
|
|
198
197
|
const hasIncludeList = included !== void 0;
|
|
199
198
|
const methods = await ObjectRegistry.getAllMethods(objectName);
|
|
200
199
|
const methodNames = new Set(Array.from(methods.keys()));
|
|
@@ -211,6 +210,7 @@ var MCPGenerator = class {
|
|
|
211
210
|
tools.push(this.buildCustomActionTool(objectName, lowerName, methodName, methodDef, this.hasCollectionReceiver(classInfo)));
|
|
212
211
|
}
|
|
213
212
|
else for (const [methodName, methodDef] of methods) {
|
|
213
|
+
if (isCrudToolAction(methodName)) continue;
|
|
214
214
|
if (!methodDef.isPublic) continue;
|
|
215
215
|
if (excluded.includes(methodName)) continue;
|
|
216
216
|
tools.push(this.buildCustomActionTool(objectName, lowerName, methodName, methodDef, this.hasCollectionReceiver(classInfo)));
|
|
@@ -313,13 +313,7 @@ var MCPGenerator = class {
|
|
|
313
313
|
} },
|
|
314
314
|
required: ["error"]
|
|
315
315
|
};
|
|
316
|
-
if (!
|
|
317
|
-
"list",
|
|
318
|
-
"get",
|
|
319
|
-
"create",
|
|
320
|
-
"update",
|
|
321
|
-
"delete"
|
|
322
|
-
].includes(action)) return finalizeMcpJsonSchema({
|
|
316
|
+
if (!isCrudAction(action)) return finalizeMcpJsonSchema({
|
|
323
317
|
type: "object",
|
|
324
318
|
anyOf: [{
|
|
325
319
|
type: "object",
|
|
@@ -528,13 +522,7 @@ var MCPGenerator = class {
|
|
|
528
522
|
if (separator <= 0) return null;
|
|
529
523
|
const objectPrefix = toolName.slice(0, separator);
|
|
530
524
|
const action = toolName.slice(separator + 1);
|
|
531
|
-
if (
|
|
532
|
-
"list",
|
|
533
|
-
"get",
|
|
534
|
-
"create",
|
|
535
|
-
"update",
|
|
536
|
-
"delete"
|
|
537
|
-
].includes(action)) return null;
|
|
525
|
+
if (isCrudToolAction(action)) return null;
|
|
538
526
|
const classEntry = Array.from(ObjectRegistry.getAllClasses().entries()).find(([key, info]) => (info.name || key).toLowerCase() === objectPrefix.toLowerCase());
|
|
539
527
|
if (!classEntry) return null;
|
|
540
528
|
const [key, classInfo] = classEntry;
|
|
@@ -561,13 +549,7 @@ var MCPGenerator = class {
|
|
|
561
549
|
}
|
|
562
550
|
/** Build the MCP-required object root without changing legacy text payloads. */
|
|
563
551
|
toStructuredContent(action, publicResult) {
|
|
564
|
-
if (!
|
|
565
|
-
"list",
|
|
566
|
-
"get",
|
|
567
|
-
"create",
|
|
568
|
-
"update",
|
|
569
|
-
"delete"
|
|
570
|
-
].includes(action)) return { data: publicResult };
|
|
552
|
+
if (!isCrudToolAction(action)) return { data: publicResult };
|
|
571
553
|
if (publicResult === null || typeof publicResult !== "object" || Array.isArray(publicResult)) throw new Error(`Expected object result for MCP ${action} action`);
|
|
572
554
|
return publicResult;
|
|
573
555
|
}
|
|
@@ -942,20 +924,13 @@ var MCPGenerator = class {
|
|
|
942
924
|
}
|
|
943
925
|
async runtimeCustomActions(tools) {
|
|
944
926
|
const metadata = {};
|
|
945
|
-
const crudActions = /* @__PURE__ */ new Set([
|
|
946
|
-
"list",
|
|
947
|
-
"get",
|
|
948
|
-
"create",
|
|
949
|
-
"update",
|
|
950
|
-
"delete"
|
|
951
|
-
]);
|
|
952
927
|
const classes = ObjectRegistry.getAllClasses();
|
|
953
928
|
for (const tool of tools) {
|
|
954
929
|
const separator = tool.name.indexOf("_");
|
|
955
930
|
if (separator === -1) continue;
|
|
956
931
|
const objectPrefix = tool.name.slice(0, separator);
|
|
957
932
|
const action = tool.name.slice(separator + 1);
|
|
958
|
-
if (
|
|
933
|
+
if (isCrudToolAction(action)) continue;
|
|
959
934
|
const matched = Array.from(classes.entries()).find(([key, info]) => (info.name || key).toLowerCase() === objectPrefix);
|
|
960
935
|
if (!matched) continue;
|
|
961
936
|
const [key, classInfo] = matched;
|