@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.
- package/AGENTS.md +39 -0
- package/agents/generators.md +0 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/decorators/compatibility.d.ts +46 -0
- package/dist/decorators/compatibility.d.ts.map +1 -1
- package/dist/decorators/compatibility.js +48 -5
- package/dist/decorators/compatibility.js.map +1 -1
- package/dist/decorators/index.d.ts +119 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +52 -8
- package/dist/decorators/index.js.map +1 -1
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +20 -1
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/custom-action.d.ts +450 -41
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +765 -56
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/generators/index.d.ts +1 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +3 -4
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +1 -1
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/preflight-route.d.ts +12 -10
- package/dist/generators/preflight-route.d.ts.map +1 -1
- package/dist/generators/preflight-route.js +46 -14
- package/dist/generators/preflight-route.js.map +1 -1
- package/dist/generators/rest.d.ts +44 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +84 -9
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/tenant-gate.d.ts +33 -9
- package/dist/generators/tenant-gate.d.ts.map +1 -1
- package/dist/generators/tenant-gate.js +2 -1
- package/dist/generators/tenant-gate.js.map +1 -1
- package/dist/generators.js +3 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +87 -31
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +125 -53
- 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 +184 -53
- package/dist/migrations/framework-base-tables.d.ts +148 -0
- package/dist/migrations/framework-base-tables.d.ts.map +1 -0
- package/dist/migrations/framework-base-tables.js +419 -0
- package/dist/migrations/framework-base-tables.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -1
- package/dist/migrations.js +2 -1
- package/dist/postgres-permissions.d.ts +45 -0
- package/dist/postgres-permissions.d.ts.map +1 -0
- package/dist/postgres-permissions.js +424 -0
- package/dist/postgres-permissions.js.map +1 -0
- package/dist/registry/framework-base-classes.d.ts.map +1 -1
- package/dist/registry/framework-base-classes.js +4 -1
- package/dist/registry/framework-base-classes.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/shared-state.d.ts +19 -0
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js +16 -1
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry.d.ts +136 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +231 -1
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +18 -16
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +59 -6
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/scripts/create-wrappers.js +0 -5
- package/dist/smrt-knowledge.json +35 -35
- package/dist/testing/database.d.ts +6 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +9 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +23 -19
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +3 -167
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +31 -4
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +137 -71
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/templates/default-ui.ts +22 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +1 -1
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +10 -10
- package/dist/generators/cli.d.ts +0 -155
- package/dist/generators/cli.d.ts.map +0 -1
- package/dist/generators/cli.js +0 -473
- 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;
|
|
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
|
|
607
|
-
|
|
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(
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 [...
|
|
1246
|
-
if (typeof apiConfig !== "object") return [...
|
|
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) =>
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
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
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
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
|
-
},
|
|
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()`
|
|
1381
|
-
* same as an explicit `include`).
|
|
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
|
-
|
|
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
|