@happyvertical/smrt-core 0.55.9 → 0.55.11
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 +2 -0
- package/agents/build-knowledge.md +40 -0
- package/agents/cookbook-engine.md +102 -0
- package/agents/cookbook.md +63 -0
- package/dist/consumer-plugin/artifact-publication.js +3 -3
- package/dist/consumer-plugin/artifact-publication.js.map +1 -1
- package/dist/consumer-plugin/hosted-sti-collections.d.ts +10 -0
- package/dist/consumer-plugin/hosted-sti-collections.d.ts.map +1 -0
- package/dist/consumer-plugin/hosted-sti-collections.js +92 -0
- package/dist/consumer-plugin/hosted-sti-collections.js.map +1 -0
- package/dist/consumer-plugin/index.d.ts +1 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +76 -51
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/cookbook/engine/commands.d.ts +37 -0
- package/dist/cookbook/engine/commands.d.ts.map +1 -0
- package/dist/cookbook/engine/commands.js +275 -0
- package/dist/cookbook/engine/commands.js.map +1 -0
- package/dist/cookbook/engine/defaults.d.ts +10 -0
- package/dist/cookbook/engine/defaults.d.ts.map +1 -0
- package/dist/cookbook/engine/defaults.js +49 -0
- package/dist/cookbook/engine/defaults.js.map +1 -0
- package/dist/cookbook/engine/document.d.ts +66 -0
- package/dist/cookbook/engine/document.d.ts.map +1 -0
- package/dist/cookbook/engine/document.js +357 -0
- package/dist/cookbook/engine/document.js.map +1 -0
- package/dist/cookbook/engine/engine.d.ts +49 -0
- package/dist/cookbook/engine/engine.d.ts.map +1 -0
- package/dist/cookbook/engine/engine.js +399 -0
- package/dist/cookbook/engine/engine.js.map +1 -0
- package/dist/cookbook/engine/index.d.ts +14 -0
- package/dist/cookbook/engine/index.d.ts.map +1 -0
- package/dist/cookbook/engine/index.js +8 -0
- package/dist/cookbook/engine/nav.d.ts +45 -0
- package/dist/cookbook/engine/nav.d.ts.map +1 -0
- package/dist/cookbook/engine/nav.js +191 -0
- package/dist/cookbook/engine/nav.js.map +1 -0
- package/dist/cookbook/engine/schemas.d.ts +29 -0
- package/dist/cookbook/engine/schemas.d.ts.map +1 -0
- package/dist/cookbook/engine/schemas.js +363 -0
- package/dist/cookbook/engine/schemas.js.map +1 -0
- package/dist/cookbook/engine/types.d.ts +331 -0
- package/dist/cookbook/engine/types.d.ts.map +1 -0
- package/dist/cookbook/engine/types.js +10 -0
- package/dist/cookbook/engine/types.js.map +1 -0
- package/dist/cookbook/engine.d.ts +2 -0
- package/dist/cookbook/engine.d.ts.map +1 -0
- package/dist/cookbook/engine.js +9 -0
- package/dist/cookbook/index.d.ts +6 -0
- package/dist/cookbook/index.d.ts.map +1 -0
- package/dist/cookbook/v1.schema.json +131 -0
- package/dist/cookbook/validate.d.ts +37 -0
- package/dist/cookbook/validate.d.ts.map +1 -0
- package/dist/cookbook/validate.js +172 -0
- package/dist/cookbook/validate.js.map +1 -0
- package/dist/cookbook.d.ts +7 -0
- package/dist/cookbook.d.ts.map +1 -0
- package/dist/cookbook.js +2 -0
- package/dist/manifest/browser-capability.generated.d.ts.map +1 -1
- package/dist/manifest/browser-capability.generated.js +48 -2
- package/dist/manifest/browser-capability.generated.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- 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 +1 -1
- package/dist/prebuild/index.js +13 -13
- package/dist/prebuild/index.js.map +1 -1
- package/dist/smrt-knowledge.json +23 -7
- package/dist/vite-plugin/web-collections.d.ts +5 -0
- 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/dist/vite-preset/cookbook-exposure.d.ts +11 -0
- package/dist/vite-preset/cookbook-exposure.d.ts.map +1 -0
- package/dist/vite-preset/cookbook-exposure.js +95 -0
- package/dist/vite-preset/cookbook-exposure.js.map +1 -0
- package/dist/vite-preset/index.d.ts +9 -0
- package/dist/vite-preset/index.d.ts.map +1 -1
- package/dist/vite-preset/index.js +17 -7
- package/dist/vite-preset/index.js.map +1 -1
- package/package.json +18 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-collections.js","names":[],"sources":["../../src/vite-plugin/web-collections.ts"],"sourcesContent":["/**\n * Manifest → web collection selection (single source of truth).\n *\n * The browser client data runtime (`@happyvertical/smrt-web`, #1761) consumes\n * one typed collection definition per API-exposed REST collection. Four\n * co-managed emission surfaces need the SAME selection and field rules, or the\n * emitted runtime values and their declared types drift apart:\n *\n * - the `\\0smrt:web` runtime virtual module (JSON literal — {@link generateWebModule})\n * - the `@happyvertical/smrt-virt-web` ambient d.ts (vite-plugin)\n * - the physical `@smrt/web` d.ts (prebuild, for `tsc`-only consumers)\n * - the dependency-free type mirror in `@happyvertical/smrt-web`\n *\n * The three generated sites import {@link selectWebCollectionEntries} from\n * here so the value emission and the type emission can never disagree. The hand-written mirror\n * is deliberately smrt-dependency-free, so its copy cannot import these types;\n * keep it textually in sync when the shape here changes. The per-collection SHAPE\n * (name/objectRef/className/endpoint/idField/actions/fields/relationships) is built by the\n * ONE {@link buildWebCollectionDefinition}, shared by the runtime emission AND\n * the #1764 {@link computeWebManifestHash} shape digest — so the emitted shape\n * and the hashed shape can never drift (a drift would let the hash under-cover a\n * change → stale client caches).\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n customActionParameterInputName,\n isCrudOperation,\n isCrudToolAction,\n resolveCustomActionMetadata,\n} from '../generators/custom-action.js';\nimport {\n buildToolDescriptors,\n finalizeMcpJsonSchema,\n isCrudAction,\n type ToolDescriptor,\n type ToolFieldMeta,\n type ToolIdType,\n type ToolRouteDescriptor,\n} from '../generators/tool-schema.js';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport { readFieldEnum } from '../registry/manifest-field-merge.js';\nimport type {\n FieldDefinition,\n FieldUIHints,\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { isFieldUIWidget } from '../ui-metadata.js';\nimport {\n resolveApiActionRouteConfig,\n resolveApiActionSet,\n} from './sveltekit-generator.js';\n\n/**\n * Field types that are relationship pseudo-columns rather than persisted\n * public-DTO columns — they never appear on the wire as scalar values.\n */\nconst RELATIONSHIP_FIELD_TYPES: ReadonlySet<FieldDefinition['type']> = new Set([\n 'oneToMany',\n 'manyToMany',\n]);\n\n/**\n * Field types that describe a relationship to another model. A superset of\n * {@link RELATIONSHIP_FIELD_TYPES}: `foreignKey`/`crossPackageRef` are persisted\n * scalar id columns (they DO appear on the wire), while `oneToMany`/`manyToMany`\n * are pseudo-columns — but all four carry a `related` edge to a sibling model.\n */\nconst RELATIONSHIP_EDGE_TYPES: ReadonlySet<FieldDefinition['type']> = new Set([\n 'foreignKey',\n 'crossPackageRef',\n 'oneToMany',\n 'manyToMany',\n]);\n\n/**\n * The field types that actually reach an emitted web collection definition:\n * everything {@link buildWebFieldDefinitions} does not skip. Relationship\n * pseudo-columns (`oneToMany`/`manyToMany`) and STI `meta` internals never\n * appear on the wire, so they are excluded from the union — keeping the\n * declared type honest about what a client can observe. Co-managed with the\n * `SmrtWebFieldType` unions in the ambient `@happyvertical/smrt-virt-web`\n * d.ts, the physical `@smrt/web` d.ts, and the hand-written\n * `@happyvertical/smrt-web` mirror.\n */\nexport type WebFieldType = Exclude<\n FieldDefinition['type'],\n 'meta' | 'oneToMany' | 'manyToMany'\n>;\n\n/** Informational per-column metadata carried by a collection definition. */\nexport interface WebFieldDefinition {\n type: WebFieldType;\n required?: boolean;\n /** Whether the public field contract explicitly permits `null`. */\n nullable?: boolean;\n default?: unknown;\n /**\n * The developer-authored `@field({ description })` (#2046) — the same text\n * that feeds OpenAPI and MCP schemas, now available to browser UIs as the\n * seed for end-user field help. Sensitive/transient fields are excluded\n * from emission entirely, so their descriptions never ship to the client.\n */\n description?: string;\n /** Static `@field({ ui })` hints (#2046) — the field-policy rail seed. */\n ui?: FieldUIHints;\n}\n\n/** The relationship kinds a web collection edge can describe. */\nexport type WebRelationshipKind =\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\n/**\n * One manifest-derived relationship edge from a collection to a sibling REST\n * collection. Consumed by the browser client-data runtime to invalidate\n * dependent collection caches when this collection is mutated (#1761) — the\n * cache-invalidation graph is derived entirely from these edges, never\n * hand-wired. SMRT-owned data: no client-engine type appears here.\n */\nexport interface WebRelationship {\n /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */\n field: string;\n /** The relationship kind, mirroring the manifest field type. */\n kind: WebRelationshipKind;\n /** REST collection name the edge resolves to (e.g. `ad_groups`). */\n relatedCollection: string;\n}\n\n/** One selected REST collection and the model that owns its definition. */\nexport interface WebCollectionEntry {\n /** REST collection name (pluralized), e.g. `products`. */\n collection: string;\n /** The manifest object that owns this collection's definition. */\n obj: SmartObjectDefinition;\n /** Sorted set of exposed CRUD + custom actions. */\n actions: string[];\n}\n\n/**\n * One canonical WebMCP tool candidate. `obj` owns the REST collection and row\n * contract; `actionHost` declares the action. They differ for custom methods\n * declared on a `SmrtCollection` subclass.\n */\nexport interface WebMcpToolEntry {\n collection: string;\n obj: SmartObjectDefinition;\n actionHost: SmartObjectDefinition;\n action: string;\n}\n\n/**\n * Transport-complete, data-only definition for one generated browser tool.\n * Unlike {@link WebCollectionEntry}, this shape does not imply that a list\n * route or cache-backed browser collection exists.\n */\nexport interface WebMcpToolDefinition extends ToolDescriptor {\n /** Stable definition identity is `(collection, action)`. */\n collection: string;\n /** Canonical qualified identity of the row model owning the collection. */\n objectRef: string;\n /** Row-model class used by the shared MCP tool vocabulary. */\n className: string;\n /** Path below the generated API base path. */\n endpoint: string;\n idField: string;\n idType: ToolIdType;\n /** Always present so a fetch-only registrar needs no action heuristics. */\n route: ToolRouteDescriptor;\n /** Cache-invalidation edges for materialized sibling collections. */\n relationships: WebRelationship[];\n}\n\n/**\n * Deterministic string comparator shared by every manifest-derived ordering\n * in this module and by consumers that need to sort manifest objects the\n * same way generated declarations do (#2749).\n */\nexport function compareText(left: string, right: string): number {\n if (left < right) return -1;\n if (left > right) return 1;\n return 0;\n}\n\n/**\n * Deterministic manifest iteration order shared by every consumer that must\n * visit manifest objects the same way (#2749, #2754): the manifest index,\n * generated route emission, and the client-declaration mirror of emitted\n * CRUD surfaces. Sorting by qualified identity (falling back to the manifest\n * key) resolves shared-endpoint last-writer-wins ties identically regardless\n * of manifest scan/insertion order; unique manifest keys make the secondary\n * key compare a total tie-break.\n */\nexport function orderedManifestObjectEntries(\n manifest: SmartObjectManifest,\n): Array<[string, SmartObjectDefinition]> {\n return Object.entries(manifest.objects).sort(\n ([leftKey, left], [rightKey, right]) =>\n compareText(\n left.qualifiedName || leftKey,\n right.qualifiedName || rightKey,\n ) || compareText(leftKey, rightKey),\n );\n}\n\nfunction manifestObjectPackage(\n manifestKey: string | undefined,\n obj: SmartObjectDefinition,\n): string | undefined {\n if (obj.packageName) return obj.packageName;\n const qualifiedName = obj.qualifiedName || manifestKey;\n const separator = qualifiedName?.lastIndexOf(':') ?? -1;\n return separator > 0 ? qualifiedName?.slice(0, separator) : undefined;\n}\n\ninterface ManifestObjectIndex {\n byExactName: Map<string, SmartObjectDefinition>;\n byPackageAndClass: Map<string, SmartObjectDefinition>;\n bySimpleName: Map<string, SmartObjectDefinition>;\n packageByObject: WeakMap<SmartObjectDefinition, string>;\n objectRefByObject: WeakMap<SmartObjectDefinition, string>;\n}\n\nconst manifestObjectIndexes = new WeakMap<\n SmartObjectManifest['objects'],\n ManifestObjectIndex\n>();\n\nfunction packageAndClassKey(packageName: string, className: string): string {\n return `${packageName}\\0${className}`;\n}\n\nfunction getManifestObjectIndex(\n manifest: SmartObjectManifest,\n): ManifestObjectIndex {\n const cached = manifestObjectIndexes.get(manifest.objects);\n if (cached) return cached;\n\n const entries = orderedManifestObjectEntries(manifest);\n const index: ManifestObjectIndex = {\n byExactName: new Map(),\n byPackageAndClass: new Map(),\n bySimpleName: new Map(),\n packageByObject: new WeakMap(),\n objectRefByObject: new WeakMap(),\n };\n\n for (const [manifestKey, candidate] of entries) {\n if (!index.byExactName.has(manifestKey)) {\n index.byExactName.set(manifestKey, candidate);\n }\n if (\n candidate.qualifiedName &&\n !index.byExactName.has(candidate.qualifiedName)\n ) {\n index.byExactName.set(candidate.qualifiedName, candidate);\n }\n if (!index.bySimpleName.has(candidate.className)) {\n index.bySimpleName.set(candidate.className, candidate);\n }\n\n const packageName = manifestObjectPackage(manifestKey, candidate);\n if (packageName) {\n if (!index.packageByObject.has(candidate)) {\n index.packageByObject.set(candidate, packageName);\n }\n const packageKey = packageAndClassKey(packageName, candidate.className);\n if (!index.byPackageAndClass.has(packageKey)) {\n index.byPackageAndClass.set(packageKey, candidate);\n }\n }\n\n // New manifests carry `qualifiedName` on each object. Legacy aggregated\n // manifests can omit it from the object body while retaining a qualified\n // manifest key, so retain that equally canonical identity for browser\n // definitions rather than falling back to an ambiguous simple class name.\n const objectRef = candidate.qualifiedName?.includes(':')\n ? candidate.qualifiedName\n : manifestKey.includes(':')\n ? manifestKey\n : packageName\n ? `${packageName}:${candidate.className}`\n : undefined;\n if (objectRef && !index.objectRefByObject.has(candidate)) {\n index.objectRefByObject.set(candidate, objectRef);\n }\n }\n\n manifestObjectIndexes.set(manifest.objects, index);\n return index;\n}\n\nfunction indexedManifestObjectPackage(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): string | undefined {\n const index = getManifestObjectIndex(manifest);\n return (\n index.packageByObject.get(obj) || manifestObjectPackage(undefined, obj)\n );\n}\n\n/**\n * Return the canonical qualified model identity carried by a web collection.\n * Field-policy APIs require this `@package/name:ClassName` reference; a simple\n * class name is ambiguous in aggregated manifests and must never reach a\n * browser definition.\n */\nfunction webCollectionObjectRef(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): string {\n const objectRef = getManifestObjectIndex(manifest).objectRefByObject.get(obj);\n if (objectRef) return objectRef;\n\n throw new Error(\n `[smrt] Cannot emit web collection definition for ${obj.className}: ` +\n 'a qualifiedName, qualified manifest key, or packageName is required',\n );\n}\n\n/**\n * Resolve a manifest object deterministically across aggregated packages.\n *\n * Qualified references win exactly. Simple references prefer the referencing\n * object's package, then fall back to a stable qualified/key identity. This is\n * important because aggregated manifests can contain duplicate class names and\n * their object insertion order reflects package discovery order.\n */\nexport function findManifestObjectByName(\n manifest: SmartObjectManifest,\n name: string,\n owner?: SmartObjectDefinition,\n): SmartObjectDefinition | undefined {\n const index = getManifestObjectIndex(manifest);\n const exact = name.includes(':') ? index.byExactName.get(name) : undefined;\n if (exact) return exact;\n\n const ownerPackage = owner\n ? indexedManifestObjectPackage(manifest, owner)\n : undefined;\n const packageLocal = ownerPackage\n ? index.byPackageAndClass.get(packageAndClassKey(ownerPackage, name))\n : undefined;\n\n return packageLocal || index.bySimpleName.get(name);\n}\n\n/**\n * Normalize a relationship field's `related` value to a resolvable class name.\n *\n * Thunk forward-ref decorators — `@foreignKey(() => Scene)`, used heavily in\n * video/voice for models that reference a class declared later — used to\n * serialize as the RAW arrow-function source string `\"() => Scene\"` in the\n * manifest, which neither `className` nor `qualifiedName` matches. The scanner\n * now unwraps the thunk itself (#2379), so this only has to cover manifests\n * generated before that fix. Extract the target class name from the thunk so\n * the edge resolves. Plain (`\"Scene\"`) and qualified\n * (`\"@happyvertical/smrt-assets:Asset\"`) forms contain no `=>` and pass through\n * untouched — the qualified `:` separator is preserved.\n *\n * Kept local to the relationship-edge path on purpose: extends-chain resolution\n * never sees a thunk, so `findManifestObjectByName` and the scanner stay\n * unchanged.\n */\nfunction normalizeRelatedName(related: string): string {\n const thunk = related.match(/=>\\s*([A-Za-z_$][\\w$]*)/);\n return thunk ? thunk[1] : related.trim();\n}\n\n/**\n * True when `obj` is (transitively) a SmrtCollection subclass. Collection\n * classes describe access, not row shapes, so they never become web\n * collection definitions.\n *\n * NOTE: deliberately stronger than sveltekit-generator's private\n * `isCollectionClass`, which only inspects the direct base / a type argument.\n * A deeper subclass (`SpecialWidgetCollection extends WidgetCollection`)\n * carries no type argument of its own; without walking the extends chain it\n * would be mistaken for a model and claim its base model's REST collection.\n */\nexport function isCollectionManifestClass(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n seen: Set<string> = new Set(),\n): boolean {\n // Truthy check (not `!== undefined`) mirrors the scanner's own\n // manifest-generator: a scanner that emits `extendsTypeArg: null` for a\n // non-generic base must not be misread as a collection.\n if (obj.extends === 'SmrtCollection' || obj.extendsTypeArg) {\n return true;\n }\n const parentName = obj.extendsQualified || obj.extends;\n if (!parentName || seen.has(parentName)) return false;\n seen.add(parentName);\n const parent = findManifestObjectByName(manifest, parentName, obj);\n return parent ? isCollectionManifestClass(manifest, parent, seen) : false;\n}\n\n/**\n * Resolve the row model carried by a collection class.\n *\n * An explicit generic on any ancestor is authoritative. Conventional\n * `FooCollection` → `Foo` lookup is only a fallback after the full ancestry\n * has been checked, so an unrelated `SpecialFoo` cannot override an inherited\n * `FooCollection<Foo>` contract.\n */\nfunction collectionAncestry(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): SmartObjectDefinition[] {\n const ancestry: SmartObjectDefinition[] = [];\n const seen = new Set<string>();\n let candidate: SmartObjectDefinition | undefined = obj;\n\n while (candidate) {\n ancestry.push(candidate);\n const parentName = candidate.extendsQualified || candidate.extends;\n if (!parentName || seen.has(parentName)) break;\n seen.add(parentName);\n candidate = findManifestObjectByName(manifest, parentName, candidate);\n }\n return ancestry;\n}\n\n/**\n * Resolve the authoritative collection item type name even when a partial\n * manifest omits the item definition itself.\n */\ninterface CollectionItemReference {\n name: string;\n owner: SmartObjectDefinition;\n object?: SmartObjectDefinition;\n}\n\nfunction resolveCollectionItemCandidate(\n manifest: SmartObjectManifest,\n name: string,\n owner: SmartObjectDefinition,\n): CollectionItemReference {\n const object = findManifestObjectByName(manifest, name, owner);\n const ownerPackage = indexedManifestObjectPackage(manifest, owner);\n\n if (!name.includes(':') && ownerPackage) {\n const objectPackage = object\n ? indexedManifestObjectPackage(manifest, object)\n : undefined;\n if (objectPackage !== ownerPackage) {\n return {\n name: `${ownerPackage}:${name}`,\n owner,\n };\n }\n }\n\n return { name, owner, object };\n}\n\nfunction resolveCollectionItemReference(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): CollectionItemReference | undefined {\n const ancestry = collectionAncestry(manifest, obj);\n for (const collectionClass of ancestry) {\n if (collectionClass.extendsTypeArg) {\n return resolveCollectionItemCandidate(\n manifest,\n collectionClass.extendsTypeArg,\n collectionClass,\n );\n }\n }\n\n let fallback: CollectionItemReference | undefined;\n for (const collectionClass of ancestry) {\n if (!collectionClass.className.endsWith('Collection')) continue;\n const conventionalName = collectionClass.className.slice(\n 0,\n -'Collection'.length,\n );\n const candidate = resolveCollectionItemCandidate(\n manifest,\n conventionalName,\n collectionClass,\n );\n if (candidate.object) return candidate;\n fallback ??= candidate;\n }\n\n return fallback;\n}\n\nexport function resolveCollectionItemTypeName(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): string | undefined {\n return resolveCollectionItemReference(manifest, obj)?.name;\n}\n\nexport function resolveCollectionItemObject(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): SmartObjectDefinition | undefined {\n return resolveCollectionItemReference(manifest, obj)?.object;\n}\n\n/**\n * True when some ancestor model maps to the SAME REST collection (a shared STI\n * table). The STI base model owns the shared table's single definition.\n */\nfunction isStiChildModel(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): boolean {\n const seen = new Set<string>();\n let child = obj;\n let parentName = child.extendsQualified || child.extends;\n while (parentName && !seen.has(parentName)) {\n seen.add(parentName);\n const parent = findManifestObjectByName(manifest, parentName, child);\n if (!parent) return false;\n if (parent.collection === obj.collection) return true;\n child = parent;\n parentName = parent.extendsQualified || parent.extends;\n }\n return false;\n}\n\n/**\n * Select one entry per REST collection whose exposed action set satisfies\n * `qualifies` — STI children folding into their base model, collection classes\n * excluded. Uses the canonical {@link resolveApiActionSet} so the exposed-action\n * set matches exactly what the REST/SvelteKit generators actually emit. Shared\n * by {@link selectWebCollectionEntries} (list-qualified — materializable\n * collections) and {@link selectWebEtagSaltEntries} (get-OR-list — every model\n * with a read route the ETag salt must cover).\n */\nfunction selectEntriesQualifiedBy(\n manifest: SmartObjectManifest,\n qualifies: (actions: ReadonlySet<string>) => boolean,\n): WebCollectionEntry[] {\n const byCollection = new Map<\n string,\n WebCollectionEntry & { isStiChild: boolean }\n >();\n\n // Manifest key order follows scan/discovery order, which is not stable\n // across runs (#2749). STI base-vs-child precedence below is independent\n // of that order, but two candidates of EQUAL isStiChild status sharing one\n // `collection` (e.g. two unrelated models, or two STI children whose base\n // is absent/not qualified) are not — the map keeps whichever arrives\n // first. Pre-sort the iteration source so that tie is resolved by\n // qualified name instead of scan order. Unlike\n // {@link selectWebMcpToolEntries}'s sort, this falls back to the manifest\n // key rather than the strict {@link webCollectionObjectRef} (which throws\n // without a qualifiedName/packageName): every candidate must be ordered\n // here, including ones that never reach {@link buildWebCollectionDefinition}\n // and so never need a resolvable object ref.\n const sortedEntries = Object.entries(manifest.objects)\n .filter(([, obj]) => !isFrameworkBaseClass(obj.className, obj.packageName))\n .sort(\n ([leftKey, left], [rightKey, right]) =>\n compareText(\n left.qualifiedName || leftKey,\n right.qualifiedName || rightKey,\n ) || compareText(leftKey, rightKey),\n );\n\n for (const [, obj] of sortedEntries) {\n if (isCollectionManifestClass(manifest, obj)) continue;\n\n // Pass the manifest: without it the wire-ability heuristic cannot tell a\n // model class from an options interface, and this selector would qualify a\n // collection on an action the route emitters withhold (#2686).\n const exposedActions = resolveApiActionSet(obj, manifest);\n if (!qualifies(exposedActions)) continue;\n\n const isStiChild = isStiChildModel(manifest, obj);\n const existing = byCollection.get(obj.collection);\n // One definition per REST collection. The STI BASE model owns it: a child\n // only wins while no base has been recorded yet. Between two candidates\n // of equal status, the pre-sort above makes the winner deterministic.\n if (existing && !(existing.isStiChild && !isStiChild)) continue;\n\n byCollection.set(obj.collection, {\n collection: obj.collection,\n obj,\n actions: [...exposedActions].sort(),\n isStiChild,\n });\n }\n\n // The winner for each collection is now independent of scan order (above);\n // sort the final list by collection name so the emitted declarations are\n // too.\n return [...byCollection.values()]\n .sort((left, right) => compareText(left.collection, right.collection))\n .map(({ collection, obj, actions }) => ({\n collection,\n obj,\n actions,\n }));\n}\n\n/**\n * Select the manifest entries that become web collection definitions: one per\n * REST collection, STI children folding into their base model, collection\n * classes excluded, and only models that expose `list` (a read surface is\n * required to MATERIALIZE a client collection — that is what persists).\n */\nexport function selectWebCollectionEntries(\n manifest: SmartObjectManifest,\n): WebCollectionEntry[] {\n return selectEntriesQualifiedBy(manifest, (actions) => actions.has('list'));\n}\n\n/** Walk to the highest STI ancestor that owns the same REST collection. */\nfunction resolveStiCollectionOwner(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): SmartObjectDefinition {\n const seen = new Set<string>();\n let owner = obj;\n let parentName = owner.extendsQualified || owner.extends;\n while (parentName && !seen.has(parentName)) {\n seen.add(parentName);\n const parent = findManifestObjectByName(manifest, parentName, owner);\n if (!parent || parent.collection !== obj.collection) break;\n owner = parent;\n parentName = owner.extendsQualified || owner.extends;\n }\n return owner;\n}\n\n/**\n * Select every generated WebMCP action independently of collection\n * materialization. Object actions establish the owning row model first;\n * collection-class actions then supplement missing `(collection, action)`\n * identities. Therefore an object action wins an exact collision, while all\n * ordering is independent of manifest insertion order.\n */\nexport function selectWebMcpToolEntries(\n manifest: SmartObjectManifest,\n): WebMcpToolEntry[] {\n // The framework's own abstract base classes are scaffolding, not\n // resources — filtered once here so every loop below (object owners,\n // custom-action hosts, and the collection-class ownership pass) excludes\n // them, regardless of config (#2642).\n const manifestObjects = Object.values(manifest.objects).filter(\n (obj) => !isFrameworkBaseClass(obj.className, obj.packageName),\n );\n const sortedObjects = [...manifestObjects].sort((left, right) =>\n compareText(\n webCollectionObjectRef(manifest, left),\n webCollectionObjectRef(manifest, right),\n ),\n );\n const objectOwners = new Map<string, WebCollectionEntry>();\n\n for (const candidate of sortedObjects) {\n if (isCollectionManifestClass(manifest, candidate)) continue;\n const existing = objectOwners.get(candidate.collection);\n const candidateIsChild = isStiChildModel(manifest, candidate);\n const existingIsChild = existing\n ? isStiChildModel(manifest, existing.obj)\n : false;\n if (existing && !(existingIsChild && !candidateIsChild)) continue;\n\n objectOwners.set(candidate.collection, {\n collection: candidate.collection,\n obj: candidate,\n actions: [],\n });\n }\n\n const entries = new Map<string, WebMcpToolEntry>();\n const ownerObjects = [...objectOwners.values()]\n .sort(\n (left, right) =>\n compareText(left.collection, right.collection) ||\n compareText(\n webCollectionObjectRef(manifest, left.obj),\n webCollectionObjectRef(manifest, right.obj),\n ),\n )\n .map((entry) => entry.obj);\n const ownerSet = new Set(ownerObjects);\n // Owners go first so the STI base wins an exact object-action collision.\n // Remaining models still contribute unique custom actions on the shared\n // collection instead of disappearing behind the owning row definition.\n const objectActionHosts = [\n ...ownerObjects,\n ...sortedObjects.filter(\n (candidate) =>\n !isCollectionManifestClass(manifest, candidate) &&\n !ownerSet.has(candidate),\n ),\n ];\n\n // Which CRUD verbs are actually emitted, keyed by COLLECTION rather than by\n // host. The tool id namespace is `${collectionClassName}_${action}`, so it is\n // shared by every host mapping to one collection — an STI base and its\n // children, or two models declaring the same collection. Asking only \"does\n // THIS host emit `list`?\" would let a host that excludes `list` but declares\n // `List()` claim `product_list`, after which a sibling host's real `list`\n // hits the fold-dedupe and is dropped. That is the impostor outranking the\n // verb again, one door over (#2648).\n const emittedCrudByCollection = new Map<string, Set<string>>();\n for (const host of [\n ...objectActionHosts,\n ...manifestObjects.filter((candidate) =>\n isCollectionManifestClass(manifest, candidate),\n ),\n ]) {\n const collection = objectOwners.get(host.collection)?.collection;\n if (!collection) continue;\n const bucket = emittedCrudByCollection.get(collection) ?? new Set<string>();\n for (const action of resolveApiActionSet(host, manifest)) {\n if (isCrudOperation(action)) bucket.add(action);\n }\n emittedCrudByCollection.set(collection, bucket);\n }\n\n for (const actionHost of objectActionHosts) {\n const owner = objectOwners.get(actionHost.collection);\n if (!owner) continue;\n const emittedCrud =\n emittedCrudByCollection.get(owner.collection) ?? new Set<string>();\n for (const action of [...resolveApiActionSet(actionHost, manifest)].sort(\n compareText,\n )) {\n // A browser tool id is lowercased whole (`${prefix}_${action}`), so this\n // namespace is case-folded even though the API action set that feeds it\n // is not. Two guards, in order (#2648):\n //\n // 1. An action that is not exactly a CRUD verb but folds onto one is a\n // custom method wearing the generated tool's id. Drop it rather than\n // letting it win: `compareText` sorts `List` BEFORE `list`, so a\n // first-wins dedupe alone would keep the impostor and lose the real\n // CRUD tool. REST keeps both — `/products/List` is a distinct route\n // from `/products` — so this reservation belongs here, not in\n // `resolveApiActionSet`.\n //\n // CONDITIONAL on the CRUD verb actually being in this host's action\n // set. `generators/mcp.ts` reserves unconditionally because its\n // dispatch parses the verb out of the tool id, so `${obj}_list` can\n // only ever run the built-in list. A WebMCP descriptor is different:\n // a non-CRUD action carries its own `route` from\n // `resolveApiActionRouteConfig`, so with `api: { include: ['List'] }`\n // the id `product_list` genuinely dispatches to `/products/List` and\n // is the ONLY tool for that id. Dropping it there would make a\n // custom-action-only model undiscoverable while its REST route still\n // answers (#2648).\n if (\n isCrudToolAction(action) &&\n !isCrudOperation(action) &&\n emittedCrud.has(action.toLowerCase())\n ) {\n continue;\n }\n // 2. Anything still colliding after that is two custom methods differing\n // only in case; keep the first so one id yields one descriptor.\n const identity = `${owner.collection}\\0${action.toLowerCase()}`;\n if (entries.has(identity)) continue;\n entries.set(identity, {\n collection: owner.collection,\n obj: owner.obj,\n // CRUD is a shared collection identity owned by the STI base even if\n // a child is the manifest entry that exposed it. Custom actions retain\n // their declaring host so route overrides and parameters stay exact.\n actionHost: isCrudAction(action) ? owner.obj : actionHost,\n action,\n });\n }\n }\n\n // SvelteKit emits collection-class routes in manifest order and a later\n // class replaces an earlier handler at the same path. Follow that ownership\n // rule so the selected parameter/route metadata describes the live handler.\n // Object actions remain authoritative over collection-class collisions.\n for (const collectionClass of manifestObjects) {\n if (!isCollectionManifestClass(manifest, collectionClass)) continue;\n const itemObject = resolveCollectionItemObject(manifest, collectionClass);\n if (!itemObject) continue;\n const owner =\n objectOwners.get(collectionClass.collection)?.obj ??\n resolveStiCollectionOwner(manifest, itemObject);\n\n // Same collection-level set as loop 1. A collection class's OWN action set\n // never contains an exact CRUD verb — `resolveApiActionSet` starts a\n // collection class at an empty set and skips the shared CRUD verb list\n // (`isCrudOperation`, generators/custom-action.ts, #2665) —\n // so a per-host set here would always be empty and the guard dead.\n const collectionEmittedCrud =\n emittedCrudByCollection.get(collectionClass.collection) ??\n new Set<string>();\n for (const action of [\n ...resolveApiActionSet(collectionClass, manifest),\n ].sort(compareText)) {\n // Same two guards, same order and conditionality as the object loop\n // above — and the same key shape. Both loops share `entries`, so a case-sensitive key here\n // would miss the lowercased one written there and insert a second entry\n // for the same tool id, breaking the ownership rule this loop exists to\n // enforce (#2648).\n if (\n isCrudToolAction(action) &&\n !isCrudOperation(action) &&\n collectionEmittedCrud.has(action.toLowerCase())\n ) {\n continue;\n }\n const identity = `${collectionClass.collection}\\0${action.toLowerCase()}`;\n const existing = entries.get(identity);\n if (\n existing &&\n !isCollectionManifestClass(manifest, existing.actionHost)\n ) {\n continue;\n }\n entries.set(identity, {\n collection: collectionClass.collection,\n obj: owner,\n actionHost: collectionClass,\n action,\n });\n }\n }\n\n return [...entries.values()].sort(\n (left, right) =>\n compareText(left.collection, right.collection) ||\n compareText(left.action, right.action) ||\n compareText(\n webCollectionObjectRef(manifest, left.actionHost),\n webCollectionObjectRef(manifest, right.actionHost),\n ),\n );\n}\n\nfunction requireRoutePathInputs(\n descriptor: ToolDescriptor | undefined,\n): ToolDescriptor | undefined {\n if (!descriptor) return undefined;\n const route = descriptor.route;\n if (!route) return descriptor;\n\n const pathParameters = route.path\n .filter((segment) => /^\\[[^\\]]+\\]$/.test(segment))\n .map((segment) => segment.slice(1, -1));\n if (pathParameters.length === 0) return descriptor;\n\n const schema = descriptor.inputSchema;\n const properties = {\n ...((schema.properties as Record<string, unknown> | undefined) ?? {}),\n };\n const required = new Set(\n Array.isArray(schema.required)\n ? schema.required.filter(\n (name): name is string => typeof name === 'string',\n )\n : [],\n );\n\n for (const parameterName of pathParameters) {\n const inputName =\n Object.entries(route.parameterAliases ?? {}).find(\n ([, originalName]) => originalName === parameterName,\n )?.[0] ?? parameterName;\n properties[inputName] ??= {\n type: 'string',\n description: `Required route parameter: ${parameterName}`,\n };\n required.add(inputName);\n }\n\n return {\n ...descriptor,\n inputSchema: finalizeMcpJsonSchema({\n ...schema,\n properties,\n required: [...required],\n }),\n };\n}\n\n/**\n * Select the entries the ETag salt (#1764) must cover: every api-exposed model\n * with a GENERATED READ ROUTE — `list` OR `get`. Broader than\n * {@link selectWebCollectionEntries} on purpose: a get-only model\n * (`api: { include: ['get'] }`) has no materializable client collection (so it\n * never persists), but its generated GET route IS salted, so a shape-only change\n * to it must still change the salt — otherwise a client holding the old concrete\n * ETag would get a zero-query 304 after a shape-only deploy (the #1765 gap the\n * salt closes). Not exported: only {@link computeWebManifestHash} consumes it.\n */\nfunction selectWebEtagSaltEntries(\n manifest: SmartObjectManifest,\n): WebCollectionEntry[] {\n return selectEntriesQualifiedBy(\n manifest,\n (actions) => actions.has('list') || actions.has('get'),\n );\n}\n\n/**\n * Build the per-collection web-collection definition literal — the SINGLE\n * source of truth for the shape emitted by {@link generateWebModule} and hashed\n * by {@link computeWebManifestHash}. Building it in ONE place is a\n * cache-coherency requirement: if the emitted shape and the hashed shape were\n * built independently, adding a field to one and not the other would let the\n * hash silently UNDER-cover a shape change, so persisted caches would not drop\n * and stale rows would hydrate into new code.\n */\nexport function buildWebCollectionDefinition(\n entry: WebCollectionEntry,\n manifest: SmartObjectManifest,\n): {\n name: string;\n objectRef: string;\n className: string;\n endpoint: string;\n idField: string;\n actions: string[];\n fields: Record<string, WebFieldDefinition>;\n relationships: WebRelationship[];\n} {\n return {\n name: entry.collection,\n objectRef: webCollectionObjectRef(manifest, entry.obj),\n className: entry.obj.className,\n endpoint: `/${entry.collection}`,\n idField: 'id',\n actions: entry.actions,\n fields: buildWebFieldDefinitions(entry.obj),\n relationships: buildWebRelationships(entry.obj, manifest),\n };\n}\n\n/**\n * Build the informational per-field metadata for a web collection definition:\n * the persisted public-DTO columns only. Relationship pseudo-fields, STI meta\n * internals, transient (unpersisted) and sensitive (wire-stripped) fields are\n * excluded — they are not columns a client reads back over the REST surface.\n */\nexport function buildWebFieldDefinitions(\n obj: SmartObjectDefinition,\n): Record<string, WebFieldDefinition> {\n const fields: Record<string, WebFieldDefinition> = {};\n for (const [fieldName, field] of Object.entries(obj.fields ?? {})) {\n if (RELATIONSHIP_FIELD_TYPES.has(field.type)) continue;\n if (field.type === 'meta') continue;\n if (field.transient) continue;\n if (field.sensitive) continue;\n const ui = sanitizeFieldUIHints(field._meta?.ui);\n // Scanner manifests expose `description` top-level; runtime-registry\n // manifests (computeRuntimeWebManifestHash → registeredFieldsToManifest)\n // keep it under `_meta` only. Read both so the build-time and runtime\n // shape digests agree — otherwise every described field would latch a\n // false `updateAvailable.contract` signal on APIGenerator SSE connects.\n const description = readFieldDescription(field);\n fields[fieldName] = {\n // Safe: the three skips above are exactly the types WebFieldType excludes.\n type: field.type as WebFieldType,\n ...(field.required !== undefined ? { required: field.required } : {}),\n ...(field._meta?.nullable === true ? { nullable: true } : {}),\n ...(field.default !== undefined ? { default: field.default } : {}),\n ...(description !== undefined ? { description } : {}),\n ...(ui ? { ui } : {}),\n };\n }\n return fields;\n}\n\n/** Top-level `description` with `_meta.description` fallback (see call site). */\nfunction readFieldDescription(field: FieldDefinition): string | undefined {\n if (typeof field.description === 'string' && field.description !== '') {\n return field.description;\n }\n const metaDescription = field._meta?.description;\n if (typeof metaDescription === 'string' && metaDescription !== '') {\n return metaDescription;\n }\n return undefined;\n}\n\n/**\n * Narrow a manifest `_meta.ui` bag to the known {@link FieldUIHints} keys with\n * per-key type guards. `_meta` is an open bag, so authored junk (wrong-typed\n * values, arbitrary extra keys) must not reach the emitted client module — the\n * emitted value also feeds the #1764 shape digest, so unknown unstable keys\n * would churn the hash. Returns undefined when nothing valid remains, so\n * hint-less fields emit no `ui` key at all (existing shapes stay byte-stable).\n */\nfunction sanitizeFieldUIHints(value: unknown): FieldUIHints | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n const raw = value as Record<string, unknown>;\n const ui: FieldUIHints = {\n ...(typeof raw.basic === 'boolean' ? { basic: raw.basic } : {}),\n ...(typeof raw.group === 'string' && raw.group !== ''\n ? { group: raw.group }\n : {}),\n ...(typeof raw.order === 'number' && Number.isFinite(raw.order)\n ? { order: raw.order }\n : {}),\n ...(typeof raw.locked === 'boolean' ? { locked: raw.locked } : {}),\n ...(isFieldUIWidget(raw.widget) ? { widget: raw.widget } : {}),\n };\n return Object.keys(ui).length > 0 ? ui : undefined;\n}\n\n/**\n * Build the manifest-derived relationship edges for a web collection\n * definition (#1761): one entry per relationship field (`foreignKey`,\n * `crossPackageRef`, `oneToMany`, `manyToMany`) whose `related` target resolves\n * to another API-exposed REST collection.\n *\n * These edges drive relationship-derived cache invalidation in the browser\n * client-data runtime: mutating this collection invalidates the caches of the\n * collections named here. The invalidation graph is thus derived entirely from\n * the manifest — no hand-wired cache keys.\n *\n * An edge is SKIPPED (not emitted) when:\n * - `related` is missing, or\n * - `related` cannot be resolved to a manifest object (e.g. a cross-package\n * target not present in this package's manifest), or\n * - the resolved target is not itself an API-exposed web collection (no read\n * surface to invalidate — it never appears in {@link selectWebCollectionEntries}).\n *\n * Self-referential edges (a collection related to itself) are kept: the runtime\n * always invalidates the mutated collection anyway, so a self edge is harmless\n * and keeping it avoids a special case.\n */\nexport function buildWebRelationships(\n obj: SmartObjectDefinition,\n manifest: SmartObjectManifest,\n): WebRelationship[] {\n // The set of REST collections that are actually materialized as web\n // collections. An edge to a model outside this set has no client cache to\n // invalidate, so it is dropped.\n const exposedCollections = new Set(\n selectWebCollectionEntries(manifest).map((entry) => entry.collection),\n );\n\n const relationships: WebRelationship[] = [];\n const seen = new Set<string>();\n for (const [fieldName, field] of Object.entries(obj.fields ?? {})) {\n if (!RELATIONSHIP_EDGE_TYPES.has(field.type)) continue;\n if (!field.related) continue;\n\n // Normalize first: `@foreignKey(() => Scene)` thunks serialize as the raw\n // \"() => Scene\" source, which findManifestObjectByName cannot match on its\n // own.\n const target = findManifestObjectByName(\n manifest,\n normalizeRelatedName(field.related),\n obj,\n );\n if (!target) continue;\n if (!exposedCollections.has(target.collection)) continue;\n\n // De-dupe on (field, relatedCollection): a model never declares the same\n // field twice, but guard anyway so the emitted edge list is stable.\n const dedupeKey = `${fieldName}:${target.collection}`;\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n relationships.push({\n field: fieldName,\n kind: field.type as WebRelationshipKind,\n relatedCollection: target.collection,\n });\n }\n return relationships;\n}\n\n/**\n * Build the WebMCP / MCP tool descriptors for a web collection (#1812): one\n * descriptor per exposed action, over the SAME public-DTO fields the definition\n * already exposes (`buildWebFieldDefinitions`). The tool ids match the Node MCP\n * surface (`<class>_<action>`), so a page's WebMCP tools and its MCP-server\n * tools share one vocabulary.\n *\n * Deliberately NOT part of {@link buildWebCollectionDefinition}: descriptors are\n * layered onto the emitted value by {@link generateWebModule} instead, so the\n * #1764 {@link computeWebManifestHash} shape digest keeps hashing ONLY the row\n * shape. That remains safe because descriptor-only inputs (including `idType`)\n * do not alter persisted public rows or read responses, which are the cache\n * contracts this digest protects.\n */\nexport function buildWebToolDescriptors(\n entry: WebCollectionEntry,\n options: { kebabRoutes?: boolean } = {},\n): ToolDescriptor[] {\n return buildWebToolDescriptorsForHost({\n owner: entry.obj,\n actionHost: entry.obj,\n actions: entry.actions,\n options,\n });\n}\n\nfunction buildWebToolDescriptorsForHost(input: {\n owner: SmartObjectDefinition;\n actionHost: SmartObjectDefinition;\n actions: string[];\n options: { kebabRoutes?: boolean };\n collectionHost?: boolean;\n}): ToolDescriptor[] {\n const { owner, actionHost, options, collectionHost = false } = input;\n // Every descriptor id is `${prefix}_${action}` lowercased whole, so this is\n // the one choke point both emission paths pass through: the canonical\n // per-entry `buildWebMcpToolDefinition`, and the legacy per-collection\n // `buildWebToolDescriptors` that fills `collectionDefinitions[].toolDescriptors`.\n // The canonical path is already filtered by `selectWebMcpToolEntries`, so\n // this only bites for the legacy one — where an unfiltered `entry.actions`\n // would emit two `product_list` descriptors and make `validateProspectiveTools`\n // throw `Duplicate WebMCP tool name`, aborting registration of EVERY tool on\n // the page rather than just the colliding pair (#2648).\n //\n // Same two guards, same order and conditionality as\n // `selectWebMcpToolEntries`: drop a cased CRUD impostor only when the verb it\n // shadows is actually in this action set (it must not outrank the real verb,\n // but it is the only tool for that id when the verb is absent), then\n // fold-dedupe whatever still collides.\n const emittedCrudActions = new Set(input.actions.filter(isCrudOperation));\n const seenToolActions = new Set<string>();\n const actions = input.actions.filter((action) => {\n if (\n isCrudToolAction(action) &&\n !isCrudOperation(action) &&\n emittedCrudActions.has(action.toLowerCase())\n ) {\n return false;\n }\n const folded = action.toLowerCase();\n if (seenToolActions.has(folded)) return false;\n seenToolActions.add(folded);\n return true;\n });\n const webFields = buildWebFieldDefinitions(owner);\n const fields: ToolFieldMeta[] = Object.entries(webFields).map(\n ([name, def]) => ({\n name,\n type: def.type,\n ...(def.required !== undefined ? { required: def.required } : {}),\n ...(def.nullable === true ? { nullable: true } : {}),\n ...(def.default !== undefined ? { default: def.default } : {}),\n // #2046: the authored field description flows into the generated JSON\n // Schema (`fieldTypeToJsonSchema` prefers it over the type-derived\n // fallback), so browser-side WebMCP tools document their arguments.\n ...(def.description !== undefined\n ? { description: def.description }\n : {}),\n // #3598: enum / literal-union fields constrain the tool input schema.\n ...(readFieldEnum(owner.fields?.[name])\n ? { enum: readFieldEnum(owner.fields?.[name]) }\n : {}),\n }),\n );\n const descriptors = buildToolDescriptors({\n className: owner.className,\n fields,\n actions,\n customActions: Object.fromEntries(\n actions.map((action) => [\n action,\n resolveCustomActionMetadata({\n actionName: action,\n method: actionHost.methods?.[action],\n apiConfig: actionHost.decoratorConfig?.api,\n ...(collectionHost ? { defaultScope: 'collection' as const } : {}),\n }),\n ]),\n ),\n idType: owner.decoratorConfig.idType,\n ...(owner.displayLabelField\n ? { displayLabelField: owner.displayLabelField }\n : {}),\n });\n\n return descriptors.map((descriptor) => {\n if (isCrudAction(descriptor.action)) return descriptor;\n const method = actionHost.methods?.[descriptor.action];\n const route = resolveApiActionRouteConfig(\n descriptor.action,\n method ?? {},\n actionHost.decoratorConfig?.api,\n { kebabRoutes: options.kebabRoutes },\n collectionHost ? 'collection' : method?.isStatic ? 'collection' : 'item',\n );\n const metadata = resolveCustomActionMetadata({\n actionName: descriptor.action,\n method,\n apiConfig: actionHost.decoratorConfig?.api,\n ...(collectionHost ? { defaultScope: 'collection' as const } : {}),\n });\n const parameterAliases = Object.fromEntries(\n (method?.parameters ?? [])\n .map((parameter) => [\n customActionParameterInputName(metadata, parameter.name),\n parameter.name,\n ])\n .filter(([inputName, parameterName]) => inputName !== parameterName),\n );\n return {\n ...descriptor,\n route: {\n method: route.method,\n scope: route.scope,\n path: route.pathSegments,\n ...(metadata.parameters === undefined ||\n (metadata.parameters.length === 1 &&\n metadata.parameters[0]?.name === 'options')\n ? { optionsBag: true }\n : {}),\n ...(Object.keys(parameterAliases).length > 0\n ? { parameterAliases }\n : {}),\n },\n };\n });\n}\n\nfunction crudToolRoute(action: string): ToolRouteDescriptor {\n switch (action) {\n case 'list':\n return { method: 'GET', scope: 'collection', path: [] };\n case 'get':\n return { method: 'GET', scope: 'item', path: [] };\n case 'create':\n return { method: 'POST', scope: 'collection', path: [] };\n case 'update':\n return { method: 'PUT', scope: 'item', path: [] };\n case 'delete':\n return { method: 'DELETE', scope: 'item', path: [] };\n default:\n throw new Error(`[smrt] Unknown generated CRUD action: ${action}`);\n }\n}\n\n/** Build the canonical, transport-complete definition for one selected tool. */\nexport function buildWebMcpToolDefinition(\n entry: WebMcpToolEntry,\n manifest: SmartObjectManifest,\n options: { kebabRoutes?: boolean } = {},\n): WebMcpToolDefinition {\n const descriptor = requireRoutePathInputs(\n buildWebToolDescriptorsForHost({\n owner: entry.obj,\n actionHost: entry.actionHost,\n actions: [entry.action],\n options,\n collectionHost: isCollectionManifestClass(manifest, entry.actionHost),\n })[0],\n );\n if (!descriptor) {\n throw new Error(\n `[smrt] Failed to build WebMCP descriptor for ${entry.collection}.${entry.action}`,\n );\n }\n\n return {\n collection: entry.collection,\n objectRef: webCollectionObjectRef(manifest, entry.obj),\n className: entry.obj.className,\n endpoint: `/${entry.collection}`,\n idField: 'id',\n idType: entry.obj.decoratorConfig.idType ?? 'uuid',\n relationships: buildWebRelationships(entry.obj, manifest),\n ...descriptor,\n route: descriptor.route ?? crudToolRoute(entry.action),\n };\n}\n\n/** Emit every API-backed WebMCP tool, including non-list models. */\nexport function buildWebMcpToolDefinitions(\n manifest: SmartObjectManifest,\n options: { kebabRoutes?: boolean } = {},\n): WebMcpToolDefinition[] {\n return selectWebMcpToolEntries(manifest).map((entry) =>\n buildWebMcpToolDefinition(entry, manifest, options),\n );\n}\n\n/**\n * Recursively sort object keys so structurally-equal values serialize to the\n * SAME JSON regardless of insertion order. Arrays keep their order (order is\n * semantic for `actions`/`relationships`); objects are rebuilt with keys sorted.\n * Required for {@link computeWebManifestHash} to be replica-stable: two builds\n * that produce the same schema but visit the manifest in a different order (map\n * insertion, scan order) must still hash identically, which a plain\n * `JSON.stringify` of insertion-ordered objects would NOT guarantee.\n */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => canonicalize(entry));\n }\n if (value && typeof value === 'object') {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[key] = canonicalize((value as Record<string, unknown>)[key]);\n }\n return sorted;\n }\n return value;\n}\n\n/**\n * A deterministic, replica-stable digest of the web-collection SHAPE (#1764).\n *\n * The hash covers exactly the thing whose change means either old persisted\n * client rows may mis-hydrate OR a stale read ETag would still 304: the same\n * per-collection definition shape {@link generateWebModule} emits — name,\n * className, endpoint, idField, actions, fields, relationships — built via the\n * SHARED {@link buildWebCollectionDefinition} so the hash can never disagree\n * with what is actually shipped. The shape is CANONICALIZED (keys recursively\n * sorted; see {@link canonicalize}) before hashing, so the same schema always\n * yields the same digest across builds and replicas regardless of manifest\n * iteration order.\n *\n * SCOPE — get-OR-list (broader than materializable collections). Covered by\n * {@link selectWebEtagSaltEntries}, so it includes GET-ONLY models too: those do\n * not persist (no materializable collection), but their generated GET route IS\n * salted with this hash, so a shape-only change to a get-only model must change\n * it or a client holding the old concrete ETag gets a zero-query 304 after a\n * shape-only deploy (the #1765 gap the salt closes). The two consumers both use\n * this one value, so it stays identical between them:\n * - `@happyvertical/smrt-web` persistence (#1764) folds it into the durable\n * namespace, so a contract-changing deploy lands on a fresh namespace and old\n * rows are never found (dropped, not mis-hydrated). Including get-only models\n * here is harmless over-invalidation — only list-materializable collections\n * ever hold a persisted snapshot.\n * - the generated read ETag (#1765 salt, #1764) folds it in so a shape-only\n * deploy (no table write) busts every read validator, get-only routes too.\n *\n * Truncated to the first 16 base64url chars: 96 bits is far more than enough to\n * make an accidental shape collision negligible, and a short constant keeps the\n * emitted module and every persistence key compact.\n */\nexport function computeWebManifestHash(manifest: SmartObjectManifest): string {\n const definitions: Record<string, unknown> = {};\n for (const entry of selectWebEtagSaltEntries(manifest)) {\n definitions[entry.collection] = buildWebCollectionDefinition(\n entry,\n manifest,\n );\n }\n const canonicalJson = JSON.stringify(canonicalize(definitions));\n return createHash('sha256')\n .update(canonicalJson)\n .digest('base64url')\n .slice(0, 16);\n}\n\n/**\n * Generate the data-only `@smrt/web` module used by both producer and consumer\n * Vite plugins. Keeping the emission beside its selection, row definition, and\n * hash helpers makes every browser collection surface use one projection.\n */\nexport function generateWebModule(\n manifest: SmartObjectManifest,\n options: { kebabRoutes?: boolean } = {},\n): string {\n const definitions: Record<string, unknown> = {};\n for (const entry of selectWebCollectionEntries(manifest)) {\n definitions[entry.collection] = {\n ...buildWebCollectionDefinition(entry, manifest),\n toolDescriptors: buildWebToolDescriptors(entry, options),\n };\n }\n\n const manifestHash = computeWebManifestHash(manifest);\n const webMcpToolDefinitions = buildWebMcpToolDefinitions(manifest, options);\n\n return `\n// Auto-generated web collection definitions from SMRT objects (#1761)\n// This file is generated automatically - do not edit\n\nexport const collectionDefinitions = ${JSON.stringify(definitions, null, 2)};\n\n// Canonical browser-tool definitions. This export is independent of list\n// materialization, so get-only and custom-action-only API routes are included.\nexport const webMcpToolDefinitions = ${JSON.stringify(webMcpToolDefinitions, null, 2)};\n\n// Build-time inject of the web-collection shape digest (#1764) — see\n// computeWebManifestHash. A change here means old persisted client rows may\n// mis-hydrate, so persistence namespaces and read ETags key on it.\nexport const manifestHash = ${JSON.stringify(manifestHash)};\n\nexport function getCollectionDefinition(name) {\n const definition = collectionDefinitions[name];\n if (!definition) {\n throw new Error(\n \\`[smrt] Unknown web collection definition: \\${name}. Known: \\${Object.keys(collectionDefinitions).join(', ')}\\`,\n );\n }\n return definition;\n}\n\nexport { collectionDefinitions as default };\n`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web-collections.js","names":[],"sources":["../../src/vite-plugin/web-collections.ts"],"sourcesContent":["/**\n * Manifest → web collection selection (single source of truth).\n *\n * The browser client data runtime (`@happyvertical/smrt-web`, #1761) consumes\n * one typed collection definition per API-exposed REST collection. Four\n * co-managed emission surfaces need the SAME selection and field rules, or the\n * emitted runtime values and their declared types drift apart:\n *\n * - the `\\0smrt:web` runtime virtual module (JSON literal — {@link generateWebModule})\n * - the `@happyvertical/smrt-virt-web` ambient d.ts (vite-plugin)\n * - the physical `@smrt/web` d.ts (prebuild, for `tsc`-only consumers)\n * - the dependency-free type mirror in `@happyvertical/smrt-web`\n *\n * The three generated sites import {@link selectWebCollectionEntries} from\n * here so the value emission and the type emission can never disagree. The hand-written mirror\n * is deliberately smrt-dependency-free, so its copy cannot import these types;\n * keep it textually in sync when the shape here changes. The per-collection SHAPE\n * (name/objectRef/className/endpoint/idField/actions/fields/relationships) is built by the\n * ONE {@link buildWebCollectionDefinition}, shared by the runtime emission AND\n * the #1764 {@link computeWebManifestHash} shape digest — so the emitted shape\n * and the hashed shape can never drift (a drift would let the hash under-cover a\n * change → stale client caches).\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n customActionParameterInputName,\n isCrudOperation,\n isCrudToolAction,\n resolveCustomActionMetadata,\n} from '../generators/custom-action.js';\nimport {\n buildToolDescriptors,\n finalizeMcpJsonSchema,\n isCrudAction,\n type ToolDescriptor,\n type ToolFieldMeta,\n type ToolIdType,\n type ToolRouteDescriptor,\n} from '../generators/tool-schema.js';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport { readFieldEnum } from '../registry/manifest-field-merge.js';\nimport type {\n FieldDefinition,\n FieldUIHints,\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { isFieldUIWidget } from '../ui-metadata.js';\nimport {\n resolveApiActionRouteConfig,\n resolveApiActionSet,\n} from './sveltekit-generator.js';\n\n/**\n * Field types that are relationship pseudo-columns rather than persisted\n * public-DTO columns — they never appear on the wire as scalar values.\n */\nconst RELATIONSHIP_FIELD_TYPES: ReadonlySet<FieldDefinition['type']> = new Set([\n 'oneToMany',\n 'manyToMany',\n]);\n\n/**\n * Field types that describe a relationship to another model. A superset of\n * {@link RELATIONSHIP_FIELD_TYPES}: `foreignKey`/`crossPackageRef` are persisted\n * scalar id columns (they DO appear on the wire), while `oneToMany`/`manyToMany`\n * are pseudo-columns — but all four carry a `related` edge to a sibling model.\n */\nconst RELATIONSHIP_EDGE_TYPES: ReadonlySet<FieldDefinition['type']> = new Set([\n 'foreignKey',\n 'crossPackageRef',\n 'oneToMany',\n 'manyToMany',\n]);\n\n/**\n * The field types that actually reach an emitted web collection definition:\n * everything {@link buildWebFieldDefinitions} does not skip. Relationship\n * pseudo-columns (`oneToMany`/`manyToMany`) and STI `meta` internals never\n * appear on the wire, so they are excluded from the union — keeping the\n * declared type honest about what a client can observe. Co-managed with the\n * `SmrtWebFieldType` unions in the ambient `@happyvertical/smrt-virt-web`\n * d.ts, the physical `@smrt/web` d.ts, and the hand-written\n * `@happyvertical/smrt-web` mirror.\n */\nexport type WebFieldType = Exclude<\n FieldDefinition['type'],\n 'meta' | 'oneToMany' | 'manyToMany'\n>;\n\n/** Informational per-column metadata carried by a collection definition. */\nexport interface WebFieldDefinition {\n type: WebFieldType;\n required?: boolean;\n /** Whether the public field contract explicitly permits `null`. */\n nullable?: boolean;\n default?: unknown;\n /**\n * The developer-authored `@field({ description })` (#2046) — the same text\n * that feeds OpenAPI and MCP schemas, now available to browser UIs as the\n * seed for end-user field help. Sensitive/transient fields are excluded\n * from emission entirely, so their descriptions never ship to the client.\n */\n description?: string;\n /** Static `@field({ ui })` hints (#2046) — the field-policy rail seed. */\n ui?: FieldUIHints;\n}\n\n/** The relationship kinds a web collection edge can describe. */\nexport type WebRelationshipKind =\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\n/**\n * One manifest-derived relationship edge from a collection to a sibling REST\n * collection. Consumed by the browser client-data runtime to invalidate\n * dependent collection caches when this collection is mutated (#1761) — the\n * cache-invalidation graph is derived entirely from these edges, never\n * hand-wired. SMRT-owned data: no client-engine type appears here.\n */\nexport interface WebRelationship {\n /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */\n field: string;\n /** The relationship kind, mirroring the manifest field type. */\n kind: WebRelationshipKind;\n /** REST collection name the edge resolves to (e.g. `ad_groups`). */\n relatedCollection: string;\n}\n\n/** One selected REST collection and the model that owns its definition. */\nexport interface WebCollectionEntry {\n /** REST collection name (pluralized), e.g. `products`. */\n collection: string;\n /** The manifest object that owns this collection's definition. */\n obj: SmartObjectDefinition;\n /** Sorted set of exposed CRUD + custom actions. */\n actions: string[];\n}\n\n/**\n * One canonical WebMCP tool candidate. `obj` owns the REST collection and row\n * contract; `actionHost` declares the action. They differ for custom methods\n * declared on a `SmrtCollection` subclass.\n */\nexport interface WebMcpToolEntry {\n collection: string;\n obj: SmartObjectDefinition;\n actionHost: SmartObjectDefinition;\n action: string;\n}\n\n/**\n * Transport-complete, data-only definition for one generated browser tool.\n * Unlike {@link WebCollectionEntry}, this shape does not imply that a list\n * route or cache-backed browser collection exists.\n */\nexport interface WebMcpToolDefinition extends ToolDescriptor {\n /** Stable definition identity is `(collection, action)`. */\n collection: string;\n /** Canonical qualified identity of the row model owning the collection. */\n objectRef: string;\n /** Row-model class used by the shared MCP tool vocabulary. */\n className: string;\n /** Path below the generated API base path. */\n endpoint: string;\n idField: string;\n idType: ToolIdType;\n /** Always present so a fetch-only registrar needs no action heuristics. */\n route: ToolRouteDescriptor;\n /** Cache-invalidation edges for materialized sibling collections. */\n relationships: WebRelationship[];\n}\n\n/**\n * Deterministic string comparator shared by every manifest-derived ordering\n * in this module and by consumers that need to sort manifest objects the\n * same way generated declarations do (#2749).\n */\nexport function compareText(left: string, right: string): number {\n if (left < right) return -1;\n if (left > right) return 1;\n return 0;\n}\n\n/**\n * Deterministic manifest iteration order shared by every consumer that must\n * visit manifest objects the same way (#2749, #2754): the manifest index,\n * generated route emission, and the client-declaration mirror of emitted\n * CRUD surfaces. Sorting by qualified identity (falling back to the manifest\n * key) resolves shared-endpoint last-writer-wins ties identically regardless\n * of manifest scan/insertion order; unique manifest keys make the secondary\n * key compare a total tie-break.\n */\nexport function orderedManifestObjectEntries(\n manifest: SmartObjectManifest,\n): Array<[string, SmartObjectDefinition]> {\n return Object.entries(manifest.objects).sort(\n ([leftKey, left], [rightKey, right]) =>\n compareText(\n left.qualifiedName || leftKey,\n right.qualifiedName || rightKey,\n ) || compareText(leftKey, rightKey),\n );\n}\n\nfunction manifestObjectPackage(\n manifestKey: string | undefined,\n obj: SmartObjectDefinition,\n): string | undefined {\n if (obj.packageName) return obj.packageName;\n const qualifiedName = obj.qualifiedName || manifestKey;\n const separator = qualifiedName?.lastIndexOf(':') ?? -1;\n return separator > 0 ? qualifiedName?.slice(0, separator) : undefined;\n}\n\ninterface ManifestObjectIndex {\n byExactName: Map<string, SmartObjectDefinition>;\n byPackageAndClass: Map<string, SmartObjectDefinition>;\n bySimpleName: Map<string, SmartObjectDefinition>;\n packageByObject: WeakMap<SmartObjectDefinition, string>;\n objectRefByObject: WeakMap<SmartObjectDefinition, string>;\n}\n\nconst manifestObjectIndexes = new WeakMap<\n SmartObjectManifest['objects'],\n ManifestObjectIndex\n>();\n\nfunction packageAndClassKey(packageName: string, className: string): string {\n return `${packageName}\\0${className}`;\n}\n\nfunction getManifestObjectIndex(\n manifest: SmartObjectManifest,\n): ManifestObjectIndex {\n const cached = manifestObjectIndexes.get(manifest.objects);\n if (cached) return cached;\n\n const entries = orderedManifestObjectEntries(manifest);\n const index: ManifestObjectIndex = {\n byExactName: new Map(),\n byPackageAndClass: new Map(),\n bySimpleName: new Map(),\n packageByObject: new WeakMap(),\n objectRefByObject: new WeakMap(),\n };\n\n for (const [manifestKey, candidate] of entries) {\n if (!index.byExactName.has(manifestKey)) {\n index.byExactName.set(manifestKey, candidate);\n }\n if (\n candidate.qualifiedName &&\n !index.byExactName.has(candidate.qualifiedName)\n ) {\n index.byExactName.set(candidate.qualifiedName, candidate);\n }\n if (!index.bySimpleName.has(candidate.className)) {\n index.bySimpleName.set(candidate.className, candidate);\n }\n\n const packageName = manifestObjectPackage(manifestKey, candidate);\n if (packageName) {\n if (!index.packageByObject.has(candidate)) {\n index.packageByObject.set(candidate, packageName);\n }\n const packageKey = packageAndClassKey(packageName, candidate.className);\n if (!index.byPackageAndClass.has(packageKey)) {\n index.byPackageAndClass.set(packageKey, candidate);\n }\n }\n\n // New manifests carry `qualifiedName` on each object. Legacy aggregated\n // manifests can omit it from the object body while retaining a qualified\n // manifest key, so retain that equally canonical identity for browser\n // definitions rather than falling back to an ambiguous simple class name.\n const objectRef = candidate.qualifiedName?.includes(':')\n ? candidate.qualifiedName\n : manifestKey.includes(':')\n ? manifestKey\n : packageName\n ? `${packageName}:${candidate.className}`\n : undefined;\n if (objectRef && !index.objectRefByObject.has(candidate)) {\n index.objectRefByObject.set(candidate, objectRef);\n }\n }\n\n manifestObjectIndexes.set(manifest.objects, index);\n return index;\n}\n\nfunction indexedManifestObjectPackage(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): string | undefined {\n const index = getManifestObjectIndex(manifest);\n return (\n index.packageByObject.get(obj) || manifestObjectPackage(undefined, obj)\n );\n}\n\n/**\n * Return the canonical qualified model identity carried by a web collection.\n * Field-policy APIs require this `@package/name:ClassName` reference; a simple\n * class name is ambiguous in aggregated manifests and must never reach a\n * browser definition.\n */\nfunction webCollectionObjectRef(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): string {\n const objectRef = getManifestObjectIndex(manifest).objectRefByObject.get(obj);\n if (objectRef) return objectRef;\n\n throw new Error(\n `[smrt] Cannot emit web collection definition for ${obj.className}: ` +\n 'a qualifiedName, qualified manifest key, or packageName is required',\n );\n}\n\n/**\n * Resolve a manifest object deterministically across aggregated packages.\n *\n * Qualified references win exactly. Simple references prefer the referencing\n * object's package, then fall back to a stable qualified/key identity. This is\n * important because aggregated manifests can contain duplicate class names and\n * their object insertion order reflects package discovery order.\n */\nexport function findManifestObjectByName(\n manifest: SmartObjectManifest,\n name: string,\n owner?: SmartObjectDefinition,\n): SmartObjectDefinition | undefined {\n const index = getManifestObjectIndex(manifest);\n const exact = name.includes(':') ? index.byExactName.get(name) : undefined;\n if (exact) return exact;\n\n const ownerPackage = owner\n ? indexedManifestObjectPackage(manifest, owner)\n : undefined;\n const packageLocal = ownerPackage\n ? index.byPackageAndClass.get(packageAndClassKey(ownerPackage, name))\n : undefined;\n\n return packageLocal || index.bySimpleName.get(name);\n}\n\n/**\n * Normalize a relationship field's `related` value to a resolvable class name.\n *\n * Thunk forward-ref decorators — `@foreignKey(() => Scene)`, used heavily in\n * video/voice for models that reference a class declared later — used to\n * serialize as the RAW arrow-function source string `\"() => Scene\"` in the\n * manifest, which neither `className` nor `qualifiedName` matches. The scanner\n * now unwraps the thunk itself (#2379), so this only has to cover manifests\n * generated before that fix. Extract the target class name from the thunk so\n * the edge resolves. Plain (`\"Scene\"`) and qualified\n * (`\"@happyvertical/smrt-assets:Asset\"`) forms contain no `=>` and pass through\n * untouched — the qualified `:` separator is preserved.\n *\n * Kept local to the relationship-edge path on purpose: extends-chain resolution\n * never sees a thunk, so `findManifestObjectByName` and the scanner stay\n * unchanged.\n */\nfunction normalizeRelatedName(related: string): string {\n const thunk = related.match(/=>\\s*([A-Za-z_$][\\w$]*)/);\n return thunk ? thunk[1] : related.trim();\n}\n\n/**\n * True when `obj` is (transitively) a SmrtCollection subclass. Collection\n * classes describe access, not row shapes, so they never become web\n * collection definitions.\n *\n * NOTE: deliberately stronger than sveltekit-generator's private\n * `isCollectionClass`, which only inspects the direct base / a type argument.\n * A deeper subclass (`SpecialWidgetCollection extends WidgetCollection`)\n * carries no type argument of its own; without walking the extends chain it\n * would be mistaken for a model and claim its base model's REST collection.\n */\nexport function isCollectionManifestClass(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n seen: Set<string> = new Set(),\n): boolean {\n // Truthy check (not `!== undefined`) mirrors the scanner's own\n // manifest-generator: a scanner that emits `extendsTypeArg: null` for a\n // non-generic base must not be misread as a collection.\n if (obj.extends === 'SmrtCollection' || obj.extendsTypeArg) {\n return true;\n }\n const parentName = obj.extendsQualified || obj.extends;\n if (!parentName || seen.has(parentName)) return false;\n seen.add(parentName);\n const parent = findManifestObjectByName(manifest, parentName, obj);\n return parent ? isCollectionManifestClass(manifest, parent, seen) : false;\n}\n\n/**\n * Resolve the row model carried by a collection class.\n *\n * An explicit generic on any ancestor is authoritative. Conventional\n * `FooCollection` → `Foo` lookup is only a fallback after the full ancestry\n * has been checked, so an unrelated `SpecialFoo` cannot override an inherited\n * `FooCollection<Foo>` contract.\n */\nfunction collectionAncestry(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): SmartObjectDefinition[] {\n const ancestry: SmartObjectDefinition[] = [];\n const seen = new Set<string>();\n let candidate: SmartObjectDefinition | undefined = obj;\n\n while (candidate) {\n ancestry.push(candidate);\n const parentName = candidate.extendsQualified || candidate.extends;\n if (!parentName || seen.has(parentName)) break;\n seen.add(parentName);\n candidate = findManifestObjectByName(manifest, parentName, candidate);\n }\n return ancestry;\n}\n\n/**\n * Resolve the authoritative collection item type name even when a partial\n * manifest omits the item definition itself.\n */\ninterface CollectionItemReference {\n name: string;\n owner: SmartObjectDefinition;\n object?: SmartObjectDefinition;\n}\n\nfunction resolveCollectionItemCandidate(\n manifest: SmartObjectManifest,\n name: string,\n owner: SmartObjectDefinition,\n): CollectionItemReference {\n const object = findManifestObjectByName(manifest, name, owner);\n const ownerPackage = indexedManifestObjectPackage(manifest, owner);\n\n if (!name.includes(':') && ownerPackage) {\n const objectPackage = object\n ? indexedManifestObjectPackage(manifest, object)\n : undefined;\n if (objectPackage !== ownerPackage) {\n return {\n name: `${ownerPackage}:${name}`,\n owner,\n };\n }\n }\n\n return { name, owner, object };\n}\n\nfunction resolveCollectionItemReference(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): CollectionItemReference | undefined {\n const ancestry = collectionAncestry(manifest, obj);\n for (const collectionClass of ancestry) {\n if (collectionClass.extendsTypeArg) {\n return resolveCollectionItemCandidate(\n manifest,\n collectionClass.extendsTypeArg,\n collectionClass,\n );\n }\n }\n\n let fallback: CollectionItemReference | undefined;\n for (const collectionClass of ancestry) {\n if (!collectionClass.className.endsWith('Collection')) continue;\n const conventionalName = collectionClass.className.slice(\n 0,\n -'Collection'.length,\n );\n const candidate = resolveCollectionItemCandidate(\n manifest,\n conventionalName,\n collectionClass,\n );\n if (candidate.object) return candidate;\n fallback ??= candidate;\n }\n\n return fallback;\n}\n\nexport function resolveCollectionItemTypeName(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): string | undefined {\n return resolveCollectionItemReference(manifest, obj)?.name;\n}\n\nexport function resolveCollectionItemObject(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): SmartObjectDefinition | undefined {\n return resolveCollectionItemReference(manifest, obj)?.object;\n}\n\n/**\n * True when some ancestor model maps to the SAME REST collection (a shared STI\n * table). The STI base model owns the shared table's single definition.\n */\nexport function isStiChildModel(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): boolean {\n const seen = new Set<string>();\n let child = obj;\n let parentName = child.extendsQualified || child.extends;\n while (parentName && !seen.has(parentName)) {\n seen.add(parentName);\n const parent = findManifestObjectByName(manifest, parentName, child);\n if (!parent) return false;\n if (parent.collection === obj.collection) return true;\n child = parent;\n parentName = parent.extendsQualified || parent.extends;\n }\n return false;\n}\n\n/**\n * Select one entry per REST collection whose exposed action set satisfies\n * `qualifies` — STI children folding into their base model, collection classes\n * excluded. Uses the canonical {@link resolveApiActionSet} so the exposed-action\n * set matches exactly what the REST/SvelteKit generators actually emit. Shared\n * by {@link selectWebCollectionEntries} (list-qualified — materializable\n * collections) and {@link selectWebEtagSaltEntries} (get-OR-list — every model\n * with a read route the ETag salt must cover).\n */\nfunction selectEntriesQualifiedBy(\n manifest: SmartObjectManifest,\n qualifies: (actions: ReadonlySet<string>) => boolean,\n): WebCollectionEntry[] {\n const byCollection = new Map<\n string,\n WebCollectionEntry & { isStiChild: boolean }\n >();\n\n // Manifest key order follows scan/discovery order, which is not stable\n // across runs (#2749). STI base-vs-child precedence below is independent\n // of that order, but two candidates of EQUAL isStiChild status sharing one\n // `collection` (e.g. two unrelated models, or two STI children whose base\n // is absent/not qualified) are not — the map keeps whichever arrives\n // first. Pre-sort the iteration source so that tie is resolved by\n // qualified name instead of scan order. Unlike\n // {@link selectWebMcpToolEntries}'s sort, this falls back to the manifest\n // key rather than the strict {@link webCollectionObjectRef} (which throws\n // without a qualifiedName/packageName): every candidate must be ordered\n // here, including ones that never reach {@link buildWebCollectionDefinition}\n // and so never need a resolvable object ref.\n const sortedEntries = Object.entries(manifest.objects)\n .filter(([, obj]) => !isFrameworkBaseClass(obj.className, obj.packageName))\n .sort(\n ([leftKey, left], [rightKey, right]) =>\n compareText(\n left.qualifiedName || leftKey,\n right.qualifiedName || rightKey,\n ) || compareText(leftKey, rightKey),\n );\n\n for (const [, obj] of sortedEntries) {\n if (isCollectionManifestClass(manifest, obj)) continue;\n\n // Pass the manifest: without it the wire-ability heuristic cannot tell a\n // model class from an options interface, and this selector would qualify a\n // collection on an action the route emitters withhold (#2686).\n const exposedActions = resolveApiActionSet(obj, manifest);\n if (!qualifies(exposedActions)) continue;\n\n const isStiChild = isStiChildModel(manifest, obj);\n const existing = byCollection.get(obj.collection);\n // One definition per REST collection. The STI BASE model owns it: a child\n // only wins while no base has been recorded yet. Between two candidates\n // of equal status, the pre-sort above makes the winner deterministic.\n if (existing && !(existing.isStiChild && !isStiChild)) continue;\n\n byCollection.set(obj.collection, {\n collection: obj.collection,\n obj,\n actions: [...exposedActions].sort(),\n isStiChild,\n });\n }\n\n // The winner for each collection is now independent of scan order (above);\n // sort the final list by collection name so the emitted declarations are\n // too.\n return [...byCollection.values()]\n .sort((left, right) => compareText(left.collection, right.collection))\n .map(({ collection, obj, actions }) => ({\n collection,\n obj,\n actions,\n }));\n}\n\n/**\n * Select the manifest entries that become web collection definitions: one per\n * REST collection, STI children folding into their base model, collection\n * classes excluded, and only models that expose `list` (a read surface is\n * required to MATERIALIZE a client collection — that is what persists).\n */\nexport function selectWebCollectionEntries(\n manifest: SmartObjectManifest,\n): WebCollectionEntry[] {\n return selectEntriesQualifiedBy(manifest, (actions) => actions.has('list'));\n}\n\n/** Walk to the highest STI ancestor that owns the same REST collection. */\nfunction resolveStiCollectionOwner(\n manifest: SmartObjectManifest,\n obj: SmartObjectDefinition,\n): SmartObjectDefinition {\n const seen = new Set<string>();\n let owner = obj;\n let parentName = owner.extendsQualified || owner.extends;\n while (parentName && !seen.has(parentName)) {\n seen.add(parentName);\n const parent = findManifestObjectByName(manifest, parentName, owner);\n if (!parent || parent.collection !== obj.collection) break;\n owner = parent;\n parentName = owner.extendsQualified || owner.extends;\n }\n return owner;\n}\n\n/**\n * Select every generated WebMCP action independently of collection\n * materialization. Object actions establish the owning row model first;\n * collection-class actions then supplement missing `(collection, action)`\n * identities. Therefore an object action wins an exact collision, while all\n * ordering is independent of manifest insertion order.\n */\nexport function selectWebMcpToolEntries(\n manifest: SmartObjectManifest,\n): WebMcpToolEntry[] {\n // The framework's own abstract base classes are scaffolding, not\n // resources — filtered once here so every loop below (object owners,\n // custom-action hosts, and the collection-class ownership pass) excludes\n // them, regardless of config (#2642).\n const manifestObjects = Object.values(manifest.objects).filter(\n (obj) => !isFrameworkBaseClass(obj.className, obj.packageName),\n );\n const sortedObjects = [...manifestObjects].sort((left, right) =>\n compareText(\n webCollectionObjectRef(manifest, left),\n webCollectionObjectRef(manifest, right),\n ),\n );\n const objectOwners = new Map<string, WebCollectionEntry>();\n\n for (const candidate of sortedObjects) {\n if (isCollectionManifestClass(manifest, candidate)) continue;\n const existing = objectOwners.get(candidate.collection);\n const candidateIsChild = isStiChildModel(manifest, candidate);\n const existingIsChild = existing\n ? isStiChildModel(manifest, existing.obj)\n : false;\n if (existing && !(existingIsChild && !candidateIsChild)) continue;\n\n objectOwners.set(candidate.collection, {\n collection: candidate.collection,\n obj: candidate,\n actions: [],\n });\n }\n\n const entries = new Map<string, WebMcpToolEntry>();\n const ownerObjects = [...objectOwners.values()]\n .sort(\n (left, right) =>\n compareText(left.collection, right.collection) ||\n compareText(\n webCollectionObjectRef(manifest, left.obj),\n webCollectionObjectRef(manifest, right.obj),\n ),\n )\n .map((entry) => entry.obj);\n const ownerSet = new Set(ownerObjects);\n // Owners go first so the STI base wins an exact object-action collision.\n // Remaining models still contribute unique custom actions on the shared\n // collection instead of disappearing behind the owning row definition.\n const objectActionHosts = [\n ...ownerObjects,\n ...sortedObjects.filter(\n (candidate) =>\n !isCollectionManifestClass(manifest, candidate) &&\n !ownerSet.has(candidate),\n ),\n ];\n\n // Which CRUD verbs are actually emitted, keyed by COLLECTION rather than by\n // host. The tool id namespace is `${collectionClassName}_${action}`, so it is\n // shared by every host mapping to one collection — an STI base and its\n // children, or two models declaring the same collection. Asking only \"does\n // THIS host emit `list`?\" would let a host that excludes `list` but declares\n // `List()` claim `product_list`, after which a sibling host's real `list`\n // hits the fold-dedupe and is dropped. That is the impostor outranking the\n // verb again, one door over (#2648).\n const emittedCrudByCollection = new Map<string, Set<string>>();\n for (const host of [\n ...objectActionHosts,\n ...manifestObjects.filter((candidate) =>\n isCollectionManifestClass(manifest, candidate),\n ),\n ]) {\n const collection = objectOwners.get(host.collection)?.collection;\n if (!collection) continue;\n const bucket = emittedCrudByCollection.get(collection) ?? new Set<string>();\n for (const action of resolveApiActionSet(host, manifest)) {\n if (isCrudOperation(action)) bucket.add(action);\n }\n emittedCrudByCollection.set(collection, bucket);\n }\n\n for (const actionHost of objectActionHosts) {\n const owner = objectOwners.get(actionHost.collection);\n if (!owner) continue;\n const emittedCrud =\n emittedCrudByCollection.get(owner.collection) ?? new Set<string>();\n for (const action of [...resolveApiActionSet(actionHost, manifest)].sort(\n compareText,\n )) {\n // A browser tool id is lowercased whole (`${prefix}_${action}`), so this\n // namespace is case-folded even though the API action set that feeds it\n // is not. Two guards, in order (#2648):\n //\n // 1. An action that is not exactly a CRUD verb but folds onto one is a\n // custom method wearing the generated tool's id. Drop it rather than\n // letting it win: `compareText` sorts `List` BEFORE `list`, so a\n // first-wins dedupe alone would keep the impostor and lose the real\n // CRUD tool. REST keeps both — `/products/List` is a distinct route\n // from `/products` — so this reservation belongs here, not in\n // `resolveApiActionSet`.\n //\n // CONDITIONAL on the CRUD verb actually being in this host's action\n // set. `generators/mcp.ts` reserves unconditionally because its\n // dispatch parses the verb out of the tool id, so `${obj}_list` can\n // only ever run the built-in list. A WebMCP descriptor is different:\n // a non-CRUD action carries its own `route` from\n // `resolveApiActionRouteConfig`, so with `api: { include: ['List'] }`\n // the id `product_list` genuinely dispatches to `/products/List` and\n // is the ONLY tool for that id. Dropping it there would make a\n // custom-action-only model undiscoverable while its REST route still\n // answers (#2648).\n if (\n isCrudToolAction(action) &&\n !isCrudOperation(action) &&\n emittedCrud.has(action.toLowerCase())\n ) {\n continue;\n }\n // 2. Anything still colliding after that is two custom methods differing\n // only in case; keep the first so one id yields one descriptor.\n const identity = `${owner.collection}\\0${action.toLowerCase()}`;\n if (entries.has(identity)) continue;\n entries.set(identity, {\n collection: owner.collection,\n obj: owner.obj,\n // CRUD is a shared collection identity owned by the STI base even if\n // a child is the manifest entry that exposed it. Custom actions retain\n // their declaring host so route overrides and parameters stay exact.\n actionHost: isCrudAction(action) ? owner.obj : actionHost,\n action,\n });\n }\n }\n\n // SvelteKit emits collection-class routes in manifest order and a later\n // class replaces an earlier handler at the same path. Follow that ownership\n // rule so the selected parameter/route metadata describes the live handler.\n // Object actions remain authoritative over collection-class collisions.\n for (const collectionClass of manifestObjects) {\n if (!isCollectionManifestClass(manifest, collectionClass)) continue;\n const itemObject = resolveCollectionItemObject(manifest, collectionClass);\n if (!itemObject) continue;\n const owner =\n objectOwners.get(collectionClass.collection)?.obj ??\n resolveStiCollectionOwner(manifest, itemObject);\n\n // Same collection-level set as loop 1. A collection class's OWN action set\n // never contains an exact CRUD verb — `resolveApiActionSet` starts a\n // collection class at an empty set and skips the shared CRUD verb list\n // (`isCrudOperation`, generators/custom-action.ts, #2665) —\n // so a per-host set here would always be empty and the guard dead.\n const collectionEmittedCrud =\n emittedCrudByCollection.get(collectionClass.collection) ??\n new Set<string>();\n for (const action of [\n ...resolveApiActionSet(collectionClass, manifest),\n ].sort(compareText)) {\n // Same two guards, same order and conditionality as the object loop\n // above — and the same key shape. Both loops share `entries`, so a case-sensitive key here\n // would miss the lowercased one written there and insert a second entry\n // for the same tool id, breaking the ownership rule this loop exists to\n // enforce (#2648).\n if (\n isCrudToolAction(action) &&\n !isCrudOperation(action) &&\n collectionEmittedCrud.has(action.toLowerCase())\n ) {\n continue;\n }\n const identity = `${collectionClass.collection}\\0${action.toLowerCase()}`;\n const existing = entries.get(identity);\n if (\n existing &&\n !isCollectionManifestClass(manifest, existing.actionHost)\n ) {\n continue;\n }\n entries.set(identity, {\n collection: collectionClass.collection,\n obj: owner,\n actionHost: collectionClass,\n action,\n });\n }\n }\n\n return [...entries.values()].sort(\n (left, right) =>\n compareText(left.collection, right.collection) ||\n compareText(left.action, right.action) ||\n compareText(\n webCollectionObjectRef(manifest, left.actionHost),\n webCollectionObjectRef(manifest, right.actionHost),\n ),\n );\n}\n\nfunction requireRoutePathInputs(\n descriptor: ToolDescriptor | undefined,\n): ToolDescriptor | undefined {\n if (!descriptor) return undefined;\n const route = descriptor.route;\n if (!route) return descriptor;\n\n const pathParameters = route.path\n .filter((segment) => /^\\[[^\\]]+\\]$/.test(segment))\n .map((segment) => segment.slice(1, -1));\n if (pathParameters.length === 0) return descriptor;\n\n const schema = descriptor.inputSchema;\n const properties = {\n ...((schema.properties as Record<string, unknown> | undefined) ?? {}),\n };\n const required = new Set(\n Array.isArray(schema.required)\n ? schema.required.filter(\n (name): name is string => typeof name === 'string',\n )\n : [],\n );\n\n for (const parameterName of pathParameters) {\n const inputName =\n Object.entries(route.parameterAliases ?? {}).find(\n ([, originalName]) => originalName === parameterName,\n )?.[0] ?? parameterName;\n properties[inputName] ??= {\n type: 'string',\n description: `Required route parameter: ${parameterName}`,\n };\n required.add(inputName);\n }\n\n return {\n ...descriptor,\n inputSchema: finalizeMcpJsonSchema({\n ...schema,\n properties,\n required: [...required],\n }),\n };\n}\n\n/**\n * Select the entries the ETag salt (#1764) must cover: every api-exposed model\n * with a GENERATED READ ROUTE — `list` OR `get`. Broader than\n * {@link selectWebCollectionEntries} on purpose: a get-only model\n * (`api: { include: ['get'] }`) has no materializable client collection (so it\n * never persists), but its generated GET route IS salted, so a shape-only change\n * to it must still change the salt — otherwise a client holding the old concrete\n * ETag would get a zero-query 304 after a shape-only deploy (the #1765 gap the\n * salt closes). Not exported: only {@link computeWebManifestHash} consumes it.\n */\nfunction selectWebEtagSaltEntries(\n manifest: SmartObjectManifest,\n): WebCollectionEntry[] {\n return selectEntriesQualifiedBy(\n manifest,\n (actions) => actions.has('list') || actions.has('get'),\n );\n}\n\n/**\n * Build the per-collection web-collection definition literal — the SINGLE\n * source of truth for the shape emitted by {@link generateWebModule} and hashed\n * by {@link computeWebManifestHash}. Building it in ONE place is a\n * cache-coherency requirement: if the emitted shape and the hashed shape were\n * built independently, adding a field to one and not the other would let the\n * hash silently UNDER-cover a shape change, so persisted caches would not drop\n * and stale rows would hydrate into new code.\n */\nexport function buildWebCollectionDefinition(\n entry: WebCollectionEntry,\n manifest: SmartObjectManifest,\n): {\n name: string;\n objectRef: string;\n className: string;\n endpoint: string;\n idField: string;\n actions: string[];\n fields: Record<string, WebFieldDefinition>;\n relationships: WebRelationship[];\n} {\n return {\n name: entry.collection,\n objectRef: webCollectionObjectRef(manifest, entry.obj),\n className: entry.obj.className,\n endpoint: `/${entry.collection}`,\n idField: 'id',\n actions: entry.actions,\n fields: buildWebFieldDefinitions(entry.obj),\n relationships: buildWebRelationships(entry.obj, manifest),\n };\n}\n\n/**\n * Build the informational per-field metadata for a web collection definition:\n * the persisted public-DTO columns only. Relationship pseudo-fields, STI meta\n * internals, transient (unpersisted) and sensitive (wire-stripped) fields are\n * excluded — they are not columns a client reads back over the REST surface.\n */\nexport function buildWebFieldDefinitions(\n obj: SmartObjectDefinition,\n): Record<string, WebFieldDefinition> {\n const fields: Record<string, WebFieldDefinition> = {};\n for (const [fieldName, field] of Object.entries(obj.fields ?? {})) {\n if (RELATIONSHIP_FIELD_TYPES.has(field.type)) continue;\n if (field.type === 'meta') continue;\n if (field.transient) continue;\n if (field.sensitive) continue;\n const ui = sanitizeFieldUIHints(field._meta?.ui);\n // Scanner manifests expose `description` top-level; runtime-registry\n // manifests (computeRuntimeWebManifestHash → registeredFieldsToManifest)\n // keep it under `_meta` only. Read both so the build-time and runtime\n // shape digests agree — otherwise every described field would latch a\n // false `updateAvailable.contract` signal on APIGenerator SSE connects.\n const description = readFieldDescription(field);\n fields[fieldName] = {\n // Safe: the three skips above are exactly the types WebFieldType excludes.\n type: field.type as WebFieldType,\n ...(field.required !== undefined ? { required: field.required } : {}),\n ...(field._meta?.nullable === true ? { nullable: true } : {}),\n ...(field.default !== undefined ? { default: field.default } : {}),\n ...(description !== undefined ? { description } : {}),\n ...(ui ? { ui } : {}),\n };\n }\n return fields;\n}\n\n/** Top-level `description` with `_meta.description` fallback (see call site). */\nfunction readFieldDescription(field: FieldDefinition): string | undefined {\n if (typeof field.description === 'string' && field.description !== '') {\n return field.description;\n }\n const metaDescription = field._meta?.description;\n if (typeof metaDescription === 'string' && metaDescription !== '') {\n return metaDescription;\n }\n return undefined;\n}\n\n/**\n * Narrow a manifest `_meta.ui` bag to the known {@link FieldUIHints} keys with\n * per-key type guards. `_meta` is an open bag, so authored junk (wrong-typed\n * values, arbitrary extra keys) must not reach the emitted client module — the\n * emitted value also feeds the #1764 shape digest, so unknown unstable keys\n * would churn the hash. Returns undefined when nothing valid remains, so\n * hint-less fields emit no `ui` key at all (existing shapes stay byte-stable).\n */\nfunction sanitizeFieldUIHints(value: unknown): FieldUIHints | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n const raw = value as Record<string, unknown>;\n const ui: FieldUIHints = {\n ...(typeof raw.basic === 'boolean' ? { basic: raw.basic } : {}),\n ...(typeof raw.group === 'string' && raw.group !== ''\n ? { group: raw.group }\n : {}),\n ...(typeof raw.order === 'number' && Number.isFinite(raw.order)\n ? { order: raw.order }\n : {}),\n ...(typeof raw.locked === 'boolean' ? { locked: raw.locked } : {}),\n ...(isFieldUIWidget(raw.widget) ? { widget: raw.widget } : {}),\n };\n return Object.keys(ui).length > 0 ? ui : undefined;\n}\n\n/**\n * Build the manifest-derived relationship edges for a web collection\n * definition (#1761): one entry per relationship field (`foreignKey`,\n * `crossPackageRef`, `oneToMany`, `manyToMany`) whose `related` target resolves\n * to another API-exposed REST collection.\n *\n * These edges drive relationship-derived cache invalidation in the browser\n * client-data runtime: mutating this collection invalidates the caches of the\n * collections named here. The invalidation graph is thus derived entirely from\n * the manifest — no hand-wired cache keys.\n *\n * An edge is SKIPPED (not emitted) when:\n * - `related` is missing, or\n * - `related` cannot be resolved to a manifest object (e.g. a cross-package\n * target not present in this package's manifest), or\n * - the resolved target is not itself an API-exposed web collection (no read\n * surface to invalidate — it never appears in {@link selectWebCollectionEntries}).\n *\n * Self-referential edges (a collection related to itself) are kept: the runtime\n * always invalidates the mutated collection anyway, so a self edge is harmless\n * and keeping it avoids a special case.\n */\nexport function buildWebRelationships(\n obj: SmartObjectDefinition,\n manifest: SmartObjectManifest,\n): WebRelationship[] {\n // The set of REST collections that are actually materialized as web\n // collections. An edge to a model outside this set has no client cache to\n // invalidate, so it is dropped.\n const exposedCollections = new Set(\n selectWebCollectionEntries(manifest).map((entry) => entry.collection),\n );\n\n const relationships: WebRelationship[] = [];\n const seen = new Set<string>();\n for (const [fieldName, field] of Object.entries(obj.fields ?? {})) {\n if (!RELATIONSHIP_EDGE_TYPES.has(field.type)) continue;\n if (!field.related) continue;\n\n // Normalize first: `@foreignKey(() => Scene)` thunks serialize as the raw\n // \"() => Scene\" source, which findManifestObjectByName cannot match on its\n // own.\n const target = findManifestObjectByName(\n manifest,\n normalizeRelatedName(field.related),\n obj,\n );\n if (!target) continue;\n if (!exposedCollections.has(target.collection)) continue;\n\n // De-dupe on (field, relatedCollection): a model never declares the same\n // field twice, but guard anyway so the emitted edge list is stable.\n const dedupeKey = `${fieldName}:${target.collection}`;\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n relationships.push({\n field: fieldName,\n kind: field.type as WebRelationshipKind,\n relatedCollection: target.collection,\n });\n }\n return relationships;\n}\n\n/**\n * Build the WebMCP / MCP tool descriptors for a web collection (#1812): one\n * descriptor per exposed action, over the SAME public-DTO fields the definition\n * already exposes (`buildWebFieldDefinitions`). The tool ids match the Node MCP\n * surface (`<class>_<action>`), so a page's WebMCP tools and its MCP-server\n * tools share one vocabulary.\n *\n * Deliberately NOT part of {@link buildWebCollectionDefinition}: descriptors are\n * layered onto the emitted value by {@link generateWebModule} instead, so the\n * #1764 {@link computeWebManifestHash} shape digest keeps hashing ONLY the row\n * shape. That remains safe because descriptor-only inputs (including `idType`)\n * do not alter persisted public rows or read responses, which are the cache\n * contracts this digest protects.\n */\nexport function buildWebToolDescriptors(\n entry: WebCollectionEntry,\n options: { kebabRoutes?: boolean } = {},\n): ToolDescriptor[] {\n return buildWebToolDescriptorsForHost({\n owner: entry.obj,\n actionHost: entry.obj,\n actions: entry.actions,\n options,\n });\n}\n\nfunction buildWebToolDescriptorsForHost(input: {\n owner: SmartObjectDefinition;\n actionHost: SmartObjectDefinition;\n actions: string[];\n options: { kebabRoutes?: boolean };\n collectionHost?: boolean;\n}): ToolDescriptor[] {\n const { owner, actionHost, options, collectionHost = false } = input;\n // Every descriptor id is `${prefix}_${action}` lowercased whole, so this is\n // the one choke point both emission paths pass through: the canonical\n // per-entry `buildWebMcpToolDefinition`, and the legacy per-collection\n // `buildWebToolDescriptors` that fills `collectionDefinitions[].toolDescriptors`.\n // The canonical path is already filtered by `selectWebMcpToolEntries`, so\n // this only bites for the legacy one — where an unfiltered `entry.actions`\n // would emit two `product_list` descriptors and make `validateProspectiveTools`\n // throw `Duplicate WebMCP tool name`, aborting registration of EVERY tool on\n // the page rather than just the colliding pair (#2648).\n //\n // Same two guards, same order and conditionality as\n // `selectWebMcpToolEntries`: drop a cased CRUD impostor only when the verb it\n // shadows is actually in this action set (it must not outrank the real verb,\n // but it is the only tool for that id when the verb is absent), then\n // fold-dedupe whatever still collides.\n const emittedCrudActions = new Set(input.actions.filter(isCrudOperation));\n const seenToolActions = new Set<string>();\n const actions = input.actions.filter((action) => {\n if (\n isCrudToolAction(action) &&\n !isCrudOperation(action) &&\n emittedCrudActions.has(action.toLowerCase())\n ) {\n return false;\n }\n const folded = action.toLowerCase();\n if (seenToolActions.has(folded)) return false;\n seenToolActions.add(folded);\n return true;\n });\n const webFields = buildWebFieldDefinitions(owner);\n const fields: ToolFieldMeta[] = Object.entries(webFields).map(\n ([name, def]) => ({\n name,\n type: def.type,\n ...(def.required !== undefined ? { required: def.required } : {}),\n ...(def.nullable === true ? { nullable: true } : {}),\n ...(def.default !== undefined ? { default: def.default } : {}),\n // #2046: the authored field description flows into the generated JSON\n // Schema (`fieldTypeToJsonSchema` prefers it over the type-derived\n // fallback), so browser-side WebMCP tools document their arguments.\n ...(def.description !== undefined\n ? { description: def.description }\n : {}),\n // #3598: enum / literal-union fields constrain the tool input schema.\n ...(readFieldEnum(owner.fields?.[name])\n ? { enum: readFieldEnum(owner.fields?.[name]) }\n : {}),\n }),\n );\n const descriptors = buildToolDescriptors({\n className: owner.className,\n fields,\n actions,\n customActions: Object.fromEntries(\n actions.map((action) => [\n action,\n resolveCustomActionMetadata({\n actionName: action,\n method: actionHost.methods?.[action],\n apiConfig: actionHost.decoratorConfig?.api,\n ...(collectionHost ? { defaultScope: 'collection' as const } : {}),\n }),\n ]),\n ),\n idType: owner.decoratorConfig.idType,\n ...(owner.displayLabelField\n ? { displayLabelField: owner.displayLabelField }\n : {}),\n });\n\n return descriptors.map((descriptor) => {\n if (isCrudAction(descriptor.action)) return descriptor;\n const method = actionHost.methods?.[descriptor.action];\n const route = resolveApiActionRouteConfig(\n descriptor.action,\n method ?? {},\n actionHost.decoratorConfig?.api,\n { kebabRoutes: options.kebabRoutes },\n collectionHost ? 'collection' : method?.isStatic ? 'collection' : 'item',\n );\n const metadata = resolveCustomActionMetadata({\n actionName: descriptor.action,\n method,\n apiConfig: actionHost.decoratorConfig?.api,\n ...(collectionHost ? { defaultScope: 'collection' as const } : {}),\n });\n const parameterAliases = Object.fromEntries(\n (method?.parameters ?? [])\n .map((parameter) => [\n customActionParameterInputName(metadata, parameter.name),\n parameter.name,\n ])\n .filter(([inputName, parameterName]) => inputName !== parameterName),\n );\n return {\n ...descriptor,\n route: {\n method: route.method,\n scope: route.scope,\n path: route.pathSegments,\n ...(metadata.parameters === undefined ||\n (metadata.parameters.length === 1 &&\n metadata.parameters[0]?.name === 'options')\n ? { optionsBag: true }\n : {}),\n ...(Object.keys(parameterAliases).length > 0\n ? { parameterAliases }\n : {}),\n },\n };\n });\n}\n\nfunction crudToolRoute(action: string): ToolRouteDescriptor {\n switch (action) {\n case 'list':\n return { method: 'GET', scope: 'collection', path: [] };\n case 'get':\n return { method: 'GET', scope: 'item', path: [] };\n case 'create':\n return { method: 'POST', scope: 'collection', path: [] };\n case 'update':\n return { method: 'PUT', scope: 'item', path: [] };\n case 'delete':\n return { method: 'DELETE', scope: 'item', path: [] };\n default:\n throw new Error(`[smrt] Unknown generated CRUD action: ${action}`);\n }\n}\n\n/** Build the canonical, transport-complete definition for one selected tool. */\nexport function buildWebMcpToolDefinition(\n entry: WebMcpToolEntry,\n manifest: SmartObjectManifest,\n options: { kebabRoutes?: boolean } = {},\n): WebMcpToolDefinition {\n const descriptor = requireRoutePathInputs(\n buildWebToolDescriptorsForHost({\n owner: entry.obj,\n actionHost: entry.actionHost,\n actions: [entry.action],\n options,\n collectionHost: isCollectionManifestClass(manifest, entry.actionHost),\n })[0],\n );\n if (!descriptor) {\n throw new Error(\n `[smrt] Failed to build WebMCP descriptor for ${entry.collection}.${entry.action}`,\n );\n }\n\n return {\n collection: entry.collection,\n objectRef: webCollectionObjectRef(manifest, entry.obj),\n className: entry.obj.className,\n endpoint: `/${entry.collection}`,\n idField: 'id',\n idType: entry.obj.decoratorConfig.idType ?? 'uuid',\n relationships: buildWebRelationships(entry.obj, manifest),\n ...descriptor,\n route: descriptor.route ?? crudToolRoute(entry.action),\n };\n}\n\n/** Emit every API-backed WebMCP tool, including non-list models. */\nexport function buildWebMcpToolDefinitions(\n manifest: SmartObjectManifest,\n options: { kebabRoutes?: boolean } = {},\n): WebMcpToolDefinition[] {\n return selectWebMcpToolEntries(manifest).map((entry) =>\n buildWebMcpToolDefinition(entry, manifest, options),\n );\n}\n\n/**\n * Recursively sort object keys so structurally-equal values serialize to the\n * SAME JSON regardless of insertion order. Arrays keep their order (order is\n * semantic for `actions`/`relationships`); objects are rebuilt with keys sorted.\n * Required for {@link computeWebManifestHash} to be replica-stable: two builds\n * that produce the same schema but visit the manifest in a different order (map\n * insertion, scan order) must still hash identically, which a plain\n * `JSON.stringify` of insertion-ordered objects would NOT guarantee.\n */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => canonicalize(entry));\n }\n if (value && typeof value === 'object') {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[key] = canonicalize((value as Record<string, unknown>)[key]);\n }\n return sorted;\n }\n return value;\n}\n\n/**\n * A deterministic, replica-stable digest of the web-collection SHAPE (#1764).\n *\n * The hash covers exactly the thing whose change means either old persisted\n * client rows may mis-hydrate OR a stale read ETag would still 304: the same\n * per-collection definition shape {@link generateWebModule} emits — name,\n * className, endpoint, idField, actions, fields, relationships — built via the\n * SHARED {@link buildWebCollectionDefinition} so the hash can never disagree\n * with what is actually shipped. The shape is CANONICALIZED (keys recursively\n * sorted; see {@link canonicalize}) before hashing, so the same schema always\n * yields the same digest across builds and replicas regardless of manifest\n * iteration order.\n *\n * SCOPE — get-OR-list (broader than materializable collections). Covered by\n * {@link selectWebEtagSaltEntries}, so it includes GET-ONLY models too: those do\n * not persist (no materializable collection), but their generated GET route IS\n * salted with this hash, so a shape-only change to a get-only model must change\n * it or a client holding the old concrete ETag gets a zero-query 304 after a\n * shape-only deploy (the #1765 gap the salt closes). The two consumers both use\n * this one value, so it stays identical between them:\n * - `@happyvertical/smrt-web` persistence (#1764) folds it into the durable\n * namespace, so a contract-changing deploy lands on a fresh namespace and old\n * rows are never found (dropped, not mis-hydrated). Including get-only models\n * here is harmless over-invalidation — only list-materializable collections\n * ever hold a persisted snapshot.\n * - the generated read ETag (#1765 salt, #1764) folds it in so a shape-only\n * deploy (no table write) busts every read validator, get-only routes too.\n *\n * Truncated to the first 16 base64url chars: 96 bits is far more than enough to\n * make an accidental shape collision negligible, and a short constant keeps the\n * emitted module and every persistence key compact.\n */\nexport function computeWebManifestHash(manifest: SmartObjectManifest): string {\n const definitions: Record<string, unknown> = {};\n for (const entry of selectWebEtagSaltEntries(manifest)) {\n definitions[entry.collection] = buildWebCollectionDefinition(\n entry,\n manifest,\n );\n }\n const canonicalJson = JSON.stringify(canonicalize(definitions));\n return createHash('sha256')\n .update(canonicalJson)\n .digest('base64url')\n .slice(0, 16);\n}\n\n/**\n * Generate the data-only `@smrt/web` module used by both producer and consumer\n * Vite plugins. Keeping the emission beside its selection, row definition, and\n * hash helpers makes every browser collection surface use one projection.\n */\nexport function generateWebModule(\n manifest: SmartObjectManifest,\n options: { kebabRoutes?: boolean } = {},\n): string {\n const definitions: Record<string, unknown> = {};\n for (const entry of selectWebCollectionEntries(manifest)) {\n definitions[entry.collection] = {\n ...buildWebCollectionDefinition(entry, manifest),\n toolDescriptors: buildWebToolDescriptors(entry, options),\n };\n }\n\n const manifestHash = computeWebManifestHash(manifest);\n const webMcpToolDefinitions = buildWebMcpToolDefinitions(manifest, options);\n\n return `\n// Auto-generated web collection definitions from SMRT objects (#1761)\n// This file is generated automatically - do not edit\n\nexport const collectionDefinitions = ${JSON.stringify(definitions, null, 2)};\n\n// Canonical browser-tool definitions. This export is independent of list\n// materialization, so get-only and custom-action-only API routes are included.\nexport const webMcpToolDefinitions = ${JSON.stringify(webMcpToolDefinitions, null, 2)};\n\n// Build-time inject of the web-collection shape digest (#1764) — see\n// computeWebManifestHash. A change here means old persisted client rows may\n// mis-hydrate, so persistence namespaces and read ETags key on it.\nexport const manifestHash = ${JSON.stringify(manifestHash)};\n\nexport function getCollectionDefinition(name) {\n const definition = collectionDefinitions[name];\n if (!definition) {\n throw new Error(\n \\`[smrt] Unknown web collection definition: \\${name}. Known: \\${Object.keys(collectionDefinitions).join(', ')}\\`,\n );\n }\n return definition;\n}\n\nexport { collectionDefinitions as default };\n`;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The cookbook file `smrt cookbook apply` writes at the project root. */
|
|
2
|
+
export declare const COOKBOOK_FILE = "smrt.cookbook.json";
|
|
3
|
+
/**
|
|
4
|
+
* Qualified refs of the consumed models the project's cookbook hosts. Returns
|
|
5
|
+
* `[]` when the project has no cookbook. Refs that no consumed package
|
|
6
|
+
* declares are dropped (the shell reports an unknown recipe or feature).
|
|
7
|
+
*
|
|
8
|
+
* @throws When `smrt.cookbook.json` is present but unreadable or invalid.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveCookbookHostedObjects(projectRoot: string, packages: readonly string[]): string[];
|
|
11
|
+
//# sourceMappingURL=cookbook-exposure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookbook-exposure.d.ts","sourceRoot":"","sources":["../../src/vite-preset/cookbook-exposure.ts"],"names":[],"mappings":"AAiBA,0EAA0E;AAC1E,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAyDlD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,MAAM,EAAE,CA+DV"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { manifestExportCandidates } from "../manifest/package-manifest-exports.js";
|
|
2
|
+
import { validateCookbook } from "../cookbook/validate.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
//#region src/vite-preset/cookbook-exposure.ts
|
|
6
|
+
/**
|
|
7
|
+
* Which consumed package models an app hosts over REST, derived from its
|
|
8
|
+
* cookbook (#3749).
|
|
9
|
+
*
|
|
10
|
+
* `smrtConsumer({ svelteKit: { objects } })` already generates routes for an
|
|
11
|
+
* explicit, provider-qualified allowlist and honours each model's `api` config
|
|
12
|
+
* (an `api: false` or empty-include model gets no route). The cookbook is that
|
|
13
|
+
* allowlist: the models of its recipes plus its `features`. The cookbook's own
|
|
14
|
+
* `exposure` (surfaces switched off per model) and a recipe's
|
|
15
|
+
* `options[model].exposure.api === false` withdraw a model; nothing here widens
|
|
16
|
+
* what a model declares.
|
|
17
|
+
*/
|
|
18
|
+
/** The cookbook file `smrt cookbook apply` writes at the project root. */
|
|
19
|
+
var COOKBOOK_FILE = "smrt.cookbook.json";
|
|
20
|
+
function readJson(file) {
|
|
21
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
22
|
+
}
|
|
23
|
+
/** The package's JSON manifest, found the way the consumer plugin finds it. */
|
|
24
|
+
function readPackageManifest(projectRoot, packageName) {
|
|
25
|
+
const dir = path.join(projectRoot, "node_modules", packageName);
|
|
26
|
+
const candidates = [
|
|
27
|
+
...manifestExportCandidates(dir),
|
|
28
|
+
path.join(dir, "dist", "manifest.json"),
|
|
29
|
+
path.join(dir, "manifest.json")
|
|
30
|
+
];
|
|
31
|
+
for (const file of candidates) {
|
|
32
|
+
if (!file.endsWith(".json") || !fs.existsSync(file)) continue;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = readJson(file);
|
|
35
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function objectRef(key, def, fallbackPackage) {
|
|
41
|
+
if (key.includes(":")) return key;
|
|
42
|
+
if (def.qualifiedName?.includes(":")) return def.qualifiedName;
|
|
43
|
+
const pkg = def.packageName ?? fallbackPackage;
|
|
44
|
+
return def.className ? `${pkg}:${def.className}` : void 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Qualified refs of the consumed models the project's cookbook hosts. Returns
|
|
48
|
+
* `[]` when the project has no cookbook. Refs that no consumed package
|
|
49
|
+
* declares are dropped (the shell reports an unknown recipe or feature).
|
|
50
|
+
*
|
|
51
|
+
* @throws When `smrt.cookbook.json` is present but unreadable or invalid.
|
|
52
|
+
*/
|
|
53
|
+
function resolveCookbookHostedObjects(projectRoot, packages) {
|
|
54
|
+
const file = path.join(projectRoot, COOKBOOK_FILE);
|
|
55
|
+
if (!fs.existsSync(file)) return [];
|
|
56
|
+
let document;
|
|
57
|
+
try {
|
|
58
|
+
document = readJson(file);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`[smrt] Cannot read ${COOKBOOK_FILE}: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
const result = validateCookbook(document);
|
|
63
|
+
if (!result.ok) throw new Error(`[smrt] ${COOKBOOK_FILE} is not a valid cookbook:\n- ${result.errors.join("\n- ")}`);
|
|
64
|
+
const { cookbook } = result;
|
|
65
|
+
const declared = /* @__PURE__ */ new Set();
|
|
66
|
+
const recipes = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const packageName of packages) {
|
|
68
|
+
const manifest = readPackageManifest(projectRoot, packageName);
|
|
69
|
+
if (!manifest) continue;
|
|
70
|
+
for (const [key, def] of Object.entries(manifest.objects ?? {})) {
|
|
71
|
+
const ref = objectRef(key, def ?? {}, manifest.packageName ?? packageName);
|
|
72
|
+
if (ref) declared.add(ref);
|
|
73
|
+
}
|
|
74
|
+
for (const recipe of manifest.recipes ?? []) if (typeof recipe.id === "string" && !recipes.has(recipe.id)) recipes.set(recipe.id, recipe);
|
|
75
|
+
}
|
|
76
|
+
const hosted = /* @__PURE__ */ new Set();
|
|
77
|
+
const withdrawn = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const id of cookbook.recipes) {
|
|
79
|
+
const recipe = recipes.get(id);
|
|
80
|
+
if (!recipe || !Array.isArray(recipe.models)) continue;
|
|
81
|
+
const options = recipe.options ?? {};
|
|
82
|
+
for (const model of recipe.models) {
|
|
83
|
+
if (typeof model !== "string") continue;
|
|
84
|
+
hosted.add(model);
|
|
85
|
+
if (options[model]?.exposure?.api === false) withdrawn.add(model);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const feature of cookbook.features ?? []) hosted.add(feature);
|
|
89
|
+
for (const [model, surfaces] of Object.entries(cookbook.exposure ?? {})) if (surfaces.includes("api")) withdrawn.add(model);
|
|
90
|
+
return [...hosted].filter((ref) => declared.has(ref) && !withdrawn.has(ref)).sort();
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { COOKBOOK_FILE, resolveCookbookHostedObjects };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=cookbook-exposure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookbook-exposure.js","names":[],"sources":["../../src/vite-preset/cookbook-exposure.ts"],"sourcesContent":["/**\n * Which consumed package models an app hosts over REST, derived from its\n * cookbook (#3749).\n *\n * `smrtConsumer({ svelteKit: { objects } })` already generates routes for an\n * explicit, provider-qualified allowlist and honours each model's `api` config\n * (an `api: false` or empty-include model gets no route). The cookbook is that\n * allowlist: the models of its recipes plus its `features`. The cookbook's own\n * `exposure` (surfaces switched off per model) and a recipe's\n * `options[model].exposure.api === false` withdraw a model; nothing here widens\n * what a model declares.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { validateCookbook } from '../cookbook/validate.js';\nimport { manifestExportCandidates } from '../manifest/package-manifest-exports.js';\n\n/** The cookbook file `smrt cookbook apply` writes at the project root. */\nexport const COOKBOOK_FILE = 'smrt.cookbook.json';\n\ninterface RecipeEntry {\n id?: unknown;\n models?: unknown;\n options?: unknown;\n}\n\ninterface PackageManifestJson {\n packageName?: string;\n objects?: Record<\n string,\n { qualifiedName?: string; className?: string; packageName?: string }\n >;\n recipes?: RecipeEntry[];\n}\n\nfunction readJson(file: string): unknown {\n return JSON.parse(fs.readFileSync(file, 'utf8'));\n}\n\n/** The package's JSON manifest, found the way the consumer plugin finds it. */\nfunction readPackageManifest(\n projectRoot: string,\n packageName: string,\n): PackageManifestJson | null {\n const dir = path.join(projectRoot, 'node_modules', packageName);\n const candidates = [\n ...manifestExportCandidates(dir),\n path.join(dir, 'dist', 'manifest.json'),\n path.join(dir, 'manifest.json'),\n ];\n for (const file of candidates) {\n if (!file.endsWith('.json') || !fs.existsSync(file)) continue;\n try {\n const parsed = readJson(file);\n if (parsed && typeof parsed === 'object') {\n return parsed as PackageManifestJson;\n }\n } catch {\n // Keep probing; the consumer plugin reports an unreadable manifest.\n }\n }\n return null;\n}\n\nfunction objectRef(\n key: string,\n def: { qualifiedName?: string; className?: string; packageName?: string },\n fallbackPackage: string,\n): string | undefined {\n if (key.includes(':')) return key;\n if (def.qualifiedName?.includes(':')) return def.qualifiedName;\n const pkg = def.packageName ?? fallbackPackage;\n return def.className ? `${pkg}:${def.className}` : undefined;\n}\n\n/**\n * Qualified refs of the consumed models the project's cookbook hosts. Returns\n * `[]` when the project has no cookbook. Refs that no consumed package\n * declares are dropped (the shell reports an unknown recipe or feature).\n *\n * @throws When `smrt.cookbook.json` is present but unreadable or invalid.\n */\nexport function resolveCookbookHostedObjects(\n projectRoot: string,\n packages: readonly string[],\n): string[] {\n const file = path.join(projectRoot, COOKBOOK_FILE);\n if (!fs.existsSync(file)) return [];\n\n let document: unknown;\n try {\n document = readJson(file);\n } catch (error) {\n throw new Error(\n `[smrt] Cannot read ${COOKBOOK_FILE}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n const result = validateCookbook(document);\n if (!result.ok) {\n throw new Error(\n `[smrt] ${COOKBOOK_FILE} is not a valid cookbook:\\n- ${result.errors.join('\\n- ')}`,\n );\n }\n const { cookbook } = result;\n\n const declared = new Set<string>();\n const recipes = new Map<string, RecipeEntry>();\n for (const packageName of packages) {\n const manifest = readPackageManifest(projectRoot, packageName);\n if (!manifest) continue;\n for (const [key, def] of Object.entries(manifest.objects ?? {})) {\n const ref = objectRef(\n key,\n def ?? {},\n manifest.packageName ?? packageName,\n );\n if (ref) declared.add(ref);\n }\n for (const recipe of manifest.recipes ?? []) {\n if (typeof recipe.id === 'string' && !recipes.has(recipe.id)) {\n recipes.set(recipe.id, recipe);\n }\n }\n }\n\n const hosted = new Set<string>();\n const withdrawn = new Set<string>();\n for (const id of cookbook.recipes) {\n const recipe = recipes.get(id);\n if (!recipe || !Array.isArray(recipe.models)) continue;\n const options = (recipe.options ?? {}) as Record<\n string,\n { exposure?: { api?: unknown } } | undefined\n >;\n for (const model of recipe.models) {\n if (typeof model !== 'string') continue;\n hosted.add(model);\n if (options[model]?.exposure?.api === false) withdrawn.add(model);\n }\n }\n for (const feature of cookbook.features ?? []) hosted.add(feature);\n for (const [model, surfaces] of Object.entries(cookbook.exposure ?? {})) {\n if (surfaces.includes('api')) withdrawn.add(model);\n }\n\n return [...hosted]\n .filter((ref) => declared.has(ref) && !withdrawn.has(ref))\n .sort();\n}\n"],"mappings":"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"}
|
|
@@ -14,6 +14,15 @@ export interface SmrtPresetOptions {
|
|
|
14
14
|
* the app consumes none. Tooling/UI packages must not be listed.
|
|
15
15
|
*/
|
|
16
16
|
packages?: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* Consumed package models to host over generated REST routes, as
|
|
19
|
+
* provider-qualified refs (`@scope/pkg:Model`). This is an HTTP exposure
|
|
20
|
+
* boundary: each model's own `api` config still applies, so an `api: false`
|
|
21
|
+
* model stays closed. Omit to derive it from `smrt.cookbook.json` (the models
|
|
22
|
+
* of its recipes and `features`, minus those its `exposure` turns off for
|
|
23
|
+
* `api`); without a cookbook nothing is hosted. `[]` hosts nothing.
|
|
24
|
+
*/
|
|
25
|
+
expose?: readonly string[];
|
|
17
26
|
/**
|
|
18
27
|
* Project root. Omit to follow the root Vite/SvelteKit resolve (the launch
|
|
19
28
|
* cwd); `smrt.config.ts` is searched from this value or `process.cwd()`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-preset/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-preset/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,MAAM,CAAC;AAK/C,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,oBAAoB;;;;;;;CAOvB,CAAC;AAmEX;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,4BAA6B,CAAC;AAWvE;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgE7E"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { smrtPlugin } from "../vite-plugin/index.js";
|
|
2
2
|
import { smrtConsumer } from "../consumer-plugin/index.js";
|
|
3
|
+
import { resolveCookbookHostedObjects } from "./cookbook-exposure.js";
|
|
3
4
|
//#region src/vite-preset/index.ts
|
|
4
5
|
var SMRT_PRESET_DEFAULTS = {
|
|
5
6
|
objectsDir: "src/lib/objects",
|
|
@@ -75,13 +76,22 @@ async function smrt(options = {}) {
|
|
|
75
76
|
const plugins = [];
|
|
76
77
|
if (options.decorators !== false) plugins.push(decoratorPlugin());
|
|
77
78
|
plugins.push(sharedRuntimePlugin());
|
|
78
|
-
if (packages.length > 0)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
if (packages.length > 0) {
|
|
80
|
+
const hosted = options.expose ? validatePackages(options.expose, "smrt({ expose })") : resolveCookbookHostedObjects(options.projectRoot ?? process.cwd(), packages);
|
|
81
|
+
plugins.push(smrtConsumer({
|
|
82
|
+
...rootOption,
|
|
83
|
+
packages,
|
|
84
|
+
generateTypes: true,
|
|
85
|
+
typesDir,
|
|
86
|
+
svelteKit: hosted.length > 0 ? {
|
|
87
|
+
objects: hosted,
|
|
88
|
+
routesDir,
|
|
89
|
+
objectsDir,
|
|
90
|
+
configPath,
|
|
91
|
+
configFileName
|
|
92
|
+
} : true
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
85
95
|
const pluginOptions = {
|
|
86
96
|
...rootOption,
|
|
87
97
|
include: options.include ?? [`${objectsDir}/**/*.ts`],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/vite-preset/index.ts"],"sourcesContent":["/**\n * Single-plugin Vite preset for SMRT applications.\n *\n * `plugins: [sveltekit(), smrt()]` replaces the hand-copied glue every app\n * carried: the Oxc legacy-decorator block, `smrtConsumer({...})` with an\n * explicit package list, and `smrtPlugin({...})` with the SvelteKit path\n * options. `smrtConsumer` and `smrtPlugin` stay public and unchanged.\n */\nimport type { Plugin, UserConfig } from 'vite';\nimport { smrtConsumer } from '../consumer-plugin/index.js';\nimport { type SmrtPluginOptions, smrtPlugin } from '../vite-plugin/index.js';\n\nexport interface SmrtPresetOptions {\n /**\n * SMRT packages whose object manifests this app consumes. Takes precedence\n * over `consumer.packages` in `smrt.config.ts`. An empty array declares that\n * the app consumes none. Tooling/UI packages must not be listed.\n */\n packages?: readonly string[];\n /**\n * Project root. Omit to follow the root Vite/SvelteKit resolve (the launch\n * cwd); `smrt.config.ts` is searched from this value or `process.cwd()`.\n */\n projectRoot?: string;\n /** Directory of the app's own `@smrt()` objects. Default `src/lib/objects`. */\n objectsDir?: string;\n /** Generated type declarations. Default `src/lib/types/smrt-generated`. */\n typesDir?: string;\n /** Generated API routes. Default `src/routes/api`. */\n routesDir?: string;\n /** Directory holding the generated runtime config module. Default `src/lib/server`. */\n configPath?: string;\n /** File name of that module. Default `smrt.ts`. */\n configFileName?: string;\n /** Source globs scanned for objects. Default `<objectsDir>/**\\/*.ts`. */\n include?: string[];\n /** Source globs excluded from the scan. Default test and spec files. */\n exclude?: string[];\n /**\n * Apply the Oxc legacy-decorator config. Default `true`. Values the app sets\n * under `oxc.decorator` itself are never overwritten.\n */\n decorators?: boolean;\n}\n\nexport const SMRT_PRESET_DEFAULTS = {\n objectsDir: 'src/lib/objects',\n typesDir: 'src/lib/types/smrt-generated',\n routesDir: 'src/routes/api',\n configPath: 'src/lib/server',\n configFileName: 'smrt.ts',\n exclude: ['**/*.test.ts', '**/*.spec.ts'],\n} as const;\n\nfunction validatePackages(value: unknown, source: string): string[] {\n if (\n !Array.isArray(value) ||\n value.some((p) => typeof p !== 'string' || p.trim() === '')\n ) {\n throw new Error(\n `[smrt] ${source} must be an array of non-empty package names, got ${JSON.stringify(value)}.`,\n );\n }\n return [...new Set(value.map((p: string) => p.trim()))];\n}\n\n/** Resolve the declared consumer package list: option first, then smrt.config. */\nasync function resolvePackages(\n options: SmrtPresetOptions,\n searchFrom: string,\n): Promise<string[]> {\n if (options.packages !== undefined) {\n return validatePackages(options.packages, 'smrt({ packages })');\n }\n\n let declared: unknown;\n try {\n const { loadConfig } = await import('@happyvertical/smrt-config');\n const config = await loadConfig({ cache: false, searchFrom });\n declared = config.consumer?.packages;\n } catch (error) {\n throw new Error(\n `[smrt] Could not read smrt.config to resolve consumer packages: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n if (declared === undefined) {\n throw new Error(\n '[smrt] No consumer package list declared. Set `consumer: { packages: [...] }` in smrt.config.ts ' +\n '(use [] when the app consumes no SMRT packages) or pass `smrt({ packages })`. ' +\n 'The list is explicit so runtime registration is deterministic; it is never inferred from package.json.',\n );\n }\n return validatePackages(declared, 'smrt.config `consumer.packages`');\n}\n\nfunction decoratorPlugin(): Plugin {\n return {\n name: 'smrt:decorators',\n // Vite merges a hook's result over the user config, so fill only the\n // values the app has not set itself.\n config(user: UserConfig) {\n const decorator = (\n user as { oxc?: { decorator?: Record<string, unknown> } }\n ).oxc?.decorator;\n const fill: Record<string, boolean> = {};\n if (decorator?.legacy === undefined) fill.legacy = true;\n if (decorator?.emitDecoratorMetadata === undefined) {\n fill.emitDecoratorMetadata = true;\n }\n return Object.keys(fill).length\n ? ({ oxc: { decorator: fill } } as UserConfig)\n : undefined;\n },\n };\n}\n\n/**\n * Packages SMRT's SvelteKit entries share with the app by identity. SvelteKit\n * recognizes `redirect()`/`fail()` results by class, so a server entry such as\n * `@happyvertical/smrt-app-runtime/sveltekit` must import the app's own copy.\n * A registry install already resolves that peer from the app; a linked or\n * workspace package resolves it from its own directory instead, and its\n * redirects then surface as 500s. Deduping makes both layouts identical.\n */\nexport const SMRT_SHARED_RUNTIME_PACKAGES = ['@sveltejs/kit'] as const;\n\nfunction sharedRuntimePlugin(): Plugin {\n return {\n name: 'smrt:shared-runtime',\n config() {\n return { resolve: { dedupe: [...SMRT_SHARED_RUNTIME_PACKAGES] } };\n },\n };\n}\n\n/**\n * SMRT Vite preset: decorators, dependency manifests, and app object\n * generation in one entry. Returns a promise because the package list is read\n * from `smrt.config.ts`; Vite accepts promised plugins.\n */\nexport async function smrt(options: SmrtPresetOptions = {}): Promise<Plugin[]> {\n const D = SMRT_PRESET_DEFAULTS;\n const objectsDir = options.objectsDir ?? D.objectsDir;\n const typesDir = options.typesDir ?? D.typesDir;\n const routesDir = options.routesDir ?? D.routesDir;\n const configPath = options.configPath ?? D.configPath;\n const configFileName = options.configFileName ?? D.configFileName;\n const rootOption = options.projectRoot\n ? { projectRoot: options.projectRoot }\n : {};\n\n const packages = await resolvePackages(\n options,\n options.projectRoot ?? process.cwd(),\n );\n\n const plugins: Plugin[] = [];\n if (options.decorators !== false) plugins.push(decoratorPlugin());\n plugins.push(sharedRuntimePlugin());\n // smrtConsumer must precede smrtPlugin, as in the template's hand-written form.\n // With no declared packages there is nothing to consume, and an empty list\n // would otherwise trigger dependency discovery.\n if (packages.length > 0) {\n plugins.push(\n smrtConsumer({\n ...rootOption,\n packages,\n generateTypes: true,\n typesDir,\n svelteKit: true,\n }),\n );\n }\n const pluginOptions: SmrtPluginOptions = {\n ...rootOption,\n include: options.include ?? [`${objectsDir}/**/*.ts`],\n exclude: options.exclude ?? [...D.exclude],\n generateTypes: true,\n typeDeclarationsPath: typesDir,\n svelteKit: {\n enabled: true,\n routesDir,\n objectsDir,\n configPath,\n configFileName,\n },\n };\n plugins.push(smrtPlugin(pluginOptions));\n return plugins;\n}\n"],"mappings":";;;AA6CA,IAAa,uBAAuB;CAClC,YAAY;CACZ,UAAU;CACV,WAAW;CACX,YAAY;CACZ,gBAAgB;CAChB,SAAS,CAAC,gBAAgB,cAAc;AAC1C;AAEA,SAAS,iBAAiB,OAAgB,QAA0B;CAClE,IACE,CAAC,MAAM,QAAQ,KAAK,KACpB,MAAM,MAAM,MAAM,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,EAAE,GAE1D,MAAM,IAAI,MACR,UAAU,OAAO,oDAAoD,KAAK,UAAU,KAAK,EAAE,EAC7F;CAEF,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,MAAc,EAAE,KAAK,CAAC,CAAC,CAAC;AACxD;;AAGA,eAAe,gBACb,SACA,YACmB;CACnB,IAAI,QAAQ,aAAa,KAAA,GACvB,OAAO,iBAAiB,QAAQ,UAAU,oBAAoB;CAGhE,IAAI;CACJ,IAAI;EACF,MAAM,EAAE,eAAe,MAAM,OAAO;EAEpC,YAAW,MADU,WAAW;GAAE,OAAO;GAAO;EAAW,CAAC,EAAA,CAC1C,UAAU;CAC9B,SAAS,OAAO;EACd,MAAM,IAAI,MACR,mEACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEzD;CACF;CAEA,IAAI,aAAa,KAAA,GACf,MAAM,IAAI,MACR,sRAGF;CAEF,OAAO,iBAAiB,UAAU,iCAAiC;AACrE;AAEA,SAAS,kBAA0B;CACjC,OAAO;EACL,MAAM;EAGN,OAAO,MAAkB;GACvB,MAAM,YACJ,KACA,KAAK;GACP,MAAM,OAAgC,CAAC;GACvC,IAAI,WAAW,WAAW,KAAA,GAAW,KAAK,SAAS;GACnD,IAAI,WAAW,0BAA0B,KAAA,GACvC,KAAK,wBAAwB;GAE/B,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,SACpB,EAAE,KAAK,EAAE,WAAW,KAAK,EAAE,IAC5B,KAAA;EACN;CACF;AACF;;;;;;;;;AAUA,IAAa,+BAA+B,CAAC,eAAe;AAE5D,SAAS,sBAA8B;CACrC,OAAO;EACL,MAAM;EACN,SAAS;GACP,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,GAAG,4BAA4B,EAAE,EAAE;EAClE;CACF;AACF;;;;;;AAOA,eAAsB,KAAK,UAA6B,CAAC,GAAsB;CAC7E,MAAM,IAAI;CACV,MAAM,aAAa,QAAQ,cAAc,EAAE;CAC3C,MAAM,WAAW,QAAQ,YAAY,EAAE;CACvC,MAAM,YAAY,QAAQ,aAAa,EAAE;CACzC,MAAM,aAAa,QAAQ,cAAc,EAAE;CAC3C,MAAM,iBAAiB,QAAQ,kBAAkB,EAAE;CACnD,MAAM,aAAa,QAAQ,cACvB,EAAE,aAAa,QAAQ,YAAY,IACnC,CAAC;CAEL,MAAM,WAAW,MAAM,gBACrB,SACA,QAAQ,eAAe,QAAQ,IAAI,CACrC;CAEA,MAAM,UAAoB,CAAC;CAC3B,IAAI,QAAQ,eAAe,OAAO,QAAQ,KAAK,gBAAgB,CAAC;CAChE,QAAQ,KAAK,oBAAoB,CAAC;CAIlC,IAAI,SAAS,SAAS,GACpB,QAAQ,KACN,aAAa;EACX,GAAG;EACH;EACA,eAAe;EACf;EACA,WAAW;CACb,CAAC,CACH;CAEF,MAAM,gBAAmC;EACvC,GAAG;EACH,SAAS,QAAQ,WAAW,CAAC,GAAG,WAAW,SAAS;EACpD,SAAS,QAAQ,WAAW,CAAC,GAAG,EAAE,OAAO;EACzC,eAAe;EACf,sBAAsB;EACtB,WAAW;GACT,SAAS;GACT;GACA;GACA;GACA;EACF;CACF;CACA,QAAQ,KAAK,WAAW,aAAa,CAAC;CACtC,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/vite-preset/index.ts"],"sourcesContent":["/**\n * Single-plugin Vite preset for SMRT applications.\n *\n * `plugins: [sveltekit(), smrt()]` replaces the hand-copied glue every app\n * carried: the Oxc legacy-decorator block, `smrtConsumer({...})` with an\n * explicit package list, and `smrtPlugin({...})` with the SvelteKit path\n * options. `smrtConsumer` and `smrtPlugin` stay public and unchanged.\n */\nimport type { Plugin, UserConfig } from 'vite';\nimport { smrtConsumer } from '../consumer-plugin/index.js';\nimport { type SmrtPluginOptions, smrtPlugin } from '../vite-plugin/index.js';\nimport { resolveCookbookHostedObjects } from './cookbook-exposure.js';\n\nexport interface SmrtPresetOptions {\n /**\n * SMRT packages whose object manifests this app consumes. Takes precedence\n * over `consumer.packages` in `smrt.config.ts`. An empty array declares that\n * the app consumes none. Tooling/UI packages must not be listed.\n */\n packages?: readonly string[];\n /**\n * Consumed package models to host over generated REST routes, as\n * provider-qualified refs (`@scope/pkg:Model`). This is an HTTP exposure\n * boundary: each model's own `api` config still applies, so an `api: false`\n * model stays closed. Omit to derive it from `smrt.cookbook.json` (the models\n * of its recipes and `features`, minus those its `exposure` turns off for\n * `api`); without a cookbook nothing is hosted. `[]` hosts nothing.\n */\n expose?: readonly string[];\n /**\n * Project root. Omit to follow the root Vite/SvelteKit resolve (the launch\n * cwd); `smrt.config.ts` is searched from this value or `process.cwd()`.\n */\n projectRoot?: string;\n /** Directory of the app's own `@smrt()` objects. Default `src/lib/objects`. */\n objectsDir?: string;\n /** Generated type declarations. Default `src/lib/types/smrt-generated`. */\n typesDir?: string;\n /** Generated API routes. Default `src/routes/api`. */\n routesDir?: string;\n /** Directory holding the generated runtime config module. Default `src/lib/server`. */\n configPath?: string;\n /** File name of that module. Default `smrt.ts`. */\n configFileName?: string;\n /** Source globs scanned for objects. Default `<objectsDir>/**\\/*.ts`. */\n include?: string[];\n /** Source globs excluded from the scan. Default test and spec files. */\n exclude?: string[];\n /**\n * Apply the Oxc legacy-decorator config. Default `true`. Values the app sets\n * under `oxc.decorator` itself are never overwritten.\n */\n decorators?: boolean;\n}\n\nexport const SMRT_PRESET_DEFAULTS = {\n objectsDir: 'src/lib/objects',\n typesDir: 'src/lib/types/smrt-generated',\n routesDir: 'src/routes/api',\n configPath: 'src/lib/server',\n configFileName: 'smrt.ts',\n exclude: ['**/*.test.ts', '**/*.spec.ts'],\n} as const;\n\nfunction validatePackages(value: unknown, source: string): string[] {\n if (\n !Array.isArray(value) ||\n value.some((p) => typeof p !== 'string' || p.trim() === '')\n ) {\n throw new Error(\n `[smrt] ${source} must be an array of non-empty package names, got ${JSON.stringify(value)}.`,\n );\n }\n return [...new Set(value.map((p: string) => p.trim()))];\n}\n\n/** Resolve the declared consumer package list: option first, then smrt.config. */\nasync function resolvePackages(\n options: SmrtPresetOptions,\n searchFrom: string,\n): Promise<string[]> {\n if (options.packages !== undefined) {\n return validatePackages(options.packages, 'smrt({ packages })');\n }\n\n let declared: unknown;\n try {\n const { loadConfig } = await import('@happyvertical/smrt-config');\n const config = await loadConfig({ cache: false, searchFrom });\n declared = config.consumer?.packages;\n } catch (error) {\n throw new Error(\n `[smrt] Could not read smrt.config to resolve consumer packages: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n if (declared === undefined) {\n throw new Error(\n '[smrt] No consumer package list declared. Set `consumer: { packages: [...] }` in smrt.config.ts ' +\n '(use [] when the app consumes no SMRT packages) or pass `smrt({ packages })`. ' +\n 'The list is explicit so runtime registration is deterministic; it is never inferred from package.json.',\n );\n }\n return validatePackages(declared, 'smrt.config `consumer.packages`');\n}\n\nfunction decoratorPlugin(): Plugin {\n return {\n name: 'smrt:decorators',\n // Vite merges a hook's result over the user config, so fill only the\n // values the app has not set itself.\n config(user: UserConfig) {\n const decorator = (\n user as { oxc?: { decorator?: Record<string, unknown> } }\n ).oxc?.decorator;\n const fill: Record<string, boolean> = {};\n if (decorator?.legacy === undefined) fill.legacy = true;\n if (decorator?.emitDecoratorMetadata === undefined) {\n fill.emitDecoratorMetadata = true;\n }\n return Object.keys(fill).length\n ? ({ oxc: { decorator: fill } } as UserConfig)\n : undefined;\n },\n };\n}\n\n/**\n * Packages SMRT's SvelteKit entries share with the app by identity. SvelteKit\n * recognizes `redirect()`/`fail()` results by class, so a server entry such as\n * `@happyvertical/smrt-app-runtime/sveltekit` must import the app's own copy.\n * A registry install already resolves that peer from the app; a linked or\n * workspace package resolves it from its own directory instead, and its\n * redirects then surface as 500s. Deduping makes both layouts identical.\n */\nexport const SMRT_SHARED_RUNTIME_PACKAGES = ['@sveltejs/kit'] as const;\n\nfunction sharedRuntimePlugin(): Plugin {\n return {\n name: 'smrt:shared-runtime',\n config() {\n return { resolve: { dedupe: [...SMRT_SHARED_RUNTIME_PACKAGES] } };\n },\n };\n}\n\n/**\n * SMRT Vite preset: decorators, dependency manifests, and app object\n * generation in one entry. Returns a promise because the package list is read\n * from `smrt.config.ts`; Vite accepts promised plugins.\n */\nexport async function smrt(options: SmrtPresetOptions = {}): Promise<Plugin[]> {\n const D = SMRT_PRESET_DEFAULTS;\n const objectsDir = options.objectsDir ?? D.objectsDir;\n const typesDir = options.typesDir ?? D.typesDir;\n const routesDir = options.routesDir ?? D.routesDir;\n const configPath = options.configPath ?? D.configPath;\n const configFileName = options.configFileName ?? D.configFileName;\n const rootOption = options.projectRoot\n ? { projectRoot: options.projectRoot }\n : {};\n\n const packages = await resolvePackages(\n options,\n options.projectRoot ?? process.cwd(),\n );\n\n const plugins: Plugin[] = [];\n if (options.decorators !== false) plugins.push(decoratorPlugin());\n plugins.push(sharedRuntimePlugin());\n // smrtConsumer must precede smrtPlugin, as in the template's hand-written form.\n // With no declared packages there is nothing to consume, and an empty list\n // would otherwise trigger dependency discovery.\n if (packages.length > 0) {\n const hosted = options.expose\n ? validatePackages(options.expose, 'smrt({ expose })')\n : resolveCookbookHostedObjects(\n options.projectRoot ?? process.cwd(),\n packages,\n );\n plugins.push(\n smrtConsumer({\n ...rootOption,\n packages,\n generateTypes: true,\n typesDir,\n svelteKit:\n hosted.length > 0\n ? {\n objects: hosted,\n routesDir,\n objectsDir,\n configPath,\n configFileName,\n }\n : true,\n }),\n );\n }\n const pluginOptions: SmrtPluginOptions = {\n ...rootOption,\n include: options.include ?? [`${objectsDir}/**/*.ts`],\n exclude: options.exclude ?? [...D.exclude],\n generateTypes: true,\n typeDeclarationsPath: typesDir,\n svelteKit: {\n enabled: true,\n routesDir,\n objectsDir,\n configPath,\n configFileName,\n },\n };\n plugins.push(smrtPlugin(pluginOptions));\n return plugins;\n}\n"],"mappings":"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"}
|