@happyvertical/smrt-core 0.49.5 → 0.49.7

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 (54) hide show
  1. package/README.md +56 -0
  2. package/dist/consumer-plugin/index.d.ts +19 -4
  3. package/dist/consumer-plugin/index.d.ts.map +1 -1
  4. package/dist/consumer-plugin/index.js +214 -6
  5. package/dist/consumer-plugin/index.js.map +1 -1
  6. package/dist/manifest/static-manifest.d.ts.map +1 -1
  7. package/dist/manifest/static-manifest.js +13 -7
  8. package/dist/manifest/static-manifest.js.map +1 -1
  9. package/dist/manifest/store.js +1 -1
  10. package/dist/manifest/store.js.map +1 -1
  11. package/dist/manifest.json +13 -7
  12. package/dist/scanner/manifest-generator.d.ts +39 -0
  13. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  14. package/dist/scanner/manifest-generator.js +79 -12
  15. package/dist/scanner/manifest-generator.js.map +1 -1
  16. package/dist/smrt-knowledge.json +5 -4
  17. package/dist/vite-plugin/changes-route.d.ts.map +1 -1
  18. package/dist/vite-plugin/changes-route.js +4 -3
  19. package/dist/vite-plugin/changes-route.js.map +1 -1
  20. package/dist/vite-plugin/dev-plane-route.d.ts +2 -0
  21. package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
  22. package/dist/vite-plugin/dev-plane-route.js +8 -5
  23. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  24. package/dist/vite-plugin/events-route.d.ts.map +1 -1
  25. package/dist/vite-plugin/events-route.js +4 -3
  26. package/dist/vite-plugin/events-route.js.map +1 -1
  27. package/dist/vite-plugin/index.d.ts.map +1 -1
  28. package/dist/vite-plugin/index.js +25 -40
  29. package/dist/vite-plugin/index.js.map +1 -1
  30. package/dist/vite-plugin/resources-route.d.ts +6 -0
  31. package/dist/vite-plugin/resources-route.d.ts.map +1 -1
  32. package/dist/vite-plugin/resources-route.js +30 -7
  33. package/dist/vite-plugin/resources-route.js.map +1 -1
  34. package/dist/vite-plugin/sveltekit-config-import.d.ts +19 -0
  35. package/dist/vite-plugin/sveltekit-config-import.d.ts.map +1 -0
  36. package/dist/vite-plugin/sveltekit-config-import.js +36 -0
  37. package/dist/vite-plugin/sveltekit-config-import.js.map +1 -0
  38. package/dist/vite-plugin/sveltekit-generator.d.ts +33 -2
  39. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  40. package/dist/vite-plugin/sveltekit-generator.js +181 -65
  41. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  42. package/dist/vite-plugin/sveltekit-path.d.ts +8 -0
  43. package/dist/vite-plugin/sveltekit-path.d.ts.map +1 -0
  44. package/dist/vite-plugin/sveltekit-path.js +25 -0
  45. package/dist/vite-plugin/sveltekit-path.js.map +1 -0
  46. package/dist/vite-plugin/sveltekit-route-coordinator.d.ts +67 -0
  47. package/dist/vite-plugin/sveltekit-route-coordinator.d.ts.map +1 -0
  48. package/dist/vite-plugin/sveltekit-route-coordinator.js +321 -0
  49. package/dist/vite-plugin/sveltekit-route-coordinator.js.map +1 -0
  50. package/dist/vite-plugin/sync-apply-route.d.ts +1 -1
  51. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  52. package/dist/vite-plugin/sync-apply-route.js +4 -3
  53. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  54. package/package.json +6 -5
@@ -3,14 +3,16 @@ import { CRUD_OPERATIONS, createManifestClassNamePredicate, declaredTypeAcceptsD
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";
6
+ import { resolveSvelteKitConfigImport } from "./sveltekit-config-import.js";
6
7
  import { generateChangesRoute } from "./changes-route.js";
7
- import { generateDevPlaneRoute } from "./dev-plane-route.js";
8
+ import { generateDevPlaneRoute, willGenerateDevPlaneRoute } from "./dev-plane-route.js";
8
9
  import { generateEventsRoute } from "./events-route.js";
9
- import { generateResourcesRoute } from "./resources-route.js";
10
- import { generateSyncApplyRoute } from "./sync-apply-route.js";
10
+ import { generateResourcesRoute, reservesResourcesRoute } from "./resources-route.js";
11
+ import { canonicalSvelteKitPath } from "./sveltekit-path.js";
12
+ import { collectSyncApplyTargets, generateSyncApplyRoute } from "./sync-apply-route.js";
11
13
  import { computeWebManifestHash, isCollectionManifestClass, orderedManifestObjectEntries, resolveCollectionItemObject, resolveCollectionItemTypeName } from "./web-collections.js";
12
- import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
13
- import { isAbsolute, join, relative, sep } from "node:path";
14
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
15
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
14
16
  //#region src/vite-plugin/sveltekit-generator.ts
15
17
  /**
16
18
  * SvelteKit route auto-generation from SMRT objects
@@ -44,7 +46,7 @@ function isCollectionClass(objectDef) {
44
46
  return objectDef.extends === "SmrtCollection" || !!objectDef.extendsTypeArg;
45
47
  }
46
48
  function getRegistrationPackageName(manifest, objectDef, isLocal) {
47
- if (isLocal) return manifest.packageName ?? objectDef.packageName;
49
+ if (isLocal) return objectDef.packageName ?? manifest.packageName;
48
50
  return objectDef.packageName;
49
51
  }
50
52
  function toSingleQuotedStringLiteral(value) {
@@ -85,9 +87,12 @@ function isValidTypeIdentifier(name) {
85
87
  function resolveObjectTypeReference(projectRoot, className, objectDef, options, routeDir) {
86
88
  const simpleClassName = extractSimpleClassName(className);
87
89
  if (!objectDef || !isValidTypeIdentifier(simpleClassName)) return { typeName: "import('@happyvertical/smrt-core').SmrtObject" };
90
+ const external = !isLocalObject(projectRoot, objectDef) && objectDef.packageName;
91
+ const importPath = external ? objectDef.importPath || objectDef.packageName : getRouteTypeImportPath(projectRoot, objectDef, options, simpleClassName, routeDir);
92
+ const exportName = external ? objectDef.exportName || simpleClassName : simpleClassName;
88
93
  return {
89
94
  typeName: simpleClassName,
90
- importStatement: `import type { ${simpleClassName} } from '${isLocalObject(projectRoot, objectDef) || !objectDef.packageName ? getRouteTypeImportPath(projectRoot, objectDef, options, simpleClassName, routeDir) : objectDef.packageName}';`
95
+ importStatement: `import type { ${exportName}${exportName === simpleClassName ? "" : ` as ${simpleClassName}`} } from '${importPath}';`
91
96
  };
92
97
  }
93
98
  function getRouteTypeImportPath(projectRoot, objectDef, options, simpleClassName, routeDir) {
@@ -744,6 +749,12 @@ function findItemClassRegistryKey(className, objectDef, manifest) {
744
749
  if (!itemObject) return resolveCollectionItemTypeName(manifest, objectDef) || className;
745
750
  return Object.entries(manifest.objects).find(([, manifestObject]) => manifestObject === itemObject)?.[0] || itemObject.className;
746
751
  }
752
+ function assertCompatibleCustomActionRouteSpecs(routeSpecs) {
753
+ const [firstSpec] = routeSpecs;
754
+ if (!firstSpec) return;
755
+ const { hostType, lookupClassName, routeConfig } = firstSpec;
756
+ 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.`);
757
+ }
747
758
  function groupCustomActionRoutes(actionSpecs) {
748
759
  const groupedRoutes = /* @__PURE__ */ new Map();
749
760
  for (const { routeDir, spec } of actionSpecs) {
@@ -753,24 +764,91 @@ function groupCustomActionRoutes(actionSpecs) {
753
764
  existing.push(spec);
754
765
  groupedRoutes.set(routeDir, existing);
755
766
  }
767
+ for (const routeSpecs of groupedRoutes.values()) assertCompatibleCustomActionRouteSpecs(routeSpecs);
756
768
  return groupedRoutes;
757
769
  }
758
- /**
759
- * Generates SvelteKit API routes from manifest
760
- */
761
- async function generateSvelteKitRoutes(projectRoot, manifest, options) {
770
+ /** Reject selected identities that would write the same endpoint file. */
771
+ function assertNoCrossObjectRouteCollisions(projectRoot, manifest, options, exposureManifest, reservedRoutePaths = /* @__PURE__ */ new Set()) {
772
+ const claims = /* @__PURE__ */ new Map();
773
+ const claimPath = (routePath, routeDir, owner, writer) => {
774
+ const canonicalRoutePath = canonicalSvelteKitPath(routePath);
775
+ const prior = claims.get(canonicalRoutePath);
776
+ if (prior && (prior.owner !== owner || prior.writer !== writer)) throw new Error(`Conflicting SvelteKit route ${routeDir}: ${prior.owner} (${prior.writer}) and ${owner} (${writer}) would write the same handler`);
777
+ claims.set(canonicalRoutePath, {
778
+ owner,
779
+ writer
780
+ });
781
+ };
782
+ const claim = (routeDir, owner, writer) => claimPath(join(projectRoot, routeDir, "+server.ts"), routeDir, owner, writer);
783
+ for (const routePath of reservedRoutePaths) claimPath(resolve(routePath), relative(projectRoot, routePath), "producer knowledge", "knowledge");
784
+ for (const [className, objectDef] of orderedManifestObjectEntries(manifest)) {
785
+ if (isFrameworkBaseClass(objectDef.className, objectDef.packageName)) continue;
786
+ const routeDir = join(options.routesDir, objectDef.collection);
787
+ if (isCollectionManifestClass(manifest, objectDef)) {
788
+ const actionSpecs = resolveApiCustomActions(objectDef, exposureManifest, true).exposed.map(([actionName, actionDef]) => {
789
+ const routeConfig = resolveApiActionRouteConfig(actionName, actionDef, objectDef.decoratorConfig?.api, { kebabRoutes: options.kebabRoutes }, "collection");
790
+ return {
791
+ routeDir: join(routeDir, ...routeConfig.pathSegments),
792
+ spec: {
793
+ actionName,
794
+ hostType: "collection",
795
+ lookupClassName: findItemClassRegistryKey(className, objectDef, manifest),
796
+ routeConfig
797
+ }
798
+ };
799
+ });
800
+ for (const [actionRouteDir] of groupCustomActionRoutes(actionSpecs)) claim(actionRouteDir, className, "custom-action");
801
+ continue;
802
+ }
803
+ const actions = resolveStandardCrudActions(objectDef.decoratorConfig?.api);
804
+ if (actions.some((action) => action === "list" || action === "create")) claim(routeDir, className, "crud-collection");
805
+ if (actions.some((action) => action === "get" || action === "update" || action === "delete")) claim(join(routeDir, "[id]"), className, "crud-item");
806
+ const actionSpecs = resolveApiCustomActions(objectDef, exposureManifest, false).exposed.map(([actionName, actionDef]) => {
807
+ const routeConfig = resolveApiActionRouteConfig(actionName, actionDef, objectDef.decoratorConfig?.api, { kebabRoutes: options.kebabRoutes });
808
+ return {
809
+ routeDir: join(routeConfig.scope === "collection" ? routeDir : join(routeDir, "[id]"), ...routeConfig.pathSegments),
810
+ spec: {
811
+ actionName,
812
+ hostType: "item",
813
+ lookupClassName: className,
814
+ routeConfig
815
+ }
816
+ };
817
+ });
818
+ for (const [actionRouteDir] of groupCustomActionRoutes(actionSpecs)) claim(actionRouteDir, className, "custom-action");
819
+ }
820
+ if (collectSyncApplyTargets(manifest).length > 0) claim(join(options.routesDir, "sync", "apply"), "sync/apply", "sync-apply");
821
+ const hasAnchor = Object.values(manifest.objects).some((objectDef) => !isCollectionManifestClass(manifest, objectDef));
822
+ if (hasAnchor && options.changesRoute?.enabled !== false) claim(join(options.routesDir, "_changes"), "_changes", "changes");
823
+ if (willGenerateDevPlaneRoute(projectRoot, options)) claim(join(options.routesDir, "_dev", "[...tool]"), "_dev", "dev-plane");
824
+ if (hasAnchor && options.eventsRoute?.enabled !== false) claim(join(options.routesDir, "_events"), "_events", "events");
825
+ if (reservesResourcesRoute(projectRoot, options)) claim(join(options.routesDir, "_resources"), "_resources", "resources");
826
+ if (options.knowledge?.api?.enabled) {
827
+ const routeDir = knowledgeRouteDir(projectRoot, options);
828
+ claimPath(join(routeDir, "+server.ts"), routeDir, "knowledge", "knowledge");
829
+ }
830
+ }
831
+ async function generateSvelteKitRoutes(projectRoot, routeManifest, options, semanticManifest = routeManifest, utilityManifests = {
832
+ sync: routeManifest,
833
+ changes: routeManifest,
834
+ events: routeManifest,
835
+ eventsSemantic: semanticManifest,
836
+ clearKnowledgeRoute: true
837
+ }, registrationManifest = routeManifest, hooks = {}) {
762
838
  if (!options.enabled) return;
763
839
  console.log("[smrt] Generating SvelteKit routes...");
764
- clearGeneratedRouteFiles(join(projectRoot, options.routesDir));
765
- clearGeneratedKnowledgeRoute(projectRoot, options);
766
- await generateSmrtConfigFile(projectRoot, manifest, options);
840
+ if (options.rejectRouteCollisions) assertNoCrossObjectRouteCollisions(projectRoot, routeManifest, options, semanticManifest, utilityManifests.protectedRoutePaths);
841
+ await hooks.beforeCleanup?.();
842
+ clearGeneratedSvelteKitRouteFiles(join(projectRoot, options.routesDir), utilityManifests.protectedRoutePaths, utilityManifests.protectedRouteRoots);
843
+ if (utilityManifests.clearKnowledgeRoute !== false) clearGeneratedKnowledgeRoute(projectRoot, options);
844
+ await generateSmrtConfigFile(projectRoot, registrationManifest, options);
767
845
  const generatedRoutePaths = [];
768
846
  let generatedCount = 0;
769
847
  let skippedCollections = 0;
770
- for (const [className, objectDef] of orderedManifestObjectEntries(manifest)) {
848
+ for (const [className, objectDef] of orderedManifestObjectEntries(routeManifest)) {
771
849
  if (isFrameworkBaseClass(objectDef.className, objectDef.packageName)) continue;
772
- if (isCollectionManifestClass(manifest, objectDef)) {
773
- const collectionRoutePaths = await generateCollectionRoutesForObject(projectRoot, className, objectDef, manifest, options);
850
+ if (isCollectionManifestClass(semanticManifest, objectDef)) {
851
+ const collectionRoutePaths = await generateCollectionRoutesForObject(projectRoot, className, objectDef, semanticManifest, options);
774
852
  generatedRoutePaths.push(...collectionRoutePaths);
775
853
  if (collectionRoutePaths.length > 0) generatedCount++;
776
854
  else {
@@ -779,35 +857,57 @@ async function generateSvelteKitRoutes(projectRoot, manifest, options) {
779
857
  }
780
858
  continue;
781
859
  }
782
- generatedRoutePaths.push(...await generateRoutesForObject(projectRoot, className, objectDef, manifest, options));
860
+ generatedRoutePaths.push(...await generateRoutesForObject(projectRoot, className, objectDef, semanticManifest, options));
783
861
  generatedCount++;
784
862
  }
785
863
  if (options.knowledge?.api?.enabled) generatedRoutePaths.push(generateKnowledgeRoute(projectRoot, options));
786
- if (generateSyncApplyRoute(projectRoot, manifest, options)) generatedRoutePaths.push(join(projectRoot, options.routesDir, "sync", "apply", "+server.ts"));
787
- if (generateChangesRoute(projectRoot, manifest, options)) generatedRoutePaths.push(join(projectRoot, options.routesDir, "_changes", "+server.ts"));
864
+ if (generateSyncApplyRoute(projectRoot, utilityManifests.sync, options)) generatedRoutePaths.push(join(projectRoot, options.routesDir, "sync", "apply", "+server.ts"));
865
+ if (generateChangesRoute(projectRoot, utilityManifests.changes, options)) generatedRoutePaths.push(join(projectRoot, options.routesDir, "_changes", "+server.ts"));
788
866
  if (generateDevPlaneRoute(projectRoot, options)) generatedRoutePaths.push(join(projectRoot, options.routesDir, "_dev", "[...tool]", "+server.ts"));
789
- if (generateEventsRoute(projectRoot, manifest, options, computeWebManifestHash(manifest))) generatedRoutePaths.push(join(projectRoot, options.routesDir, "_events", "+server.ts"));
867
+ if (generateEventsRoute(projectRoot, utilityManifests.events, options, computeWebManifestHash(utilityManifests.eventsSemantic))) generatedRoutePaths.push(join(projectRoot, options.routesDir, "_events", "+server.ts"));
790
868
  if (generateResourcesRoute(projectRoot, options)) generatedRoutePaths.push(join(projectRoot, options.routesDir, "_resources", "+server.ts"));
791
- updateGitignore(projectRoot, generatedRoutePaths);
869
+ updateGitignore(projectRoot, generatedRoutePaths, join(projectRoot, svelteKitRouteRoot(options.routesDir)));
792
870
  const skippedMsg = skippedCollections > 0 ? ` (skipped ${skippedCollections} collection classes)` : "";
793
871
  console.log(`[smrt] Generated routes for ${generatedCount} SMRT objects${skippedMsg}`);
794
872
  }
795
- function clearGeneratedRouteFiles(routesRoot) {
873
+ /** Remove only SMRT header-owned SvelteKit handlers below one route root. */
874
+ function clearGeneratedSvelteKitRouteFiles(routesRoot, excludedPaths = /* @__PURE__ */ new Set(), protectedRouteRoots = /* @__PURE__ */ new Set(), visitedRoots = /* @__PURE__ */ new Set()) {
875
+ const canonicalRoot = canonicalSvelteKitPath(routesRoot);
876
+ if (protectedRouteRoots.has(canonicalRoot) || visitedRoots.has(canonicalRoot)) return;
796
877
  if (!existsSync(routesRoot)) return;
878
+ visitedRoots.add(canonicalRoot);
797
879
  for (const entry of readdirSync(routesRoot, { withFileTypes: true })) {
798
880
  const entryPath = join(routesRoot, entry.name);
799
- if (entry.isDirectory()) {
800
- clearGeneratedRouteFiles(entryPath);
881
+ if (isSvelteKitRouteDirectory(entryPath, entry)) {
882
+ clearGeneratedSvelteKitRouteFiles(entryPath, excludedPaths, protectedRouteRoots, visitedRoots);
801
883
  continue;
802
884
  }
803
885
  if (!entry.isFile() || entry.name !== "+server.ts") continue;
886
+ if (excludedPaths.has(canonicalSvelteKitPath(entryPath))) continue;
804
887
  if (readFileSync(entryPath, "utf-8").startsWith("// Auto-generated by @smrt/core vite plugin")) unlinkSync(entryPath);
805
888
  }
806
889
  }
890
+ function isSvelteKitRouteDirectory(entryPath, entry) {
891
+ if (entry.isDirectory()) return true;
892
+ if (!entry.isSymbolicLink?.()) return false;
893
+ try {
894
+ return statSync(entryPath).isDirectory();
895
+ } catch {
896
+ return false;
897
+ }
898
+ }
899
+ /** Refresh the managed ignore block after an externally coordinated cleanup. */
900
+ function reconcileSvelteKitRouteGitignore(projectRoot, routesDir) {
901
+ updateGitignore(projectRoot, [], join(projectRoot, svelteKitRouteRoot(routesDir)));
902
+ }
807
903
  function knowledgeRouteDir(projectRoot, options) {
808
904
  const basePath = String(options.knowledge?.api?.basePath || "/__smrt/knowledge");
809
905
  return join(projectRoot, svelteKitRouteRoot(options.routesDir), ...basePath.split("/").map((segment) => segment.trim()).filter(Boolean));
810
906
  }
907
+ /** Exact generated knowledge handler path for coordinator-owned cleanup. */
908
+ function knowledgeRoutePath(projectRoot, options) {
909
+ return join(knowledgeRouteDir(projectRoot, options), "+server.ts");
910
+ }
811
911
  function svelteKitRouteRoot(routesDir) {
812
912
  const normalized = routesDir.replaceAll("\\", "/").replace(/\/+$/, "");
813
913
  const markerIndex = normalized.indexOf("/routes");
@@ -816,7 +916,7 @@ function svelteKitRouteRoot(routesDir) {
816
916
  return "src/routes";
817
917
  }
818
918
  function clearGeneratedKnowledgeRoute(projectRoot, options) {
819
- const routePath = join(knowledgeRouteDir(projectRoot, options), "+server.ts");
919
+ const routePath = knowledgeRoutePath(projectRoot, options);
820
920
  if (!existsSync(routePath)) return;
821
921
  if (readFileSync(routePath, "utf-8").startsWith("// Auto-generated by @smrt/core vite plugin")) unlinkSync(routePath);
822
922
  }
@@ -847,16 +947,17 @@ async function generateRegistrationFile(projectRoot, manifest, options) {
847
947
  const registrationBindings = buildRegistrationBindings(manifest);
848
948
  for (const [className, objectDef] of Object.entries(manifest.objects)) if (isLocalObject(projectRoot, objectDef)) localObjects.push([className, objectDef]);
849
949
  else if (objectDef.packageName) {
850
- const packageEntry = packageObjects.get(objectDef.packageName) || {
950
+ const externalImportPath = objectDef.importPath || objectDef.packageName;
951
+ const packageEntry = packageObjects.get(externalImportPath) || {
851
952
  objects: [],
852
953
  hasCollectionImport: false
853
954
  };
854
955
  if (isCollectionManifestClass(manifest, objectDef)) packageEntry.hasCollectionImport = true;
855
956
  else packageEntry.objects.push({
856
- simpleName: extractSimpleClassName(className),
957
+ simpleName: objectDef.exportName || extractSimpleClassName(className),
857
958
  bindingName: registrationBindings.get(className) || extractSimpleClassName(className)
858
959
  });
859
- packageObjects.set(objectDef.packageName, packageEntry);
960
+ packageObjects.set(externalImportPath, packageEntry);
860
961
  } else localObjects.push([className, objectDef]);
861
962
  const localNamedImports = /* @__PURE__ */ new Map();
862
963
  const localSideEffectImports = /* @__PURE__ */ new Set();
@@ -891,11 +992,12 @@ async function generateRegistrationFile(projectRoot, manifest, options) {
891
992
  return imports;
892
993
  }).join("\n"), localImports].filter(Boolean).join("\n");
893
994
  const externalRuntimeDependencies = (manifest.smrtDependencies || []).filter((dependency) => dependency !== "@happyvertical/smrt-core");
995
+ const consumerRegistrationPaths = options.consumerRegistrationPaths ?? (externalRuntimeDependencies.length > 0 ? [join(projectRoot, ".smrt", "register.js")] : []);
894
996
  let consumerRegistrationImport = "";
895
- if (externalRuntimeDependencies.length > 0) {
896
- const consumerRegistrationPath = relative(configDir, join(projectRoot, ".smrt", "register.js")).replace(/\\/g, "/");
897
- consumerRegistrationImport = `import '${consumerRegistrationPath.startsWith(".") ? consumerRegistrationPath : `./${consumerRegistrationPath}`}';`;
898
- }
997
+ if (consumerRegistrationPaths.length > 0) consumerRegistrationImport = [...new Set(consumerRegistrationPaths)].sort().map((registerPath) => {
998
+ const consumerRegistrationPath = relative(configDir, registerPath).replace(/\\/g, "/");
999
+ return `import '${consumerRegistrationPath.startsWith(".") ? consumerRegistrationPath : `./${consumerRegistrationPath}`}';`;
1000
+ }).join("\n");
899
1001
  /**
900
1002
  * Strip machine specifics from an object definition before it is embedded in
901
1003
  * generated source.
@@ -1154,7 +1256,7 @@ export async function getCollection<
1154
1256
  /**
1155
1257
  * Generates route files for a single SMRT object
1156
1258
  */
1157
- async function generateRoutesForObject(projectRoot, className, objectDef, manifest, options) {
1259
+ async function generateRoutesForObject(projectRoot, className, objectDef, semanticManifest, options) {
1158
1260
  const collectionName = objectDef.collection;
1159
1261
  const routeDir = join(projectRoot, options.routesDir, collectionName);
1160
1262
  const generatedRoutePaths = [];
@@ -1165,14 +1267,14 @@ async function generateRoutesForObject(projectRoot, className, objectDef, manife
1165
1267
  }
1166
1268
  const includedActions = resolveStandardCrudActions(apiConfig);
1167
1269
  if (includedActions.includes("list") || includedActions.includes("create")) {
1168
- const collectionRoute = generateCollectionRouteTemplate(projectRoot, className, objectDef, manifest, includedActions, options, routeDir);
1270
+ const collectionRoute = generateCollectionRouteTemplate(projectRoot, className, objectDef, semanticManifest, includedActions, options, routeDir);
1169
1271
  generatedRoutePaths.push(writeRoute(routeDir, "+server.ts", collectionRoute));
1170
1272
  }
1171
1273
  if (includedActions.includes("get") || includedActions.includes("update") || includedActions.includes("delete")) {
1172
- const itemRoute = generateItemRouteTemplate(projectRoot, className, objectDef, manifest, includedActions, options, join(routeDir, "[id]"));
1274
+ const itemRoute = generateItemRouteTemplate(projectRoot, className, objectDef, semanticManifest, includedActions, options, join(routeDir, "[id]"));
1173
1275
  generatedRoutePaths.push(writeRoute(join(routeDir, "[id]"), "+server.ts", itemRoute));
1174
1276
  }
1175
- const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef, manifest, false);
1277
+ const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef, semanticManifest, false);
1176
1278
  warnUnhostedActions(className, rejected);
1177
1279
  const actionSpecs = [];
1178
1280
  for (const [actionName, actionDef] of customActions) {
@@ -1193,12 +1295,12 @@ async function generateRoutesForObject(projectRoot, className, objectDef, manife
1193
1295
  });
1194
1296
  }
1195
1297
  for (const [actionRouteDir, routeSpecs] of groupCustomActionRoutes(actionSpecs)) {
1196
- const actionRoute = generateActionRouteTemplate(projectRoot, actionRouteDir, routeSpecs, objectDef, manifest, options);
1298
+ const actionRoute = generateActionRouteTemplate(projectRoot, actionRouteDir, routeSpecs, objectDef, semanticManifest, options);
1197
1299
  generatedRoutePaths.push(writeRoute(actionRouteDir, "+server.ts", actionRoute));
1198
1300
  }
1199
1301
  return generatedRoutePaths;
1200
1302
  }
1201
- async function generateCollectionRoutesForObject(projectRoot, className, objectDef, manifest, options) {
1303
+ async function generateCollectionRoutesForObject(projectRoot, className, objectDef, semanticManifest, options) {
1202
1304
  const generatedRoutePaths = [];
1203
1305
  const apiConfig = objectDef.decoratorConfig?.api;
1204
1306
  if (apiConfig === false) {
@@ -1206,9 +1308,9 @@ async function generateCollectionRoutesForObject(projectRoot, className, objectD
1206
1308
  return generatedRoutePaths;
1207
1309
  }
1208
1310
  const routeDir = join(projectRoot, options.routesDir, objectDef.collection);
1209
- const lookupClassName = findItemClassRegistryKey(className, objectDef, manifest);
1210
- const lookupObjectDef = findObjectDefByRegistryKey(manifest, lookupClassName);
1211
- const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef, manifest, true);
1311
+ const lookupClassName = findItemClassRegistryKey(className, objectDef, semanticManifest);
1312
+ const lookupObjectDef = findObjectDefByRegistryKey(semanticManifest, lookupClassName);
1313
+ const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef, semanticManifest, true);
1212
1314
  warnUnhostedActions(className, rejected);
1213
1315
  if (customActions.length === 0) return generatedRoutePaths;
1214
1316
  const actionSpecs = [];
@@ -1229,7 +1331,7 @@ async function generateCollectionRoutesForObject(projectRoot, className, objectD
1229
1331
  });
1230
1332
  }
1231
1333
  for (const [actionRouteDir, routeSpecs] of groupCustomActionRoutes(actionSpecs)) {
1232
- const actionRoute = generateActionRouteTemplate(projectRoot, actionRouteDir, routeSpecs, objectDef, manifest, options);
1334
+ const actionRoute = generateActionRouteTemplate(projectRoot, actionRouteDir, routeSpecs, objectDef, semanticManifest, options);
1233
1335
  generatedRoutePaths.push(writeRoute(actionRouteDir, "+server.ts", actionRoute));
1234
1336
  }
1235
1337
  return generatedRoutePaths;
@@ -1514,25 +1616,26 @@ function smrtRouteErrorResponse(cause: unknown) {
1514
1616
  }
1515
1617
  `;
1516
1618
  }
1517
- function generateCollectionRouteTemplate(projectRoot, className, objectDef, manifest, includedActions, options, routeDir) {
1619
+ function generateCollectionRouteTemplate(projectRoot, className, objectDef, semanticManifest, includedActions, options, routeDir) {
1518
1620
  const hasGet = includedActions.includes("list");
1519
1621
  const hasPost = includedActions.includes("create");
1520
1622
  const modelType = resolveObjectTypeReference(projectRoot, className, objectDef, options, routeDir);
1521
1623
  const serializers = resolveStandardRouteSerializers(objectDef.decoratorConfig?.api);
1522
1624
  const serializerImports = serializers.importStatements.join("\n");
1523
1625
  const listUsesSerializer = !!serializers.listItemSerializerName;
1524
- const listUsesPermissionScopedBody = collectReadPermissionFields(objectDef, manifest).length > 0;
1626
+ const listUsesPermissionScopedBody = collectReadPermissionFields(objectDef, semanticManifest).length > 0;
1525
1627
  const listUsesBodyHash = listUsesSerializer || listUsesPermissionScopedBody;
1526
- const webManifestHash = computeWebManifestHash(manifest);
1628
+ const webManifestHash = computeWebManifestHash(semanticManifest);
1629
+ const configImport = resolveSvelteKitConfigImport(projectRoot, routeDir, options);
1527
1630
  const imports = `${AUTO_GENERATED_ROUTE_HEADER}
1528
1631
  // DO NOT EDIT - changes will be overwritten
1529
1632
 
1530
1633
  import { error${hasPost ? ", json" : ""} } from '@sveltejs/kit';
1531
- ${serializerImports ? `${serializerImports}\n` : ""}import { getCollection } from '$lib/server/smrt';
1634
+ ${serializerImports ? `${serializerImports}\n` : ""}import { getCollection } from '${configImport}';
1532
1635
  ${hasPost ? "import { normalizeTypedHttpError } from '@happyvertical/smrt-core';\n" : ""}
1533
1636
  ${modelType.importStatement ? `${modelType.importStatement}\n` : ""}import type { RequestHandler } from './$types';
1534
1637
  // Note: ${className} is auto-registered by the Vite plugin scanner
1535
- ${generateAuthGuardHelper(objectDef, manifest)}${needsRouteTenantContext(objectDef) ? generateTenantContextHelper(usesPrincipalContext(objectDef), isTenantScoped(objectDef)) : ""}${hasPost ? generateWritablePolicyHelper(objectDef) : ""}${hasPost ? generateTypedRouteErrorHelper() : ""}${hasGet ? generateListBoundsHelper(objectDef) : ""}${hasGet ? generateConditionalGetRouteHelper(objectDef.decoratorConfig?.api, {
1638
+ ${generateAuthGuardHelper(objectDef, semanticManifest)}${needsRouteTenantContext(objectDef) ? generateTenantContextHelper(usesPrincipalContext(objectDef), isTenantScoped(objectDef)) : ""}${hasPost ? generateWritablePolicyHelper(objectDef) : ""}${hasPost ? generateTypedRouteErrorHelper() : ""}${hasGet ? generateListBoundsHelper(objectDef) : ""}${hasGet ? generateConditionalGetRouteHelper(objectDef.decoratorConfig?.api, {
1536
1639
  tenantScoped: isTenantScoped(objectDef),
1537
1640
  permissionScoped: listUsesPermissionScopedBody,
1538
1641
  modelName: className,
@@ -1638,7 +1741,7 @@ function generateClassNotRegisteredError(className) {
1638
1741
  /**
1639
1742
  * Generates item route template (GET, PUT, DELETE)
1640
1743
  */
1641
- function generateItemRouteTemplate(projectRoot, className, objectDef, manifest, includedActions, options, routeDir) {
1744
+ function generateItemRouteTemplate(projectRoot, className, objectDef, semanticManifest, includedActions, options, routeDir) {
1642
1745
  const hasGet = includedActions.includes("get");
1643
1746
  const hasPut = includedActions.includes("update");
1644
1747
  const hasDelete = includedActions.includes("delete");
@@ -1647,17 +1750,18 @@ function generateItemRouteTemplate(projectRoot, className, objectDef, manifest,
1647
1750
  const serializers = resolveStandardRouteSerializers(objectDef.decoratorConfig?.api);
1648
1751
  const serializerImports = serializers.importStatements.join("\n");
1649
1752
  const getUsesSerializer = !!serializers.itemSerializerName;
1650
- const getUsesPermissionScopedBody = collectReadPermissionFields(objectDef, manifest).length > 0;
1753
+ const getUsesPermissionScopedBody = collectReadPermissionFields(objectDef, semanticManifest).length > 0;
1651
1754
  const getUsesBodyHash = getUsesSerializer || getUsesPermissionScopedBody;
1652
- const webManifestHash = computeWebManifestHash(manifest);
1755
+ const webManifestHash = computeWebManifestHash(semanticManifest);
1756
+ const configImport = resolveSvelteKitConfigImport(projectRoot, routeDir, options);
1653
1757
  const imports = `${AUTO_GENERATED_ROUTE_HEADER}
1654
1758
  // DO NOT EDIT - changes will be overwritten
1655
1759
 
1656
1760
  import { error${hasPut || hasDelete ? ", json" : ""} } from '@sveltejs/kit';
1657
- ${serializerImports ? `${serializerImports}\n` : ""}import { getCollection } from '$lib/server/smrt';
1761
+ ${serializerImports ? `${serializerImports}\n` : ""}import { getCollection } from '${configImport}';
1658
1762
  ${hasPut || hasDelete ? "import { normalizeTypedHttpError } from '@happyvertical/smrt-core';\n" : ""}
1659
1763
  ${modelType.importStatement ? `${modelType.importStatement}\n` : ""}import type { RequestHandler } from './$types';
1660
- ${generateAuthGuardHelper(objectDef, manifest)}${needsRouteTenantContext(objectDef) ? generateTenantContextHelper(usesPrincipalContext(objectDef), isTenantScoped(objectDef)) : ""}${hasPut ? generateWritablePolicyHelper(objectDef) : ""}${hasPut || hasDelete ? generateTypedRouteErrorHelper() : ""}${hasGet ? generateConditionalGetRouteHelper(objectDef.decoratorConfig?.api, {
1764
+ ${generateAuthGuardHelper(objectDef, semanticManifest)}${needsRouteTenantContext(objectDef) ? generateTenantContextHelper(usesPrincipalContext(objectDef), isTenantScoped(objectDef)) : ""}${hasPut ? generateWritablePolicyHelper(objectDef) : ""}${hasPut || hasDelete ? generateTypedRouteErrorHelper() : ""}${hasGet ? generateConditionalGetRouteHelper(objectDef.decoratorConfig?.api, {
1661
1765
  tenantScoped: isTenantScoped(objectDef),
1662
1766
  permissionScoped: getUsesPermissionScopedBody,
1663
1767
  modelName: className,
@@ -1739,14 +1843,14 @@ ${generateNotFoundError(className)}
1739
1843
  /**
1740
1844
  * Generates custom action route template
1741
1845
  */
1742
- function generateActionRouteTemplate(projectRoot, routeDir, routeSpecs, objectDef, manifest, options) {
1846
+ function generateActionRouteTemplate(projectRoot, routeDir, routeSpecs, objectDef, semanticManifest, options) {
1743
1847
  if (routeSpecs.length === 0) throw new Error("Cannot generate a custom action route without handlers");
1744
1848
  const [firstSpec] = routeSpecs;
1745
1849
  const { lookupClassName, routeConfig, hostType } = firstSpec;
1850
+ assertCompatibleCustomActionRouteSpecs(routeSpecs);
1746
1851
  const lookupModelType = resolveObjectTypeReference(projectRoot, lookupClassName, firstSpec.lookupObjectDef, options, routeDir);
1747
1852
  const hostModelType = resolveObjectTypeReference(projectRoot, firstSpec.hostClassName, objectDef, options, routeDir);
1748
1853
  const typeImports = mergeImportStatements([lookupModelType.importStatement, hostModelType.importStatement]);
1749
- 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.`);
1750
1854
  const decoderImports = /* @__PURE__ */ new Set();
1751
1855
  for (const spec of routeSpecs) {
1752
1856
  if (hasSingleOptionsParameter(spec.actionDef)) continue;
@@ -1755,14 +1859,16 @@ function generateActionRouteTemplate(projectRoot, routeDir, routeSpecs, objectDe
1755
1859
  if (decoder) decoderImports.add(decoder);
1756
1860
  }
1757
1861
  }
1862
+ const coreImports = [
1863
+ "normalizeCustomActionFailure",
1864
+ "normalizeTypedHttpError",
1865
+ ...[...decoderImports].sort()
1866
+ ].join(", ");
1867
+ const configImport = resolveSvelteKitConfigImport(projectRoot, routeDir, options);
1758
1868
  const importBlock = [
1759
1869
  "import { error, json } from '@sveltejs/kit';",
1760
- `import { ${[
1761
- "normalizeCustomActionFailure",
1762
- "normalizeTypedHttpError",
1763
- ...[...decoderImports].sort()
1764
- ].join(", ")} } from '@happyvertical/smrt-core';`,
1765
- hostType === "collection" || routeConfig.scope !== "collection" ? "import { getCollection } from '$lib/server/smrt';" : "import { ObjectRegistry } from '@happyvertical/smrt-core';",
1870
+ `import { ${coreImports} } from '@happyvertical/smrt-core';`,
1871
+ hostType === "collection" || routeConfig.scope !== "collection" ? `import { getCollection } from '${configImport}';` : [`import '${configImport}';`, "import { ObjectRegistry } from '@happyvertical/smrt-core';"].join("\n"),
1766
1872
  typeImports,
1767
1873
  "import type { RequestHandler } from './$types';"
1768
1874
  ].filter(Boolean).join("\n");
@@ -1774,7 +1880,7 @@ function generateActionRouteTemplate(projectRoot, routeDir, routeSpecs, objectDe
1774
1880
  // DO NOT EDIT - changes will be overwritten
1775
1881
 
1776
1882
  ${importBlock}
1777
- ${generateAuthGuardHelper(objectDef, manifest)}${needsTenantContext ? generateTenantContextHelper(principalContext, tenantScoped) : ""}
1883
+ ${generateAuthGuardHelper(objectDef, semanticManifest)}${needsTenantContext ? generateTenantContextHelper(principalContext, tenantScoped) : ""}
1778
1884
  ${generateTypedRouteErrorHelper()}
1779
1885
  ${handlers}`;
1780
1886
  }
@@ -2001,17 +2107,27 @@ function rolesFrom(value: unknown): string[] {
2001
2107
  /**
2002
2108
  * Updates .gitignore to exclude auto-generated routes
2003
2109
  */
2004
- function updateGitignore(projectRoot, generatedRoutePaths) {
2110
+ function updateGitignore(projectRoot, generatedRoutePaths, routeRoot) {
2005
2111
  const gitignorePath = join(projectRoot, ".gitignore");
2006
2112
  let gitignoreContent = "";
2007
2113
  if (existsSync(gitignorePath)) gitignoreContent = readFileSync(gitignorePath, "utf-8");
2008
- const generatedPaths = [...new Set(generatedRoutePaths.map((path) => gitignorePatternForPath(relative(projectRoot, path))))].sort((a, b) => a.localeCompare(b));
2114
+ const generatedPaths = [...new Set([...generatedRoutePaths, ...findGeneratedRouteFiles(routeRoot)].map((path) => gitignorePatternForPath(relative(projectRoot, path))))].sort((a, b) => a.localeCompare(b));
2009
2115
  const updatedContent = updateGeneratedRouteIgnoreBlock(gitignoreContent, generatedPaths);
2010
2116
  if (updatedContent !== gitignoreContent) {
2011
2117
  writeFileSync(gitignorePath, updatedContent, "utf-8");
2012
2118
  console.log("[smrt] Updated .gitignore with generated route paths");
2013
2119
  }
2014
2120
  }
2121
+ function findGeneratedRouteFiles(routeRoot) {
2122
+ if (!existsSync(routeRoot)) return [];
2123
+ const files = [];
2124
+ for (const entry of readdirSync(routeRoot, { withFileTypes: true })) {
2125
+ const path = join(routeRoot, entry.name);
2126
+ if (entry.isDirectory()) files.push(...findGeneratedRouteFiles(path));
2127
+ else if (entry.isFile() && entry.name === "+server.ts" && readFileSync(path, "utf-8").startsWith("// Auto-generated by @smrt/core vite plugin")) files.push(path);
2128
+ }
2129
+ return files;
2130
+ }
2015
2131
  function gitignorePatternForPath(path) {
2016
2132
  return path.replaceAll("\\", "/").replaceAll(/([*?[\]\\!#])/g, "\\$1");
2017
2133
  }
@@ -2064,6 +2180,6 @@ function updateGeneratedRouteIgnoreBlock(gitignoreContent, generatedPaths) {
2064
2180
  return `${updatedContent}${managedBlock.join("\n")}\n`;
2065
2181
  }
2066
2182
  //#endregion
2067
- export { findCliApiCoherenceViolations, generateSvelteKitRoutes, methodNameToKebab, resolveApiActionRouteConfig, resolveApiActionSet, resolveApiCustomActions, validateCliIncludeAgainstApi };
2183
+ export { assertNoCrossObjectRouteCollisions, clearGeneratedSvelteKitRouteFiles, findCliApiCoherenceViolations, generateSvelteKitRoutes, knowledgeRoutePath, methodNameToKebab, reconcileSvelteKitRouteGitignore, resolveApiActionRouteConfig, resolveApiActionSet, resolveApiCustomActions, validateCliIncludeAgainstApi };
2068
2184
 
2069
2185
  //# sourceMappingURL=sveltekit-generator.js.map