@happyvertical/smrt-core 0.45.3 → 0.47.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.
Files changed (106) hide show
  1. package/AGENTS.md +39 -0
  2. package/agents/generators.md +0 -1
  3. package/dist/browser.d.ts +2 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/decorators/compatibility.d.ts +46 -0
  6. package/dist/decorators/compatibility.d.ts.map +1 -1
  7. package/dist/decorators/compatibility.js +48 -5
  8. package/dist/decorators/compatibility.js.map +1 -1
  9. package/dist/decorators/index.d.ts +119 -1
  10. package/dist/decorators/index.d.ts.map +1 -1
  11. package/dist/decorators/index.js +52 -8
  12. package/dist/decorators/index.js.map +1 -1
  13. package/dist/embedded-write-queue.d.ts.map +1 -1
  14. package/dist/embedded-write-queue.js +20 -1
  15. package/dist/embedded-write-queue.js.map +1 -1
  16. package/dist/generators/custom-action.d.ts +450 -41
  17. package/dist/generators/custom-action.d.ts.map +1 -1
  18. package/dist/generators/custom-action.js +765 -56
  19. package/dist/generators/custom-action.js.map +1 -1
  20. package/dist/generators/index.d.ts +1 -3
  21. package/dist/generators/index.d.ts.map +1 -1
  22. package/dist/generators/index.js +3 -4
  23. package/dist/generators/mcp.d.ts.map +1 -1
  24. package/dist/generators/mcp.js +1 -1
  25. package/dist/generators/mcp.js.map +1 -1
  26. package/dist/generators/preflight-route.d.ts +12 -10
  27. package/dist/generators/preflight-route.d.ts.map +1 -1
  28. package/dist/generators/preflight-route.js +46 -14
  29. package/dist/generators/preflight-route.js.map +1 -1
  30. package/dist/generators/rest.d.ts +44 -0
  31. package/dist/generators/rest.d.ts.map +1 -1
  32. package/dist/generators/rest.js +84 -9
  33. package/dist/generators/rest.js.map +1 -1
  34. package/dist/generators/tenant-gate.d.ts +33 -9
  35. package/dist/generators/tenant-gate.d.ts.map +1 -1
  36. package/dist/generators/tenant-gate.js +2 -1
  37. package/dist/generators/tenant-gate.js.map +1 -1
  38. package/dist/generators.js +3 -4
  39. package/dist/index.d.ts +5 -3
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +6 -6
  42. package/dist/knowledge.d.ts.map +1 -1
  43. package/dist/knowledge.js +87 -31
  44. package/dist/knowledge.js.map +1 -1
  45. package/dist/manifest/static-manifest.d.ts.map +1 -1
  46. package/dist/manifest/static-manifest.js +125 -53
  47. package/dist/manifest/static-manifest.js.map +1 -1
  48. package/dist/manifest/store.js +1 -1
  49. package/dist/manifest/store.js.map +1 -1
  50. package/dist/manifest.json +184 -53
  51. package/dist/migrations/framework-base-tables.d.ts +148 -0
  52. package/dist/migrations/framework-base-tables.d.ts.map +1 -0
  53. package/dist/migrations/framework-base-tables.js +419 -0
  54. package/dist/migrations/framework-base-tables.js.map +1 -0
  55. package/dist/migrations/index.d.ts +1 -0
  56. package/dist/migrations/index.d.ts.map +1 -1
  57. package/dist/migrations/index.js +2 -1
  58. package/dist/migrations.js +2 -1
  59. package/dist/postgres-permissions.d.ts +45 -0
  60. package/dist/postgres-permissions.d.ts.map +1 -0
  61. package/dist/postgres-permissions.js +424 -0
  62. package/dist/postgres-permissions.js.map +1 -0
  63. package/dist/registry/framework-base-classes.d.ts.map +1 -1
  64. package/dist/registry/framework-base-classes.js +4 -1
  65. package/dist/registry/framework-base-classes.js.map +1 -1
  66. package/dist/registry/index.d.ts +1 -1
  67. package/dist/registry/index.d.ts.map +1 -1
  68. package/dist/registry/shared-state.d.ts +19 -0
  69. package/dist/registry/shared-state.d.ts.map +1 -1
  70. package/dist/registry/shared-state.js +16 -1
  71. package/dist/registry/shared-state.js.map +1 -1
  72. package/dist/registry.d.ts +136 -1
  73. package/dist/registry.d.ts.map +1 -1
  74. package/dist/registry.js +231 -1
  75. package/dist/registry.js.map +1 -1
  76. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  77. package/dist/scanner/manifest-generator.js +18 -16
  78. package/dist/scanner/manifest-generator.js.map +1 -1
  79. package/dist/scanner/types.d.ts +59 -6
  80. package/dist/scanner/types.d.ts.map +1 -1
  81. package/dist/scanner/types.js.map +1 -1
  82. package/dist/scripts/create-wrappers.js +0 -5
  83. package/dist/smrt-knowledge.json +35 -35
  84. package/dist/testing/database.d.ts +6 -1
  85. package/dist/testing/database.d.ts.map +1 -1
  86. package/dist/testing/database.js +9 -1
  87. package/dist/testing/database.js.map +1 -1
  88. package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
  89. package/dist/vite-plugin/api-client-entries.js +23 -19
  90. package/dist/vite-plugin/api-client-entries.js.map +1 -1
  91. package/dist/vite-plugin/index.d.ts.map +1 -1
  92. package/dist/vite-plugin/index.js +3 -167
  93. package/dist/vite-plugin/index.js.map +1 -1
  94. package/dist/vite-plugin/sveltekit-generator.d.ts +31 -4
  95. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  96. package/dist/vite-plugin/sveltekit-generator.js +137 -71
  97. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  98. package/dist/vite-plugin/templates/default-ui.ts +22 -1
  99. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  100. package/dist/vite-plugin/web-collections.js +1 -1
  101. package/dist/vite-plugin/web-collections.js.map +1 -1
  102. package/package.json +10 -10
  103. package/dist/generators/cli.d.ts +0 -155
  104. package/dist/generators/cli.d.ts.map +0 -1
  105. package/dist/generators/cli.js +0 -473
  106. package/dist/generators/cli.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit-generator.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sveltekit-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAYvE,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAa1B,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACrC;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE;QACZ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;IACF;;;;;;OAMG;IACH,cAAc,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxC;AAYD,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAqxBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD;AA+BD,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EACjC,SAAS,EAAE,OAAO,EAClB,YAAY,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,EAC5C,YAAY,GAAE,MAAM,GAAG,YAEb,GACT,4BAA4B,CAwB9B;AAiUD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAqGf;AAy0BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,qBAAqB,EAChC,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,GAAG,CAAC,MAAM,CAAC,CA6Cb;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAuDD;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,mBAAmB,GAC5B,wBAAwB,EAAE,CAsB5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,mBAAmB,GAC5B,IAAI,CA2BN"}
1
+ {"version":3,"file":"sveltekit-generator.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sveltekit-generator.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { MAX_LIST_LIMIT, buildDefaultListOrderBy } from "../query-bounds.js";
2
- import { resolveCustomActionMetadata, resolveCustomActionNames } from "../generators/custom-action.js";
2
+ import { CRUD_OPERATIONS, createManifestClassNamePredicate, declaredTypeAcceptsDate, isCrudOperation, queryStringDecoderFor, resolveApiMethodExposure, resolveCustomActionMetadata, resolveCustomActionNames, resolveDeclaredScopeMismatch, resolveEffectiveActionMetadata } from "../generators/custom-action.js";
3
3
  import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
4
4
  import { generateConditionalGetRouteHelper } from "../generators/conditional-get.js";
5
5
  import { AUTO_GENERATED_ROUTE_HEADER } from "./route-header.js";
@@ -16,13 +16,6 @@ import { isAbsolute, join, relative, sep } from "node:path";
16
16
  * Generates real TypeScript files in src/routes/api/ based on discovered SMRT objects
17
17
  */
18
18
  var BIOME_LINE_WIDTH = 80;
19
- var STANDARD_API_ACTIONS = [
20
- "list",
21
- "get",
22
- "create",
23
- "update",
24
- "delete"
25
- ];
26
19
  var GITIGNORE_MANAGED_BLOCK_START = "# BEGIN SMRT auto-generated routes (Vite plugin)";
27
20
  var GITIGNORE_MANAGED_BLOCK_END = "# END SMRT auto-generated routes (Vite plugin)";
28
21
  var LEGACY_GITIGNORE_HEADER = "# SMRT auto-generated routes (from Vite plugin)";
@@ -603,8 +596,12 @@ function extractRoutePathParamNames(pathSegments) {
603
596
  return pathSegments.filter((segment) => /^\[[^\]]+\]$/.test(segment)).map((segment) => segment.slice(1, -1)).filter(Boolean);
604
597
  }
605
598
  function resolveApiActionRouteConfig(actionName, actionDef, apiConfig, routeOptions = {}, defaultScope = actionDef.isStatic ? "collection" : "item") {
606
- const routeConfig = getApiConfigObject(apiConfig)?.routes?.[actionName];
607
- const pathSegments = normalizeCustomRoutePath(actionName, routeConfig?.path, routeOptions);
599
+ const effective = resolveEffectiveActionMetadata({
600
+ actionName,
601
+ method: actionDef,
602
+ apiConfig
603
+ });
604
+ const pathSegments = normalizeCustomRoutePath(actionName, effective.path, routeOptions);
608
605
  return {
609
606
  scope: resolveCustomActionMetadata({
610
607
  actionName,
@@ -612,7 +609,7 @@ function resolveApiActionRouteConfig(actionName, actionDef, apiConfig, routeOpti
612
609
  apiConfig,
613
610
  defaultScope
614
611
  }).scope,
615
- method: normalizeApiHttpMethod(routeConfig?.method),
612
+ method: normalizeApiHttpMethod(effective.httpMethod),
616
613
  pathSegments,
617
614
  pathParamNames: extractRoutePathParamNames(pathSegments)
618
615
  };
@@ -633,11 +630,15 @@ function hasSingleOptionsParameter(actionDef) {
633
630
  const parameters = Array.isArray(actionDef.parameters) ? actionDef.parameters : [];
634
631
  return parameters.length === 1 && parameters[0]?.name === "options";
635
632
  }
636
- function buildActionInvocationArgs(actionDef, optionsIdentifier = "options") {
633
+ function buildActionInvocationArgs(actionDef, optionsIdentifier = "options", httpMethod) {
637
634
  const parameters = Array.isArray(actionDef.parameters) ? actionDef.parameters : [];
638
635
  if (parameters.length === 0) return [];
639
636
  if (hasSingleOptionsParameter(actionDef)) return [optionsIdentifier];
640
- return parameters.map((parameter) => buildOptionsPropertyAccess(parameter.name));
637
+ return parameters.map((parameter, index) => {
638
+ const access = buildOptionsPropertyAccess(parameter.name);
639
+ const decoder = httpMethod === "GET" ? queryStringDecoderFor(parameter.type) : declaredTypeAcceptsDate(parameter.type) ? "toCustomActionDate" : void 0;
640
+ return decoder ? `${decoder}(${access}) as ActionArgs[${index}]` : access;
641
+ });
641
642
  }
642
643
  function buildObjectTypeProperty(propertyName) {
643
644
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyName)) return propertyName;
@@ -1169,14 +1170,12 @@ async function generateRoutesForObject(projectRoot, className, objectDef, manife
1169
1170
  const itemRoute = generateItemRouteTemplate(projectRoot, className, objectDef, manifest, includedActions, options, join(routeDir, "[id]"));
1170
1171
  generatedRoutePaths.push(writeRoute(join(routeDir, "[id]"), "+server.ts", itemRoute));
1171
1172
  }
1172
- const customActions = Object.entries(objectDef.methods).filter(([name, method]) => !STANDARD_API_ACTIONS.includes(name) && method.isPublic && shouldIncludeInApi(name, apiConfig));
1173
+ const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef, manifest, false);
1174
+ warnUnhostedActions(className, rejected);
1173
1175
  const actionSpecs = [];
1174
1176
  for (const [actionName, actionDef] of customActions) {
1175
1177
  const routeConfig = resolveApiActionRouteConfig(actionName, actionDef, apiConfig, { kebabRoutes: options.kebabRoutes });
1176
- if (routeConfig.scope === "collection" && !actionDef.isStatic) {
1177
- console.warn(`[smrt] Skipping ${className}.${actionName} - collection API routes require a static method`);
1178
- continue;
1179
- }
1178
+ warnDeclaredScopeMismatch(className, actionName, actionDef, apiConfig, routeConfig.scope);
1180
1179
  const actionBaseDir = routeConfig.scope === "collection" ? routeDir : join(routeDir, "[id]");
1181
1180
  actionSpecs.push({
1182
1181
  routeDir: join(actionBaseDir, ...routeConfig.pathSegments),
@@ -1207,15 +1206,13 @@ async function generateCollectionRoutesForObject(projectRoot, className, objectD
1207
1206
  const routeDir = join(projectRoot, options.routesDir, objectDef.collection);
1208
1207
  const lookupClassName = findItemClassRegistryKey(className, objectDef, manifest);
1209
1208
  const lookupObjectDef = findObjectDefByRegistryKey(manifest, lookupClassName);
1210
- const customActions = Object.entries(objectDef.methods).filter(([name, method]) => !STANDARD_API_ACTIONS.includes(name) && method.isPublic && shouldIncludeInApi(name, apiConfig));
1209
+ const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef, manifest, true);
1210
+ warnUnhostedActions(className, rejected);
1211
1211
  if (customActions.length === 0) return generatedRoutePaths;
1212
1212
  const actionSpecs = [];
1213
1213
  for (const [actionName, actionDef] of customActions) {
1214
1214
  const routeConfig = resolveApiActionRouteConfig(actionName, actionDef, apiConfig, { kebabRoutes: options.kebabRoutes }, "collection");
1215
- if (routeConfig.scope !== "collection") {
1216
- console.warn(`[smrt] Skipping ${className}.${actionName} - collection class methods only support collection-scoped API routes`);
1217
- continue;
1218
- }
1215
+ warnDeclaredScopeMismatch(className, actionName, actionDef, apiConfig, routeConfig.scope);
1219
1216
  actionSpecs.push({
1220
1217
  routeDir: join(routeDir, ...routeConfig.pathSegments),
1221
1218
  spec: {
@@ -1242,10 +1239,10 @@ async function generateCollectionRoutesForObject(projectRoot, className, objectD
1242
1239
  */
1243
1240
  function resolveStandardCrudActions(apiConfig) {
1244
1241
  if (apiConfig === false) return [];
1245
- if (apiConfig === true || apiConfig === void 0) return [...STANDARD_API_ACTIONS];
1246
- if (typeof apiConfig !== "object") return [...STANDARD_API_ACTIONS];
1242
+ if (apiConfig === true || apiConfig === void 0) return [...CRUD_OPERATIONS];
1243
+ if (typeof apiConfig !== "object" || apiConfig === null) return [...CRUD_OPERATIONS];
1247
1244
  const config = apiConfig;
1248
- let crud = config.include ? config.include.filter((a) => STANDARD_API_ACTIONS.includes(a)) : [...STANDARD_API_ACTIONS];
1245
+ let crud = Array.isArray(config.include) ? config.include.filter((a) => isCrudOperation(a)) : [...CRUD_OPERATIONS];
1249
1246
  if (Array.isArray(config.exclude)) {
1250
1247
  const exclude = config.exclude;
1251
1248
  crud = crud.filter((a) => !exclude.includes(a));
@@ -1253,49 +1250,103 @@ function resolveStandardCrudActions(apiConfig) {
1253
1250
  return crud;
1254
1251
  }
1255
1252
  /**
1256
- * Check if a custom action should be included in API.
1253
+ * Partition an object's custom (non-CRUD) methods into the ones the API
1254
+ * exposes and the ones it withholds, with a reason for each rejection.
1255
+ *
1256
+ * The ONE place route generation, `resolveApiActionSet`, and the knowledge
1257
+ * artifact's API projection agree, by all three reading
1258
+ * {@link resolveApiMethodExposure}. Before #2686 each re-derived a subset and
1259
+ * could disagree — most damagingly, a gate applied only to the coherence
1260
+ * resolver would report a method unreachable while the emitter next door still
1261
+ * wrote its route file.
1262
+ */
1263
+ function resolveApiCustomActions(objectDef, manifest, objectIsCollectionClass, wireability = {}) {
1264
+ const apiConfig = objectDef.decoratorConfig?.api;
1265
+ const isModelClassName = wireability.isModelClassName ?? createManifestClassNamePredicate(manifest);
1266
+ const exposed = [];
1267
+ const rejected = [];
1268
+ for (const [name, method] of Object.entries(objectDef.methods || {})) {
1269
+ const decision = resolveApiMethodExposure({
1270
+ actionName: name,
1271
+ method,
1272
+ apiConfig,
1273
+ isCollectionClass: objectIsCollectionClass,
1274
+ ...isModelClassName ? { isModelClassName } : {}
1275
+ });
1276
+ if (decision.exposed) {
1277
+ exposed.push([name, method]);
1278
+ continue;
1279
+ }
1280
+ if (decision.code === "crud-reserved" || decision.code === "not-public") continue;
1281
+ rejected.push([name, decision]);
1282
+ }
1283
+ return {
1284
+ exposed,
1285
+ rejected
1286
+ };
1287
+ }
1288
+ /**
1289
+ * Warn about the one rejection class that would be a structural failure rather
1290
+ * than a configuration choice: an action whose declared scope has no receiver
1291
+ * to bind. Both emitters carried this warning before #2686 moved the check into
1292
+ * the shared resolver, and the message text is preserved.
1293
+ *
1294
+ * It cannot currently fire — `resolveCustomActionMetadata` collapses a
1295
+ * contradicting scope back to the receiver-derived one, as it did before this
1296
+ * change; see `resolveActionReceiver` in `generators/custom-action.ts`. The
1297
+ * warning a developer actually sees for a contradicting declaration is
1298
+ * `warnDeclaredScopeMismatch` below.
1257
1299
  *
1258
- * Mirrors the CRUD inclusion path: intersect with `include` (when set), then
1259
- * subtract `exclude`. Both filters apply together, matching principle of least
1260
- * surprise for users supplying both lists.
1300
+ * Every other rejection is silent here and reported with its reason through
1301
+ * the knowledge artifact instead, so a normal build is not buried in output
1302
+ * for the 2% of methods the heuristic legitimately withholds.
1261
1303
  */
1262
- function shouldIncludeInApi(actionName, apiConfig) {
1263
- if (apiConfig === false) return false;
1264
- if (apiConfig === true || apiConfig === void 0) return true;
1265
- if (typeof apiConfig === "object" && apiConfig !== null) {
1266
- const config = apiConfig;
1267
- const included = Array.isArray(config.include) ? config.include.includes(actionName) : true;
1268
- const excluded = Array.isArray(config.exclude) ? config.exclude.includes(actionName) : false;
1269
- return included && !excluded;
1304
+ function warnUnhostedActions(className, rejected) {
1305
+ for (const [actionName, decision] of rejected) {
1306
+ if (decision.code !== "no-receiver") continue;
1307
+ console.warn(`[smrt] Skipping ${className}.${actionName} - ${decision.reason}`);
1270
1308
  }
1271
- return true;
1309
+ }
1310
+ /**
1311
+ * Report a declared `scope` that contradicts the method's actual receiver.
1312
+ *
1313
+ * The declaration is honored only where it AGREES; nothing in config can move
1314
+ * an instance method onto the class. Warning is the whole remedy — the route
1315
+ * is still emitted at the receiver-derived URL, so an author who expected the
1316
+ * other one learns why it is missing instead of silently getting nothing at
1317
+ * either address (#2686).
1318
+ */
1319
+ function warnDeclaredScopeMismatch(className, actionName, actionDef, apiConfig, effectiveScope) {
1320
+ const declared = resolveDeclaredScopeMismatch({
1321
+ actionName,
1322
+ method: actionDef,
1323
+ apiConfig,
1324
+ effectiveScope
1325
+ });
1326
+ if (!declared) return;
1327
+ console.warn(`[smrt] ${className}.${actionName} declares scope '${declared}' but its receiver is ${effectiveScope}-scoped, so the declared scope is ignored. A scope describes where a method already lives; it cannot move it. Make ${actionName} static to reach it on the collection, or drop the scope option.`);
1272
1328
  }
1273
1329
  /**
1274
1330
  * Compute the set of action names (standard CRUD + custom methods) that the
1275
1331
  * API exposes for a given object definition. This is the same resolution used
1276
1332
  * to drive route generation, exposed so coherence checks (e.g. CLI vs API)
1277
1333
  * can ask "what does the API expose?" without re-implementing the logic.
1334
+ *
1335
+ * Pass a class inventory: without one the wire-ability heuristic cannot tell a
1336
+ * model class from an options interface and therefore accepts both, so the
1337
+ * caller silently disagrees with the emitters on the LARGEST group of withheld
1338
+ * methods (see `WireabilityOptions.isModelClassName`). Supply either
1339
+ * `manifest`, or `wireability.isModelClassName` when the caller's inventory is
1340
+ * the live registry rather than a manifest — `@happyvertical/smrt-users`' CLI
1341
+ * resource listing is the second kind. Both stay optional only because this is
1342
+ * public API.
1278
1343
  */
1279
- function resolveApiActionSet(objectDef, manifest) {
1344
+ function resolveApiActionSet(objectDef, manifest, wireability = {}) {
1280
1345
  const apiConfig = objectDef.decoratorConfig?.api;
1281
1346
  if (apiConfig === false) return /* @__PURE__ */ new Set();
1282
1347
  const objectIsCollectionClass = manifest ? isCollectionManifestClass(manifest, objectDef) : isCollectionClass(objectDef);
1283
1348
  const actions = objectIsCollectionClass ? /* @__PURE__ */ new Set() : new Set(resolveStandardCrudActions(apiConfig));
1284
- for (const [name, method] of Object.entries(objectDef.methods || {})) {
1285
- if (STANDARD_API_ACTIONS.includes(name)) continue;
1286
- if (!method.isPublic) continue;
1287
- if (!shouldIncludeInApi(name, apiConfig)) continue;
1288
- const scope = resolveCustomActionMetadata({
1289
- actionName: name,
1290
- method,
1291
- apiConfig,
1292
- defaultScope: objectIsCollectionClass ? "collection" : method.isStatic ? "collection" : "item"
1293
- }).scope;
1294
- if (objectIsCollectionClass) {
1295
- if (scope !== "collection") continue;
1296
- } else if (scope === "collection" && !method.isStatic) continue;
1297
- actions.add(name);
1298
- }
1349
+ for (const [name] of resolveApiCustomActions(objectDef, manifest, objectIsCollectionClass, wireability).exposed) actions.add(name);
1299
1350
  return actions;
1300
1351
  }
1301
1352
  /**
@@ -1317,9 +1368,11 @@ function resolveApiActionSet(objectDef, manifest) {
1317
1368
  *
1318
1369
  * **Bare `cli: true`/`cli: {}`** (#2638, the new case this lint now
1319
1370
  * inspects): every public custom method minus exclude, exactly the "every
1320
- * public method minus exclude" resolution `CLIGenerator.listCommands()`
1321
- * already applies (via `resolveCustomActionNames`, reused not
1322
- * reimplemented) -- lifecycle methods excluded, same as everywhere else.
1371
+ * public method minus exclude" resolution this function shares with
1372
+ * `packages/core/src/generators/custom-action.ts`'s `resolveCustomActionNames`
1373
+ * -- lifecycle methods excluded, same as everywhere else. (Pre-#2664, core's
1374
+ * now-retired `CLIGenerator.listCommands()` applied the same resolution via
1375
+ * that helper too.)
1323
1376
  * CRUD verbs are NOT checked in this branch: a class that closes its API
1324
1377
  * entirely (`api: false`) while keeping a default-open, CLI/MCP-only admin
1325
1378
  * surface (`cli: true`/omitted) is a common, intentional combination (see
@@ -1332,13 +1385,13 @@ function resolveApiActionSet(objectDef, manifest) {
1332
1385
  function resolveCliActionSet(objectDef) {
1333
1386
  const cliConfig = objectDef.decoratorConfig?.cli;
1334
1387
  if (cliConfig === false) return /* @__PURE__ */ new Set();
1335
- const included = typeof cliConfig === "object" ? cliConfig.include : void 0;
1336
- const excluded = typeof cliConfig === "object" && Array.isArray(cliConfig.exclude) ? cliConfig.exclude : [];
1388
+ const included = typeof cliConfig === "object" && cliConfig !== null ? cliConfig.include : void 0;
1389
+ const excluded = typeof cliConfig === "object" && cliConfig !== null && Array.isArray(cliConfig.exclude) ? cliConfig.exclude : [];
1337
1390
  if (included) return new Set(included.filter((cmd) => !excluded.includes(cmd)));
1338
1391
  return resolveCustomActionNames(Object.entries(objectDef.methods || {}), {
1339
1392
  include: void 0,
1340
1393
  exclude: excluded
1341
- }, STANDARD_API_ACTIONS);
1394
+ }, CRUD_OPERATIONS);
1342
1395
  }
1343
1396
  /**
1344
1397
  * Inspect a manifest and return classes whose effective CLI command set —
@@ -1356,7 +1409,7 @@ function findCliApiCoherenceViolations(manifest) {
1356
1409
  for (const [className, objectDef] of Object.entries(manifest.objects)) {
1357
1410
  const cliConfig = objectDef.decoratorConfig?.cli;
1358
1411
  if (cliConfig === false) continue;
1359
- if (typeof cliConfig === "object" && cliConfig.skipApiCheck) continue;
1412
+ if (typeof cliConfig === "object" && cliConfig !== null && cliConfig.skipApiCheck) continue;
1360
1413
  const effectiveCliCommands = resolveCliActionSet(objectDef);
1361
1414
  if (effectiveCliCommands.size === 0) continue;
1362
1415
  const apiActionSet = resolveApiActionSet(objectDef, manifest);
@@ -1377,8 +1430,9 @@ function findCliApiCoherenceViolations(manifest) {
1377
1430
  * This is deliberately narrower than `findCliApiCoherenceViolations` above.
1378
1431
  * That function is now fully correct for the broader `cli: true`/`cli: {}`
1379
1432
  * default surface too (#2638: same "every public method minus exclude"
1380
- * resolution `listCommands()` uses, so a bare `cli: true` is inspected the
1381
- * same as an explicit `include`). But `smrtPlugin()`'s default
1433
+ * resolution core's now-retired `CLIGenerator.listCommands()` used (#2664),
1434
+ * so a bare `cli: true` is inspected the same as an explicit `include`).
1435
+ * But `smrtPlugin()`'s default
1382
1436
  * `validateCliApiCoherence: true` calls this THROWING gate unconditionally
1383
1437
  * from every consuming package's own build (`configResolved`,
1384
1438
  * `vite-plugin/index.ts`) -- and a full-monorepo build during #2638
@@ -1400,10 +1454,10 @@ function findCliApiCoherenceViolations(manifest) {
1400
1454
  function validateCliIncludeAgainstApi(manifest) {
1401
1455
  const violations = findCliApiCoherenceViolations(manifest).filter(({ className }) => {
1402
1456
  const cliConfig = manifest.objects[className]?.decoratorConfig?.cli;
1403
- return typeof cliConfig === "object" && Array.isArray(cliConfig.include) && cliConfig.include.length > 0;
1457
+ return typeof cliConfig === "object" && cliConfig !== null && Array.isArray(cliConfig.include) && cliConfig.include.length > 0;
1404
1458
  });
1405
1459
  if (violations.length === 0) return;
1406
- const messages = violations.flatMap(({ className, unreachable }) => unreachable.map((action) => `[smrt] ${className}.${action} is exposed as a CLI command but is not exposed via the api.\n Either:\n - Add '${action}' to api.include, or\n - Remove '${action}' from cli.include / add it to cli.exclude.\n The CLI invokes methods over HTTP; methods without API routes are unreachable.\n If this CLI is intentionally invoked in-process (no HTTP), set\n \`cli: { skipApiCheck: true }\` on the @smrt() decorator to acknowledge.`));
1460
+ const messages = violations.flatMap(({ className, unreachable }) => unreachable.map((action) => `[smrt] ${className}.${action} is exposed as a CLI command but is not exposed via the api.\n Either:\n - Decorate '${action}' with @method({ expose: true }) to route it, or\n - Add '${action}' to api.include, or\n - Remove '${action}' from cli.include / add it to cli.exclude.\n The CLI invokes methods over HTTP; methods without API routes are unreachable.\n A public method is routed by default only when every parameter can be\n built from JSON; @method({ expose: true }) overrides that for one method\n without widening api.include. See withheldSurfaces in the knowledge\n artifact for the reason this one was withheld (#2686).\n If this CLI is intentionally invoked in-process (no HTTP), set\n \`cli: { skipApiCheck: true }\` on the @smrt() decorator to acknowledge.`));
1407
1461
  throw new Error(messages.join("\n\n"));
1408
1462
  }
1409
1463
  /**
@@ -1691,9 +1745,21 @@ function generateActionRouteTemplate(projectRoot, routeDir, routeSpecs, objectDe
1691
1745
  const hostModelType = resolveObjectTypeReference(projectRoot, firstSpec.hostClassName, objectDef, options, routeDir);
1692
1746
  const typeImports = mergeImportStatements([lookupModelType.importStatement, hostModelType.importStatement]);
1693
1747
  if (routeSpecs.some((spec) => spec.hostType !== hostType || spec.lookupClassName !== lookupClassName || spec.routeConfig.scope !== routeConfig.scope)) throw new Error(`Cannot generate mixed custom route handlers for ${lookupClassName}. All handlers sharing a route path must target the same host type and scope.`);
1748
+ const decoderImports = /* @__PURE__ */ new Set();
1749
+ for (const spec of routeSpecs) {
1750
+ if (hasSingleOptionsParameter(spec.actionDef)) continue;
1751
+ for (const parameter of spec.actionDef.parameters ?? []) {
1752
+ const decoder = spec.routeConfig?.method === "GET" ? queryStringDecoderFor(parameter.type) : declaredTypeAcceptsDate(parameter.type) ? "toCustomActionDate" : void 0;
1753
+ if (decoder) decoderImports.add(decoder);
1754
+ }
1755
+ }
1694
1756
  const importBlock = [
1695
1757
  "import { error, json } from '@sveltejs/kit';",
1696
- "import { normalizeCustomActionFailure, normalizeTypedHttpError } from '@happyvertical/smrt-core';",
1758
+ `import { ${[
1759
+ "normalizeCustomActionFailure",
1760
+ "normalizeTypedHttpError",
1761
+ ...[...decoderImports].sort()
1762
+ ].join(", ")} } from '@happyvertical/smrt-core';`,
1697
1763
  hostType === "collection" || routeConfig.scope !== "collection" ? "import { getCollection } from '$lib/server/smrt';" : "import { ObjectRegistry } from '@happyvertical/smrt-core';",
1698
1764
  typeImports,
1699
1765
  "import type { RequestHandler } from './$types';"
@@ -1723,7 +1789,7 @@ function generateActionRouteHandler(lookupClassName, actionName, actionDef, rout
1723
1789
  const receiverExpression = actionDef.isStatic ? "CollectionClass" : "typedCollection";
1724
1790
  const staticTargetLoad = actionDef.isStatic ? ` const CollectionClass = typedCollection.constructor as typeof ${hostTypeName};\n` : "";
1725
1791
  const scopedOptions = buildScopedOptionsForTenantRead(actionDef, routeConfig, tenantScoped);
1726
- const invocationArgs = buildActionInvocationArgs(actionDef, scopedOptions.optionsIdentifier);
1792
+ const invocationArgs = buildActionInvocationArgs(actionDef, scopedOptions.optionsIdentifier, routeConfig.method);
1727
1793
  return `// Custom collection method: ${actionName}
1728
1794
  export const ${handlerName}: RequestHandler = async (${collectionHandlerArgs}) => {
1729
1795
  ${authGuardLine}
@@ -1755,7 +1821,7 @@ ${optionsLoad}${scopedOptions.source} let result: unknown;
1755
1821
  if (routeConfig.scope === "collection") {
1756
1822
  const optionsLoad = buildActionOptionsLoad(actionName, actionDef, routeConfig, hostTypeName, true);
1757
1823
  const scopedOptions = buildScopedOptionsForTenantRead(actionDef, routeConfig, tenantScoped);
1758
- const invocationArgs = buildActionInvocationArgs(actionDef, scopedOptions.optionsIdentifier);
1824
+ const invocationArgs = buildActionInvocationArgs(actionDef, scopedOptions.optionsIdentifier, routeConfig.method);
1759
1825
  return `// Custom collection action: ${actionName}
1760
1826
  export const ${handlerName}: RequestHandler = async (${collectionHandlerArgs}) => {
1761
1827
  ${authGuardLine}
@@ -1788,7 +1854,7 @@ ${optionsLoad}${scopedOptions.source} const ClassRef = registered.constructor a
1788
1854
  const item = await collection.get(
1789
1855
  readScope ? { id: params.id, ...readScope } : params.id,
1790
1856
  );` : " const item = await collection.get(params.id);";
1791
- const invocationArgs = buildActionInvocationArgs(actionDef);
1857
+ const invocationArgs = buildActionInvocationArgs(actionDef, "options", routeConfig.method);
1792
1858
  return `// Custom action: ${actionName}
1793
1859
  export const ${handlerName}: RequestHandler = async (${itemHandlerArgs}) => {
1794
1860
  ${authGuardLine}
@@ -1996,6 +2062,6 @@ function updateGeneratedRouteIgnoreBlock(gitignoreContent, generatedPaths) {
1996
2062
  return `${updatedContent}${managedBlock.join("\n")}\n`;
1997
2063
  }
1998
2064
  //#endregion
1999
- export { findCliApiCoherenceViolations, generateSvelteKitRoutes, methodNameToKebab, resolveApiActionRouteConfig, resolveApiActionSet, validateCliIncludeAgainstApi };
2065
+ export { findCliApiCoherenceViolations, generateSvelteKitRoutes, methodNameToKebab, resolveApiActionRouteConfig, resolveApiActionSet, resolveApiCustomActions, validateCliIncludeAgainstApi };
2000
2066
 
2001
2067
  //# sourceMappingURL=sveltekit-generator.js.map