@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.
- package/README.md +56 -0
- package/dist/consumer-plugin/index.d.ts +19 -4
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +214 -6
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +13 -7
- 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 +13 -7
- package/dist/scanner/manifest-generator.d.ts +39 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +79 -12
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/smrt-knowledge.json +5 -4
- package/dist/vite-plugin/changes-route.d.ts.map +1 -1
- package/dist/vite-plugin/changes-route.js +4 -3
- package/dist/vite-plugin/changes-route.js.map +1 -1
- package/dist/vite-plugin/dev-plane-route.d.ts +2 -0
- package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
- package/dist/vite-plugin/dev-plane-route.js +8 -5
- package/dist/vite-plugin/dev-plane-route.js.map +1 -1
- package/dist/vite-plugin/events-route.d.ts.map +1 -1
- package/dist/vite-plugin/events-route.js +4 -3
- package/dist/vite-plugin/events-route.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +25 -40
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/resources-route.d.ts +6 -0
- package/dist/vite-plugin/resources-route.d.ts.map +1 -1
- package/dist/vite-plugin/resources-route.js +30 -7
- package/dist/vite-plugin/resources-route.js.map +1 -1
- package/dist/vite-plugin/sveltekit-config-import.d.ts +19 -0
- package/dist/vite-plugin/sveltekit-config-import.d.ts.map +1 -0
- package/dist/vite-plugin/sveltekit-config-import.js +36 -0
- package/dist/vite-plugin/sveltekit-config-import.js.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts +33 -2
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +181 -65
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sveltekit-path.d.ts +8 -0
- package/dist/vite-plugin/sveltekit-path.d.ts.map +1 -0
- package/dist/vite-plugin/sveltekit-path.js +25 -0
- package/dist/vite-plugin/sveltekit-path.js.map +1 -0
- package/dist/vite-plugin/sveltekit-route-coordinator.d.ts +67 -0
- package/dist/vite-plugin/sveltekit-route-coordinator.d.ts.map +1 -0
- package/dist/vite-plugin/sveltekit-route-coordinator.js +321 -0
- package/dist/vite-plugin/sveltekit-route-coordinator.js.map +1 -0
- package/dist/vite-plugin/sync-apply-route.d.ts +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +4 -3
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- 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 {
|
|
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
|
|
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 { ${
|
|
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
|
-
|
|
760
|
-
*/
|
|
761
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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(
|
|
848
|
+
for (const [className, objectDef] of orderedManifestObjectEntries(routeManifest)) {
|
|
771
849
|
if (isFrameworkBaseClass(objectDef.className, objectDef.packageName)) continue;
|
|
772
|
-
if (isCollectionManifestClass(
|
|
773
|
-
const collectionRoutePaths = await generateCollectionRoutesForObject(projectRoot, className, objectDef,
|
|
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,
|
|
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,
|
|
787
|
-
if (generateChangesRoute(projectRoot,
|
|
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,
|
|
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
|
-
|
|
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
|
|
800
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
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 (
|
|
896
|
-
const consumerRegistrationPath = relative(configDir,
|
|
897
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1210
|
-
const lookupObjectDef = findObjectDefByRegistryKey(
|
|
1211
|
-
const { exposed: customActions, rejected } = resolveApiCustomActions(objectDef,
|
|
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,
|
|
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,
|
|
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,
|
|
1626
|
+
const listUsesPermissionScopedBody = collectReadPermissionFields(objectDef, semanticManifest).length > 0;
|
|
1525
1627
|
const listUsesBodyHash = listUsesSerializer || listUsesPermissionScopedBody;
|
|
1526
|
-
const webManifestHash = computeWebManifestHash(
|
|
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 '$
|
|
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,
|
|
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,
|
|
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,
|
|
1753
|
+
const getUsesPermissionScopedBody = collectReadPermissionFields(objectDef, semanticManifest).length > 0;
|
|
1651
1754
|
const getUsesBodyHash = getUsesSerializer || getUsesPermissionScopedBody;
|
|
1652
|
-
const webManifestHash = computeWebManifestHash(
|
|
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 '$
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|