@happyvertical/smrt-core 0.37.6 → 0.37.8
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 +17 -6
- package/dist/collection.d.ts +7 -0
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +2 -0
- package/dist/collection.js.map +1 -1
- package/dist/consumer-plugin/index.js +26 -3
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/dispatch/index.d.ts +1 -1
- package/dist/dispatch/index.d.ts.map +1 -1
- package/dist/dispatch/index.js +2 -2
- package/dist/dispatch/tenant-resolver.d.ts +23 -0
- package/dist/dispatch/tenant-resolver.d.ts.map +1 -1
- package/dist/dispatch/tenant-resolver.js +33 -1
- package/dist/dispatch/tenant-resolver.js.map +1 -1
- package/dist/generators/conditional-get.d.ts +120 -0
- package/dist/generators/conditional-get.d.ts.map +1 -0
- package/dist/generators/conditional-get.js +217 -0
- package/dist/generators/conditional-get.js.map +1 -0
- package/dist/generators/index.d.ts +1 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -1
- package/dist/generators/rest.d.ts +52 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +146 -10
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/manifest/manifest-loader.d.ts +10 -1
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +18 -5
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +10 -2
- 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/test-manifest-stub.d.ts.map +1 -1
- package/dist/manifest/test-manifest-stub.js +2108 -223
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +10 -2
- package/dist/object.d.ts +19 -0
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +24 -2
- package/dist/object.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/shared-state.d.ts +8 -1
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js +11 -3
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry/types.d.ts +34 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +7 -6
- package/dist/sync/apply.d.ts +234 -0
- package/dist/sync/apply.d.ts.map +1 -0
- package/dist/sync/apply.js +378 -0
- package/dist/sync/apply.js.map +1 -0
- package/dist/utils/stack-frames.d.ts +50 -0
- package/dist/utils/stack-frames.d.ts.map +1 -0
- package/dist/utils/stack-frames.js +64 -0
- package/dist/utils/stack-frames.js.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +60 -27
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts +40 -0
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -0
- package/dist/vite-plugin/sync-apply-route.js +240 -0
- package/dist/vite-plugin/sync-apply-route.js.map +1 -0
- package/package.json +4 -4
|
@@ -188,15 +188,38 @@ function determineImportPath(packageJson) {
|
|
|
188
188
|
return packageName;
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
|
-
* Save aggregated manifest to .smrt/manifest.json for CLI discovery
|
|
191
|
+
* Save aggregated manifest to .smrt/manifest.json for CLI discovery.
|
|
192
|
+
*
|
|
193
|
+
* Merge-preserving: `smrtPlugin()` writes the project's own scanned objects
|
|
194
|
+
* to the same file (`writeLocalManifest`, issue #963), and both writes happen
|
|
195
|
+
* in parallel `buildStart` hooks — so a plain overwrite here would clobber
|
|
196
|
+
* the local objects whenever this plugin's write lands last (issue #1760
|
|
197
|
+
* review). Local field metadata would then silently vanish from CLI schema
|
|
198
|
+
* commands and from server runtimes that seed `.smrt/manifest.json`, dropping
|
|
199
|
+
* domain columns on write. This function therefore only ADDS/refreshes the
|
|
200
|
+
* external-package entries it owns and preserves everything else already in
|
|
201
|
+
* the file (including the top-level `packageName` the local write sets).
|
|
192
202
|
*/
|
|
193
203
|
async function saveAggregatedManifest(manifest, projectRoot) {
|
|
194
204
|
const smrtDir = path.join(projectRoot, ".smrt");
|
|
195
205
|
const manifestPath = path.join(smrtDir, "manifest.json");
|
|
196
206
|
try {
|
|
197
207
|
if (!fs.existsSync(smrtDir)) fs.mkdirSync(smrtDir, { recursive: true });
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
let merged = manifest;
|
|
209
|
+
if (fs.existsSync(manifestPath)) try {
|
|
210
|
+
const existing = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
211
|
+
if (existing && typeof existing.objects === "object") merged = {
|
|
212
|
+
...existing,
|
|
213
|
+
...manifest,
|
|
214
|
+
...existing.packageName ? { packageName: existing.packageName } : {},
|
|
215
|
+
objects: {
|
|
216
|
+
...existing.objects,
|
|
217
|
+
...manifest.objects
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
} catch {}
|
|
221
|
+
fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
222
|
+
console.log(`[smrt:consumer] Saved aggregated manifest to .smrt/manifest.json (${Object.keys(merged.objects).length} objects)`);
|
|
200
223
|
} catch (error) {
|
|
201
224
|
console.warn("[smrt:consumer] Failed to save aggregated manifest:", error);
|
|
202
225
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/consumer-plugin/index.ts"],"sourcesContent":["/**\n * Vite plugin for consuming SMRT packages\n * Solves virtual module resolution in downstream projects\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Plugin } from 'vite';\nimport { generateDeclarations } from '../prebuild/index.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\n\n/**\n * Loosely-typed view of an object definition as carried by an external\n * package's static manifest. The static manifests are read from JSON at the\n * package boundary, so only the fields this plugin consumes are typed; the\n * index signature preserves any additional fields (e.g. for spreads). This is\n * a structural superset of a manifest `SmartObjectDefinition` plus the\n * consumer-only `hasCollection` marker.\n */\ninterface ConsumerObjectDefinition {\n className?: string;\n packageName?: string;\n packageVersion?: string;\n qualifiedName?: string;\n importPath?: string;\n exportName?: string;\n collectionExportName?: string;\n hasCollection?: boolean;\n collection?: string;\n extends?: string;\n extendsQualified?: string;\n extendsTypeArg?: string;\n [key: string]: unknown;\n}\n\n/**\n * Aggregated manifest assembled by the consumer plugin from one or more\n * external package manifests. Loosely typed because the inputs originate from\n * JSON read at the package boundary.\n */\ninterface ConsumerManifest {\n version: string;\n timestamp: number;\n packageName?: string;\n packageVersion?: string;\n objects: Record<string, ConsumerObjectDefinition>;\n}\n\n/**\n * Minimal structural shape of a parsed `package.json` consumed here (name,\n * version, and the export map used to derive import paths). The index\n * signature keeps the remaining fields accessible.\n */\ninterface ConsumerPackageJson {\n name?: string;\n version?: string;\n main?: string;\n exports?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface SmrtConsumerOptions {\n /** SMRT packages to scan (e.g., ['@my-org/products', '@my-org/content']) */\n packages?: string[];\n /** Generate TypeScript declarations */\n generateTypes?: boolean;\n /** Output directory for generated types */\n typesDir?: string;\n /** Project root path */\n projectRoot?: string;\n /** SvelteKit integration mode */\n svelteKit?: boolean;\n /** Use static types only (for federation builds) */\n staticTypes?: boolean;\n /** Disable file scanning */\n disableScanning?: boolean;\n}\n\nconst VIRTUAL_MODULES = {\n '@smrt/routes': 'smrt:routes',\n '@smrt/client': 'smrt:client',\n '@smrt/mcp': 'smrt:mcp',\n '@smrt/types': 'smrt:types',\n '@smrt/manifest': 'smrt:manifest',\n};\n\n/**\n * Consumer plugin for projects that use SMRT packages\n */\nexport function smrtConsumer(options: SmrtConsumerOptions = {}): Plugin {\n const {\n packages = [],\n generateTypes = true,\n typesDir = 'src/types/smrt-generated',\n projectRoot = process.cwd(),\n disableScanning = false,\n } = options;\n\n let smrtPackages: string[] = [];\n let typeManifest: ConsumerManifest | null = null;\n let typesGenerated = false;\n\n return {\n name: 'smrt-consumer',\n\n async buildStart() {\n console.log('[smrt:consumer] Initializing SMRT consumer plugin');\n\n // Discover SMRT packages if not explicitly specified\n if (packages.length === 0 && !disableScanning) {\n smrtPackages = await discoverSmrtPackages(projectRoot);\n } else {\n smrtPackages = packages;\n }\n\n if (smrtPackages.length > 0) {\n console.log(\n `[smrt:consumer] Found SMRT packages: ${smrtPackages.join(', ')}`,\n );\n\n // Aggregate type manifests from discovered packages\n typeManifest = await aggregateTypeManifests(smrtPackages, projectRoot);\n\n // Save aggregated manifest for CLI discovery\n await saveAggregatedManifest(typeManifest, projectRoot);\n\n // Generate registration file for CLI class loading\n await generateRegistrationFile(typeManifest, projectRoot);\n\n // Generate types if requested\n if (generateTypes && !typesGenerated) {\n await generateProjectTypes(typeManifest, typesDir, projectRoot);\n typesGenerated = true;\n }\n } else {\n console.log('[smrt:consumer] No SMRT packages found');\n typeManifest = { version: '1.0.0', timestamp: Date.now(), objects: {} };\n }\n },\n\n resolveId(id, _importer) {\n // Resolve virtual modules to generated type declarations\n if (id in VIRTUAL_MODULES) {\n const typeFileName = getTypeFileName(id);\n const typePath = path.join(projectRoot, typesDir, typeFileName);\n\n // If types file exists, resolve to it\n if (fs.existsSync(typePath)) {\n return typePath;\n }\n\n // Otherwise use virtual module ID for runtime resolution\n return `\\0${VIRTUAL_MODULES[id as keyof typeof VIRTUAL_MODULES]}`;\n }\n return null;\n },\n\n async load(id) {\n // Handle virtual modules if types aren't available\n const cleanId = id.startsWith('\\0') ? id.slice(1) : id;\n\n if (!typeManifest) {\n typeManifest = { version: '1.0.0', timestamp: Date.now(), objects: {} };\n }\n\n switch (cleanId) {\n case 'smrt:routes':\n return generateFallbackRoutesModule();\n\n case 'smrt:client':\n return generateFallbackClientModule(typeManifest);\n\n case 'smrt:mcp':\n return generateFallbackMcpModule();\n\n case 'smrt:types':\n return generateFallbackTypesModule(typeManifest);\n\n case 'smrt:manifest':\n return generateFallbackManifestModule(typeManifest);\n\n default:\n return null;\n }\n },\n };\n}\n\n/**\n * Discover SMRT packages from a consumer app's dependencies.\n *\n * Intentional split (#1579): this **consumer-plugin** path is async and\n * resolves SMRT packages from the downstream app's `package.json` dependency\n * names (`@have/`/`smrt` heuristic + `hasSmrtManifest` probe) inside the Vite\n * consumer plugin. It is deliberately separate from the build-time\n * `discoverSmrtPackages()` in `src/manifest/discover-smrt-packages.ts` — a\n * synchronous, lockfile-cached `node_modules` manifest scan used for manifest\n * generation. Different inputs, contexts, and lifecycles, not duplicated logic.\n */\nasync function discoverSmrtPackages(projectRoot: string): Promise<string[]> {\n const packages: string[] = [];\n const nodeModulesPath = path.join(projectRoot, 'node_modules');\n\n if (!fs.existsSync(nodeModulesPath)) {\n return packages;\n }\n\n try {\n // Check package.json for workspace dependencies\n const packageJsonPath = path.join(projectRoot, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.peerDependencies,\n };\n\n // Look for packages that likely contain SMRT objects\n for (const [name, version] of Object.entries(allDeps)) {\n if (\n typeof version === 'string' &&\n (name.includes('smrt') ||\n name.includes('@have/') ||\n (await hasSmrtManifest(nodeModulesPath, name)))\n ) {\n packages.push(name);\n }\n }\n }\n } catch (error) {\n console.warn('[smrt:consumer] Error discovering packages:', error);\n }\n\n return packages;\n}\n\n/**\n * Check if a package has SMRT manifest\n */\nasync function hasSmrtManifest(\n nodeModulesPath: string,\n packageName: string,\n): Promise<boolean> {\n const packagePath = path.join(nodeModulesPath, packageName);\n const manifestPath = path.join(\n packagePath,\n 'dist',\n 'manifest',\n 'static-manifest.js',\n );\n return fs.existsSync(manifestPath);\n}\n\n/**\n * Aggregate type manifests from multiple packages\n */\nasync function aggregateTypeManifests(\n packages: string[],\n projectRoot: string,\n): Promise<ConsumerManifest> {\n const aggregatedManifest: ConsumerManifest = {\n version: '1.0.0',\n timestamp: Date.now(),\n objects: {},\n };\n\n for (const packageName of packages) {\n try {\n const packageDir = path.join(projectRoot, 'node_modules', packageName);\n\n // Load package.json for version and export information\n const packageJsonPath = path.join(packageDir, 'package.json');\n let packageJson: ConsumerPackageJson;\n try {\n const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');\n packageJson = JSON.parse(packageJsonContent) as ConsumerPackageJson;\n } catch {\n console.warn(\n `[smrt:consumer] Could not read package.json for ${packageName}`,\n );\n continue;\n }\n\n // Try multiple manifest locations\n const manifestCandidates = [\n path.join(packageDir, 'dist', 'manifest', 'static-manifest.js'),\n path.join(packageDir, 'dist', 'manifest.json'),\n path.join(packageDir, 'manifest.json'),\n ];\n\n for (const manifestPath of manifestCandidates) {\n if (fs.existsSync(manifestPath)) {\n // Import or read the manifest\n let manifest: Partial<ConsumerManifest> | undefined;\n if (manifestPath.endsWith('.js')) {\n const manifestModule = await import(manifestPath);\n manifest = manifestModule.staticManifest || manifestModule.default;\n } else {\n const manifestContent = fs.readFileSync(manifestPath, 'utf-8');\n manifest = JSON.parse(manifestContent) as Partial<ConsumerManifest>;\n }\n\n if (manifest?.objects) {\n console.log(\n `[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`,\n );\n\n // ENHANCED: Preserve package metadata for each object\n for (const [objectName, objectDef] of Object.entries(\n manifest.objects,\n )) {\n const def = objectDef;\n\n aggregatedManifest.objects[objectName] = {\n ...def,\n // Ensure package metadata is preserved/set\n packageName:\n def.packageName || manifest.packageName || packageName,\n packageVersion:\n def.packageVersion ||\n manifest.packageVersion ||\n packageJson.version,\n // Add fallback import paths if missing\n importPath: def.importPath || determineImportPath(packageJson),\n exportName: def.exportName || def.className || objectName,\n collectionExportName:\n def.collectionExportName ||\n `${def.className || objectName}Collection`,\n };\n }\n\n break; // Use first found manifest for this package\n }\n }\n }\n } catch (error) {\n console.warn(\n `[smrt:consumer] Error loading manifest from ${packageName}:`,\n error,\n );\n }\n }\n\n return aggregatedManifest;\n}\n\n/**\n * Determine import path from package.json\n */\nfunction determineImportPath(packageJson: ConsumerPackageJson): string {\n const packageName = packageJson.name;\n\n if (!packageName) {\n throw new Error('Package name not found in package.json');\n }\n\n // Strategy 1: Check for specific exports\n if (packageJson.exports) {\n // Check for objects export\n if (packageJson.exports['./objects']) {\n return `${packageName}/objects`;\n }\n\n // Check for main export\n const mainExport = packageJson.exports['.'];\n if (mainExport) {\n // Handle conditional exports\n if (typeof mainExport === 'object' && mainExport !== null) {\n const conditional = mainExport as Record<string, unknown>;\n if (conditional.import) {\n return packageName;\n }\n if (conditional.default) {\n return packageName;\n }\n }\n return packageName;\n }\n }\n\n // Strategy 2: Check main field\n if (packageJson.main) {\n return packageName;\n }\n\n // Strategy 3: Fallback to package name\n return packageName;\n}\n\n/**\n * Save aggregated manifest to .smrt/manifest.json for CLI discovery\n */\nasync function saveAggregatedManifest(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const manifestPath = path.join(smrtDir, 'manifest.json');\n\n try {\n // Create .smrt directory if it doesn't exist\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Write manifest\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');\n\n console.log(\n `[smrt:consumer] Saved aggregated manifest to .smrt/manifest.json (${Object.keys(manifest.objects).length} objects)`,\n );\n } catch (error) {\n console.warn('[smrt:consumer] Failed to save aggregated manifest:', error);\n }\n}\n\n/**\n * Generate registration file for CLI class loading\n *\n * Creates .smrt/register.js with static imports and registrations\n * for all external SMRT objects discovered during build.\n */\nasync function generateRegistrationFile(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const registerPath = path.join(smrtDir, 'register.js');\n\n // Build import statements and registrations\n const imports: string[] = [];\n const registrations: string[] = [];\n let importedEntryCount = 0;\n let registeredObjectCount = 0;\n\n const manifestObjects = manifest.objects;\n const manifestObjectLookup = new Map<string, ConsumerObjectDefinition>();\n for (const [key, def] of Object.entries(manifestObjects)) {\n const candidate = def;\n const lookupKeys = [\n key,\n key.includes(':') ? key.split(':').pop() : undefined,\n candidate.qualifiedName,\n candidate.className,\n candidate.exportName,\n ];\n\n for (const lookupKey of lookupKeys) {\n if (lookupKey && !manifestObjectLookup.has(lookupKey)) {\n manifestObjectLookup.set(lookupKey, candidate);\n }\n }\n }\n\n const collectionClassMemo = new WeakMap<object, boolean>();\n\n const isCollectionClass = (\n def: ConsumerObjectDefinition | undefined,\n seen = new Set<string>(),\n ): boolean => {\n if (!def || typeof def !== 'object') {\n return false;\n }\n\n const cached = collectionClassMemo.get(def);\n if (cached !== undefined) {\n return cached;\n }\n\n if (\n def?.extends === 'SmrtCollection' ||\n def?.extendsTypeArg !== undefined\n ) {\n collectionClassMemo.set(def, true);\n return true;\n }\n\n const parentName = def?.extendsQualified || def?.extends;\n if (!parentName || seen.has(parentName)) {\n collectionClassMemo.set(def, false);\n return false;\n }\n seen.add(parentName);\n\n const parentDef = manifestObjectLookup.get(parentName);\n const isCollection = parentDef ? isCollectionClass(parentDef, seen) : false;\n collectionClassMemo.set(def, isCollection);\n\n return isCollection;\n };\n\n for (const [objectName, objectDef] of Object.entries(manifestObjects)) {\n const def = objectDef;\n\n // Skip local objects (they're imported from local entry point)\n if (!def.packageName || def.packageName === manifest.packageName) {\n continue;\n }\n\n const importPath = def.importPath || def.packageName;\n const exportName = def.exportName || def.className || objectName;\n const collectionExportName = def.collectionExportName;\n const hasCollection = def.hasCollection; // Check if collection class actually exists\n const tableName = def.collection || objectName.toLowerCase();\n\n // Generate import statement\n // Only import collection if it exists (hasCollection is truthy)\n if (hasCollection && collectionExportName) {\n imports.push(\n `import { ${exportName}, ${collectionExportName} } from '${importPath}';`,\n );\n } else {\n imports.push(`import { ${exportName} } from '${importPath}';`);\n }\n importedEntryCount++;\n\n if (isCollectionClass(def)) {\n continue;\n }\n\n // Generate registration calls\n // The import above already triggers the @smrt() decorator which registers the class\n // properly with its simple name and qualified name. We call register() again with\n // an empty config just to ensure the class is registered (in case it lacks a decorator).\n // Do NOT pass { name: qualifiedName } as that creates a separate registry entry.\n registrations.push(\n `ObjectRegistry.register(${exportName}, { name: ${JSON.stringify(exportName)}, packageName: ${JSON.stringify(def.packageName)} });`,\n );\n\n // Only register collection if it exists\n if (hasCollection && collectionExportName) {\n registrations.push(\n `ObjectRegistry.registerCollection('${tableName}', ${collectionExportName});`,\n );\n }\n\n registeredObjectCount++;\n }\n\n // Skip generation if no external entries\n if (importedEntryCount === 0) {\n console.log('[smrt:consumer] No external entries - skipping register.js');\n return;\n }\n\n const registeredObjectLabel =\n registeredObjectCount === 1 ? 'object' : 'objects';\n\n // Generate file content\n const content = `/**\n * Auto-generated by @happyvertical/smrt-core/consumer-plugin\n * DO NOT EDIT - This file is regenerated on every build\n *\n * Registers SMRT objects from external packages for CLI discovery.\n * Generated at: ${new Date().toISOString()}\n */\n\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n${imports.join('\\n')}\n\n// Register all objects (executed during module evaluation)\n${registrations.join('\\n')}\n\nexport function registerAll() {\n // Objects are already registered during module evaluation\n console.log('[smrt:register] Registered ${registeredObjectCount} external ${registeredObjectLabel}');\n}\n`;\n\n // Create .smrt directory if needed\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Write registration file\n fs.writeFileSync(registerPath, content, 'utf-8');\n\n console.log(\n `[smrt:consumer] Generated .smrt/register.js with ${importedEntryCount} external entries (${registeredObjectCount} registered ${registeredObjectLabel})`,\n );\n}\n\n/**\n * Generate project-specific types\n */\nasync function generateProjectTypes(\n typeManifest: ConsumerManifest,\n typesDir: string,\n projectRoot: string,\n): Promise<void> {\n if (!typeManifest || Object.keys(typeManifest.objects).length === 0) {\n console.log(\n '[smrt:consumer] No SMRT objects found, skipping type generation',\n );\n return;\n }\n\n await generateDeclarations({\n // The aggregated manifest is a runtime SMRT manifest assembled from external\n // package manifests; it is intentionally typed loosely at the JSON boundary,\n // so narrow it to the declaration generator's strict manifest shape here.\n manifest: typeManifest as unknown as SmartObjectManifest,\n outDir: typesDir,\n projectRoot,\n includeVirtualModules: true,\n includeObjectTypes: true,\n });\n\n console.log(\n `[smrt:consumer] Generated types for ${Object.keys(typeManifest.objects).length} objects`,\n );\n}\n\n/**\n * Get type file name for virtual module\n */\nfunction getTypeFileName(virtualModule: string): string {\n const moduleMap: Record<string, string> = {\n '@smrt/routes': 'smrt-routes.d.ts',\n '@smrt/client': 'smrt-client.d.ts',\n '@smrt/mcp': 'smrt-mcp.d.ts',\n '@smrt/types': 'smrt-types.d.ts',\n '@smrt/manifest': 'smrt-manifest.d.ts',\n };\n return moduleMap[virtualModule] || 'smrt-unknown.d.ts';\n}\n\n/**\n * Fallback modules for when types aren't available\n */\nfunction generateFallbackRoutesModule(): string {\n return `\n// Fallback routes module\nexport function setupRoutes(app) {\n console.warn('[smrt:consumer] No routes available - SMRT packages may not be properly configured');\n}\nexport default setupRoutes;\n`;\n}\n\nfunction generateFallbackClientModule(manifest: ConsumerManifest): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `\n// Fallback client module\nexport function createClient(basePath = '/api/v1') {\n console.warn('[smrt:consumer] No API client available - SMRT packages may not be properly configured');\n return {};\n}\nexport default createClient;\n`;\n }\n\n // Generate basic client from manifest\n const clientMethods = objects\n .map(([name, obj]) => {\n const { collection } = obj;\n return `\n ${name}: {\n list: () => fetch(basePath + '/${collection}').then(r => r.json()),\n get: (id) => fetch(basePath + '/${collection}/' + id).then(r => r.json()),\n create: (data) => fetch(basePath + '/${collection}', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then(r => r.json()),\n update: (id, data) => fetch(basePath + '/${collection}/' + id, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then(r => r.json()),\n delete: (id) => fetch(basePath + '/${collection}/' + id, {\n method: 'DELETE'\n }).then(r => r.ok)\n }`;\n })\n .join(',');\n\n return `\n// Auto-generated API client from SMRT consumer\nexport function createClient(basePath = '/api/v1') {\n return {${clientMethods}\n };\n}\nexport default createClient;\n`;\n}\n\nfunction generateFallbackMcpModule(): string {\n return `\n// Fallback MCP module\nexport const tools = [];\nexport function createMCPServer() {\n console.warn('[smrt:consumer] No MCP tools available - SMRT packages may not be properly configured');\n return { name: 'smrt-consumer', version: '1.0.0', tools: [] };\n}\nexport default createMCPServer;\n`;\n}\n\nfunction generateFallbackTypesModule(manifest: ConsumerManifest): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `// No types available`;\n }\n\n // Generate basic interfaces\n const interfaces = objects.map(([_name, obj]) => {\n return `export interface ${obj.className}Data {\n id?: string;\n created_at?: string;\n updated_at?: string;\n [key: string]: any;\n}`;\n });\n\n return interfaces.join('\\n\\n');\n}\n\nfunction generateFallbackManifestModule(manifest: ConsumerManifest): string {\n return `\n// Auto-generated manifest from SMRT consumer\nexport const manifest = ${JSON.stringify(manifest, null, 2)};\nexport default manifest;\n`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/consumer-plugin/index.ts"],"sourcesContent":["/**\n * Vite plugin for consuming SMRT packages\n * Solves virtual module resolution in downstream projects\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Plugin } from 'vite';\nimport { generateDeclarations } from '../prebuild/index.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\n\n/**\n * Loosely-typed view of an object definition as carried by an external\n * package's static manifest. The static manifests are read from JSON at the\n * package boundary, so only the fields this plugin consumes are typed; the\n * index signature preserves any additional fields (e.g. for spreads). This is\n * a structural superset of a manifest `SmartObjectDefinition` plus the\n * consumer-only `hasCollection` marker.\n */\ninterface ConsumerObjectDefinition {\n className?: string;\n packageName?: string;\n packageVersion?: string;\n qualifiedName?: string;\n importPath?: string;\n exportName?: string;\n collectionExportName?: string;\n hasCollection?: boolean;\n collection?: string;\n extends?: string;\n extendsQualified?: string;\n extendsTypeArg?: string;\n [key: string]: unknown;\n}\n\n/**\n * Aggregated manifest assembled by the consumer plugin from one or more\n * external package manifests. Loosely typed because the inputs originate from\n * JSON read at the package boundary.\n */\ninterface ConsumerManifest {\n version: string;\n timestamp: number;\n packageName?: string;\n packageVersion?: string;\n objects: Record<string, ConsumerObjectDefinition>;\n}\n\n/**\n * Minimal structural shape of a parsed `package.json` consumed here (name,\n * version, and the export map used to derive import paths). The index\n * signature keeps the remaining fields accessible.\n */\ninterface ConsumerPackageJson {\n name?: string;\n version?: string;\n main?: string;\n exports?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface SmrtConsumerOptions {\n /** SMRT packages to scan (e.g., ['@my-org/products', '@my-org/content']) */\n packages?: string[];\n /** Generate TypeScript declarations */\n generateTypes?: boolean;\n /** Output directory for generated types */\n typesDir?: string;\n /** Project root path */\n projectRoot?: string;\n /** SvelteKit integration mode */\n svelteKit?: boolean;\n /** Use static types only (for federation builds) */\n staticTypes?: boolean;\n /** Disable file scanning */\n disableScanning?: boolean;\n}\n\nconst VIRTUAL_MODULES = {\n '@smrt/routes': 'smrt:routes',\n '@smrt/client': 'smrt:client',\n '@smrt/mcp': 'smrt:mcp',\n '@smrt/types': 'smrt:types',\n '@smrt/manifest': 'smrt:manifest',\n};\n\n/**\n * Consumer plugin for projects that use SMRT packages\n */\nexport function smrtConsumer(options: SmrtConsumerOptions = {}): Plugin {\n const {\n packages = [],\n generateTypes = true,\n typesDir = 'src/types/smrt-generated',\n projectRoot = process.cwd(),\n disableScanning = false,\n } = options;\n\n let smrtPackages: string[] = [];\n let typeManifest: ConsumerManifest | null = null;\n let typesGenerated = false;\n\n return {\n name: 'smrt-consumer',\n\n async buildStart() {\n console.log('[smrt:consumer] Initializing SMRT consumer plugin');\n\n // Discover SMRT packages if not explicitly specified\n if (packages.length === 0 && !disableScanning) {\n smrtPackages = await discoverSmrtPackages(projectRoot);\n } else {\n smrtPackages = packages;\n }\n\n if (smrtPackages.length > 0) {\n console.log(\n `[smrt:consumer] Found SMRT packages: ${smrtPackages.join(', ')}`,\n );\n\n // Aggregate type manifests from discovered packages\n typeManifest = await aggregateTypeManifests(smrtPackages, projectRoot);\n\n // Save aggregated manifest for CLI discovery\n await saveAggregatedManifest(typeManifest, projectRoot);\n\n // Generate registration file for CLI class loading\n await generateRegistrationFile(typeManifest, projectRoot);\n\n // Generate types if requested\n if (generateTypes && !typesGenerated) {\n await generateProjectTypes(typeManifest, typesDir, projectRoot);\n typesGenerated = true;\n }\n } else {\n console.log('[smrt:consumer] No SMRT packages found');\n typeManifest = { version: '1.0.0', timestamp: Date.now(), objects: {} };\n }\n },\n\n resolveId(id, _importer) {\n // Resolve virtual modules to generated type declarations\n if (id in VIRTUAL_MODULES) {\n const typeFileName = getTypeFileName(id);\n const typePath = path.join(projectRoot, typesDir, typeFileName);\n\n // If types file exists, resolve to it\n if (fs.existsSync(typePath)) {\n return typePath;\n }\n\n // Otherwise use virtual module ID for runtime resolution\n return `\\0${VIRTUAL_MODULES[id as keyof typeof VIRTUAL_MODULES]}`;\n }\n return null;\n },\n\n async load(id) {\n // Handle virtual modules if types aren't available\n const cleanId = id.startsWith('\\0') ? id.slice(1) : id;\n\n if (!typeManifest) {\n typeManifest = { version: '1.0.0', timestamp: Date.now(), objects: {} };\n }\n\n switch (cleanId) {\n case 'smrt:routes':\n return generateFallbackRoutesModule();\n\n case 'smrt:client':\n return generateFallbackClientModule(typeManifest);\n\n case 'smrt:mcp':\n return generateFallbackMcpModule();\n\n case 'smrt:types':\n return generateFallbackTypesModule(typeManifest);\n\n case 'smrt:manifest':\n return generateFallbackManifestModule(typeManifest);\n\n default:\n return null;\n }\n },\n };\n}\n\n/**\n * Discover SMRT packages from a consumer app's dependencies.\n *\n * Intentional split (#1579): this **consumer-plugin** path is async and\n * resolves SMRT packages from the downstream app's `package.json` dependency\n * names (`@have/`/`smrt` heuristic + `hasSmrtManifest` probe) inside the Vite\n * consumer plugin. It is deliberately separate from the build-time\n * `discoverSmrtPackages()` in `src/manifest/discover-smrt-packages.ts` — a\n * synchronous, lockfile-cached `node_modules` manifest scan used for manifest\n * generation. Different inputs, contexts, and lifecycles, not duplicated logic.\n */\nasync function discoverSmrtPackages(projectRoot: string): Promise<string[]> {\n const packages: string[] = [];\n const nodeModulesPath = path.join(projectRoot, 'node_modules');\n\n if (!fs.existsSync(nodeModulesPath)) {\n return packages;\n }\n\n try {\n // Check package.json for workspace dependencies\n const packageJsonPath = path.join(projectRoot, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.peerDependencies,\n };\n\n // Look for packages that likely contain SMRT objects\n for (const [name, version] of Object.entries(allDeps)) {\n if (\n typeof version === 'string' &&\n (name.includes('smrt') ||\n name.includes('@have/') ||\n (await hasSmrtManifest(nodeModulesPath, name)))\n ) {\n packages.push(name);\n }\n }\n }\n } catch (error) {\n console.warn('[smrt:consumer] Error discovering packages:', error);\n }\n\n return packages;\n}\n\n/**\n * Check if a package has SMRT manifest\n */\nasync function hasSmrtManifest(\n nodeModulesPath: string,\n packageName: string,\n): Promise<boolean> {\n const packagePath = path.join(nodeModulesPath, packageName);\n const manifestPath = path.join(\n packagePath,\n 'dist',\n 'manifest',\n 'static-manifest.js',\n );\n return fs.existsSync(manifestPath);\n}\n\n/**\n * Aggregate type manifests from multiple packages\n */\nasync function aggregateTypeManifests(\n packages: string[],\n projectRoot: string,\n): Promise<ConsumerManifest> {\n const aggregatedManifest: ConsumerManifest = {\n version: '1.0.0',\n timestamp: Date.now(),\n objects: {},\n };\n\n for (const packageName of packages) {\n try {\n const packageDir = path.join(projectRoot, 'node_modules', packageName);\n\n // Load package.json for version and export information\n const packageJsonPath = path.join(packageDir, 'package.json');\n let packageJson: ConsumerPackageJson;\n try {\n const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');\n packageJson = JSON.parse(packageJsonContent) as ConsumerPackageJson;\n } catch {\n console.warn(\n `[smrt:consumer] Could not read package.json for ${packageName}`,\n );\n continue;\n }\n\n // Try multiple manifest locations\n const manifestCandidates = [\n path.join(packageDir, 'dist', 'manifest', 'static-manifest.js'),\n path.join(packageDir, 'dist', 'manifest.json'),\n path.join(packageDir, 'manifest.json'),\n ];\n\n for (const manifestPath of manifestCandidates) {\n if (fs.existsSync(manifestPath)) {\n // Import or read the manifest\n let manifest: Partial<ConsumerManifest> | undefined;\n if (manifestPath.endsWith('.js')) {\n const manifestModule = await import(manifestPath);\n manifest = manifestModule.staticManifest || manifestModule.default;\n } else {\n const manifestContent = fs.readFileSync(manifestPath, 'utf-8');\n manifest = JSON.parse(manifestContent) as Partial<ConsumerManifest>;\n }\n\n if (manifest?.objects) {\n console.log(\n `[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`,\n );\n\n // ENHANCED: Preserve package metadata for each object\n for (const [objectName, objectDef] of Object.entries(\n manifest.objects,\n )) {\n const def = objectDef;\n\n aggregatedManifest.objects[objectName] = {\n ...def,\n // Ensure package metadata is preserved/set\n packageName:\n def.packageName || manifest.packageName || packageName,\n packageVersion:\n def.packageVersion ||\n manifest.packageVersion ||\n packageJson.version,\n // Add fallback import paths if missing\n importPath: def.importPath || determineImportPath(packageJson),\n exportName: def.exportName || def.className || objectName,\n collectionExportName:\n def.collectionExportName ||\n `${def.className || objectName}Collection`,\n };\n }\n\n break; // Use first found manifest for this package\n }\n }\n }\n } catch (error) {\n console.warn(\n `[smrt:consumer] Error loading manifest from ${packageName}:`,\n error,\n );\n }\n }\n\n return aggregatedManifest;\n}\n\n/**\n * Determine import path from package.json\n */\nfunction determineImportPath(packageJson: ConsumerPackageJson): string {\n const packageName = packageJson.name;\n\n if (!packageName) {\n throw new Error('Package name not found in package.json');\n }\n\n // Strategy 1: Check for specific exports\n if (packageJson.exports) {\n // Check for objects export\n if (packageJson.exports['./objects']) {\n return `${packageName}/objects`;\n }\n\n // Check for main export\n const mainExport = packageJson.exports['.'];\n if (mainExport) {\n // Handle conditional exports\n if (typeof mainExport === 'object' && mainExport !== null) {\n const conditional = mainExport as Record<string, unknown>;\n if (conditional.import) {\n return packageName;\n }\n if (conditional.default) {\n return packageName;\n }\n }\n return packageName;\n }\n }\n\n // Strategy 2: Check main field\n if (packageJson.main) {\n return packageName;\n }\n\n // Strategy 3: Fallback to package name\n return packageName;\n}\n\n/**\n * Save aggregated manifest to .smrt/manifest.json for CLI discovery.\n *\n * Merge-preserving: `smrtPlugin()` writes the project's own scanned objects\n * to the same file (`writeLocalManifest`, issue #963), and both writes happen\n * in parallel `buildStart` hooks — so a plain overwrite here would clobber\n * the local objects whenever this plugin's write lands last (issue #1760\n * review). Local field metadata would then silently vanish from CLI schema\n * commands and from server runtimes that seed `.smrt/manifest.json`, dropping\n * domain columns on write. This function therefore only ADDS/refreshes the\n * external-package entries it owns and preserves everything else already in\n * the file (including the top-level `packageName` the local write sets).\n */\nasync function saveAggregatedManifest(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const manifestPath = path.join(smrtDir, 'manifest.json');\n\n try {\n // Create .smrt directory if it doesn't exist\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Merge with whatever is on disk: existing entries (typically the local\n // project's objects written by smrtPlugin) are preserved; aggregated\n // external entries win for the qualified names this plugin owns.\n let merged: ConsumerManifest = manifest;\n if (fs.existsSync(manifestPath)) {\n try {\n const existing = JSON.parse(\n fs.readFileSync(manifestPath, 'utf-8'),\n ) as Partial<ConsumerManifest>;\n if (existing && typeof existing.objects === 'object') {\n merged = {\n ...existing,\n ...manifest,\n // The aggregated manifest carries no packageName; keep the local\n // project's (used as the manifest cache key at runtime).\n ...(existing.packageName\n ? { packageName: existing.packageName }\n : {}),\n objects: { ...existing.objects, ...manifest.objects },\n };\n }\n } catch {\n // Unreadable/corrupt existing file — fall back to a plain write.\n }\n }\n\n // Write manifest\n fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2), 'utf-8');\n\n console.log(\n `[smrt:consumer] Saved aggregated manifest to .smrt/manifest.json (${Object.keys(merged.objects).length} objects)`,\n );\n } catch (error) {\n console.warn('[smrt:consumer] Failed to save aggregated manifest:', error);\n }\n}\n\n/**\n * Generate registration file for CLI class loading\n *\n * Creates .smrt/register.js with static imports and registrations\n * for all external SMRT objects discovered during build.\n */\nasync function generateRegistrationFile(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const registerPath = path.join(smrtDir, 'register.js');\n\n // Build import statements and registrations\n const imports: string[] = [];\n const registrations: string[] = [];\n let importedEntryCount = 0;\n let registeredObjectCount = 0;\n\n const manifestObjects = manifest.objects;\n const manifestObjectLookup = new Map<string, ConsumerObjectDefinition>();\n for (const [key, def] of Object.entries(manifestObjects)) {\n const candidate = def;\n const lookupKeys = [\n key,\n key.includes(':') ? key.split(':').pop() : undefined,\n candidate.qualifiedName,\n candidate.className,\n candidate.exportName,\n ];\n\n for (const lookupKey of lookupKeys) {\n if (lookupKey && !manifestObjectLookup.has(lookupKey)) {\n manifestObjectLookup.set(lookupKey, candidate);\n }\n }\n }\n\n const collectionClassMemo = new WeakMap<object, boolean>();\n\n const isCollectionClass = (\n def: ConsumerObjectDefinition | undefined,\n seen = new Set<string>(),\n ): boolean => {\n if (!def || typeof def !== 'object') {\n return false;\n }\n\n const cached = collectionClassMemo.get(def);\n if (cached !== undefined) {\n return cached;\n }\n\n if (\n def?.extends === 'SmrtCollection' ||\n def?.extendsTypeArg !== undefined\n ) {\n collectionClassMemo.set(def, true);\n return true;\n }\n\n const parentName = def?.extendsQualified || def?.extends;\n if (!parentName || seen.has(parentName)) {\n collectionClassMemo.set(def, false);\n return false;\n }\n seen.add(parentName);\n\n const parentDef = manifestObjectLookup.get(parentName);\n const isCollection = parentDef ? isCollectionClass(parentDef, seen) : false;\n collectionClassMemo.set(def, isCollection);\n\n return isCollection;\n };\n\n for (const [objectName, objectDef] of Object.entries(manifestObjects)) {\n const def = objectDef;\n\n // Skip local objects (they're imported from local entry point)\n if (!def.packageName || def.packageName === manifest.packageName) {\n continue;\n }\n\n const importPath = def.importPath || def.packageName;\n const exportName = def.exportName || def.className || objectName;\n const collectionExportName = def.collectionExportName;\n const hasCollection = def.hasCollection; // Check if collection class actually exists\n const tableName = def.collection || objectName.toLowerCase();\n\n // Generate import statement\n // Only import collection if it exists (hasCollection is truthy)\n if (hasCollection && collectionExportName) {\n imports.push(\n `import { ${exportName}, ${collectionExportName} } from '${importPath}';`,\n );\n } else {\n imports.push(`import { ${exportName} } from '${importPath}';`);\n }\n importedEntryCount++;\n\n if (isCollectionClass(def)) {\n continue;\n }\n\n // Generate registration calls\n // The import above already triggers the @smrt() decorator which registers the class\n // properly with its simple name and qualified name. We call register() again with\n // an empty config just to ensure the class is registered (in case it lacks a decorator).\n // Do NOT pass { name: qualifiedName } as that creates a separate registry entry.\n registrations.push(\n `ObjectRegistry.register(${exportName}, { name: ${JSON.stringify(exportName)}, packageName: ${JSON.stringify(def.packageName)} });`,\n );\n\n // Only register collection if it exists\n if (hasCollection && collectionExportName) {\n registrations.push(\n `ObjectRegistry.registerCollection('${tableName}', ${collectionExportName});`,\n );\n }\n\n registeredObjectCount++;\n }\n\n // Skip generation if no external entries\n if (importedEntryCount === 0) {\n console.log('[smrt:consumer] No external entries - skipping register.js');\n return;\n }\n\n const registeredObjectLabel =\n registeredObjectCount === 1 ? 'object' : 'objects';\n\n // Generate file content\n const content = `/**\n * Auto-generated by @happyvertical/smrt-core/consumer-plugin\n * DO NOT EDIT - This file is regenerated on every build\n *\n * Registers SMRT objects from external packages for CLI discovery.\n * Generated at: ${new Date().toISOString()}\n */\n\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n${imports.join('\\n')}\n\n// Register all objects (executed during module evaluation)\n${registrations.join('\\n')}\n\nexport function registerAll() {\n // Objects are already registered during module evaluation\n console.log('[smrt:register] Registered ${registeredObjectCount} external ${registeredObjectLabel}');\n}\n`;\n\n // Create .smrt directory if needed\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Write registration file\n fs.writeFileSync(registerPath, content, 'utf-8');\n\n console.log(\n `[smrt:consumer] Generated .smrt/register.js with ${importedEntryCount} external entries (${registeredObjectCount} registered ${registeredObjectLabel})`,\n );\n}\n\n/**\n * Generate project-specific types\n */\nasync function generateProjectTypes(\n typeManifest: ConsumerManifest,\n typesDir: string,\n projectRoot: string,\n): Promise<void> {\n if (!typeManifest || Object.keys(typeManifest.objects).length === 0) {\n console.log(\n '[smrt:consumer] No SMRT objects found, skipping type generation',\n );\n return;\n }\n\n await generateDeclarations({\n // The aggregated manifest is a runtime SMRT manifest assembled from external\n // package manifests; it is intentionally typed loosely at the JSON boundary,\n // so narrow it to the declaration generator's strict manifest shape here.\n manifest: typeManifest as unknown as SmartObjectManifest,\n outDir: typesDir,\n projectRoot,\n includeVirtualModules: true,\n includeObjectTypes: true,\n });\n\n console.log(\n `[smrt:consumer] Generated types for ${Object.keys(typeManifest.objects).length} objects`,\n );\n}\n\n/**\n * Get type file name for virtual module\n */\nfunction getTypeFileName(virtualModule: string): string {\n const moduleMap: Record<string, string> = {\n '@smrt/routes': 'smrt-routes.d.ts',\n '@smrt/client': 'smrt-client.d.ts',\n '@smrt/mcp': 'smrt-mcp.d.ts',\n '@smrt/types': 'smrt-types.d.ts',\n '@smrt/manifest': 'smrt-manifest.d.ts',\n };\n return moduleMap[virtualModule] || 'smrt-unknown.d.ts';\n}\n\n/**\n * Fallback modules for when types aren't available\n */\nfunction generateFallbackRoutesModule(): string {\n return `\n// Fallback routes module\nexport function setupRoutes(app) {\n console.warn('[smrt:consumer] No routes available - SMRT packages may not be properly configured');\n}\nexport default setupRoutes;\n`;\n}\n\nfunction generateFallbackClientModule(manifest: ConsumerManifest): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `\n// Fallback client module\nexport function createClient(basePath = '/api/v1') {\n console.warn('[smrt:consumer] No API client available - SMRT packages may not be properly configured');\n return {};\n}\nexport default createClient;\n`;\n }\n\n // Generate basic client from manifest\n const clientMethods = objects\n .map(([name, obj]) => {\n const { collection } = obj;\n return `\n ${name}: {\n list: () => fetch(basePath + '/${collection}').then(r => r.json()),\n get: (id) => fetch(basePath + '/${collection}/' + id).then(r => r.json()),\n create: (data) => fetch(basePath + '/${collection}', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then(r => r.json()),\n update: (id, data) => fetch(basePath + '/${collection}/' + id, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then(r => r.json()),\n delete: (id) => fetch(basePath + '/${collection}/' + id, {\n method: 'DELETE'\n }).then(r => r.ok)\n }`;\n })\n .join(',');\n\n return `\n// Auto-generated API client from SMRT consumer\nexport function createClient(basePath = '/api/v1') {\n return {${clientMethods}\n };\n}\nexport default createClient;\n`;\n}\n\nfunction generateFallbackMcpModule(): string {\n return `\n// Fallback MCP module\nexport const tools = [];\nexport function createMCPServer() {\n console.warn('[smrt:consumer] No MCP tools available - SMRT packages may not be properly configured');\n return { name: 'smrt-consumer', version: '1.0.0', tools: [] };\n}\nexport default createMCPServer;\n`;\n}\n\nfunction generateFallbackTypesModule(manifest: ConsumerManifest): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `// No types available`;\n }\n\n // Generate basic interfaces\n const interfaces = objects.map(([_name, obj]) => {\n return `export interface ${obj.className}Data {\n id?: string;\n created_at?: string;\n updated_at?: string;\n [key: string]: any;\n}`;\n });\n\n return interfaces.join('\\n\\n');\n}\n\nfunction generateFallbackManifestModule(manifest: ConsumerManifest): string {\n return `\n// Auto-generated manifest from SMRT consumer\nexport const manifest = ${JSON.stringify(manifest, null, 2)};\nexport default manifest;\n`;\n}\n"],"mappings":"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"}
|
package/dist/dispatch/index.d.ts
CHANGED
|
@@ -42,6 +42,6 @@ export { DispatchCollection } from './collections/Dispatches.js';
|
|
|
42
42
|
export { DispatchSubscriptionCollection } from './collections/DispatchSubscriptions.js';
|
|
43
43
|
export { Dispatch, type DispatchData } from './models/Dispatch.js';
|
|
44
44
|
export { DispatchSubscription, type DispatchSubscriptionData, } from './models/DispatchSubscription.js';
|
|
45
|
-
export { type DispatchTenantResolver, type DispatchTenantScope, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, } from './tenant-resolver.js';
|
|
45
|
+
export { type DispatchTenantResolver, type DispatchTenantScope, isTenantScopedClassResolved, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, setTenantScopedClassResolver, } from './tenant-resolver.js';
|
|
46
46
|
export type { DispatchBusOptions, DispatchCleanupOptions, DispatchCleanupResult, DispatchEmitOptions, DispatchHandler, DispatchListOptions, DispatchMetadata, DispatchProcessOptions, DispatchRetryOptions, DispatchStatus, DispatchSubscribeOptions, } from './types.js';
|
|
47
47
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dispatch/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAExF,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dispatch/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAExF,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
package/dist/dispatch/index.js
CHANGED
|
@@ -2,6 +2,6 @@ import { Dispatch } from "./models/Dispatch.js";
|
|
|
2
2
|
import { DispatchCollection } from "./collections/Dispatches.js";
|
|
3
3
|
import { DispatchSubscription } from "./models/DispatchSubscription.js";
|
|
4
4
|
import { DispatchSubscriptionCollection } from "./collections/DispatchSubscriptions.js";
|
|
5
|
-
import { resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver } from "./tenant-resolver.js";
|
|
5
|
+
import { isTenantScopedClassResolved, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, setTenantScopedClassResolver } from "./tenant-resolver.js";
|
|
6
6
|
import { DispatchBus, createDispatchBus } from "./bus.js";
|
|
7
|
-
export { Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, createDispatchBus, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver };
|
|
7
|
+
export { Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, createDispatchBus, isTenantScopedClassResolved, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, setTenantScopedClassResolver };
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
export type DispatchTenantResolver = () => string | null | undefined;
|
|
26
26
|
declare global {
|
|
27
27
|
var __smrtDispatchTenantResolver: DispatchTenantResolver | undefined;
|
|
28
|
+
var __smrtTenantScopedClassResolver: ((className: string) => boolean) | undefined;
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
31
|
* Register the tenant resolver the DispatchBus uses to derive the active
|
|
@@ -95,4 +96,26 @@ export declare function resolveDispatchTenantScope(): DispatchTenantScope;
|
|
|
95
96
|
* scope.
|
|
96
97
|
*/
|
|
97
98
|
export declare function resolveDispatchTenantId(): string | null | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Register the resolver that reports whether a class is tenant-scoped, covering
|
|
101
|
+
* BOTH registration forms (S #1782). Core's `ObjectRegistry.isTenantScoped`
|
|
102
|
+
* recognizes `@smrt({ tenantScoped })` and the manifest-merged `@TenantScoped()`
|
|
103
|
+
* config, but the standalone `@TenantScoped()` decorator (smrt-tenancy) records
|
|
104
|
+
* its config only in the tenancy registry at decoration time — invisible to core
|
|
105
|
+
* until/unless a manifest carries it. Tenancy fills this slot at
|
|
106
|
+
* `enableTenancy()` so core-side fail-closed guards (the generated REST read
|
|
107
|
+
* scope) recognize tenant-scoped classes regardless of registration form or
|
|
108
|
+
* manifest timing. Mirrors {@link setDispatchTenantResolver}.
|
|
109
|
+
*
|
|
110
|
+
* @param resolver - Predicate returning `true` for tenant-scoped class names, or
|
|
111
|
+
* `undefined` to clear (which `disableTenancy()` does).
|
|
112
|
+
*/
|
|
113
|
+
export declare function setTenantScopedClassResolver(resolver: ((className: string) => boolean) | undefined): void;
|
|
114
|
+
/**
|
|
115
|
+
* Whether the tenancy layer reports `className` as tenant-scoped. Returns
|
|
116
|
+
* `false` when tenancy is disabled (no resolver) or the resolver throws.
|
|
117
|
+
*
|
|
118
|
+
* @param className - Class name (simple or qualified) to check.
|
|
119
|
+
*/
|
|
120
|
+
export declare function isTenantScopedClassResolved(className: string): boolean;
|
|
98
121
|
//# sourceMappingURL=tenant-resolver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-resolver.d.ts","sourceRoot":"","sources":["../../src/dispatch/tenant-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAErE,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,4BAA4B,EAAE,sBAAsB,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"tenant-resolver.d.ts","sourceRoot":"","sources":["../../src/dispatch/tenant-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAErE,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,4BAA4B,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAErE,IAAI,+BAA+B,EAC/B,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAChC,SAAS,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,sBAAsB,GAAG,SAAS,GAC3C,IAAI,CAEN;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,IAAI,mBAAmB,CAehE;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,CAYnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,GACrD,IAAI,CAEN;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAUtE"}
|
|
@@ -60,7 +60,39 @@ function resolveDispatchTenantId() {
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Register the resolver that reports whether a class is tenant-scoped, covering
|
|
65
|
+
* BOTH registration forms (S #1782). Core's `ObjectRegistry.isTenantScoped`
|
|
66
|
+
* recognizes `@smrt({ tenantScoped })` and the manifest-merged `@TenantScoped()`
|
|
67
|
+
* config, but the standalone `@TenantScoped()` decorator (smrt-tenancy) records
|
|
68
|
+
* its config only in the tenancy registry at decoration time — invisible to core
|
|
69
|
+
* until/unless a manifest carries it. Tenancy fills this slot at
|
|
70
|
+
* `enableTenancy()` so core-side fail-closed guards (the generated REST read
|
|
71
|
+
* scope) recognize tenant-scoped classes regardless of registration form or
|
|
72
|
+
* manifest timing. Mirrors {@link setDispatchTenantResolver}.
|
|
73
|
+
*
|
|
74
|
+
* @param resolver - Predicate returning `true` for tenant-scoped class names, or
|
|
75
|
+
* `undefined` to clear (which `disableTenancy()` does).
|
|
76
|
+
*/
|
|
77
|
+
function setTenantScopedClassResolver(resolver) {
|
|
78
|
+
globalThis.__smrtTenantScopedClassResolver = resolver;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether the tenancy layer reports `className` as tenant-scoped. Returns
|
|
82
|
+
* `false` when tenancy is disabled (no resolver) or the resolver throws.
|
|
83
|
+
*
|
|
84
|
+
* @param className - Class name (simple or qualified) to check.
|
|
85
|
+
*/
|
|
86
|
+
function isTenantScopedClassResolved(className) {
|
|
87
|
+
const resolver = globalThis.__smrtTenantScopedClassResolver;
|
|
88
|
+
if (!resolver) return false;
|
|
89
|
+
try {
|
|
90
|
+
return resolver(className) === true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
63
95
|
//#endregion
|
|
64
|
-
export { resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver };
|
|
96
|
+
export { isTenantScopedClassResolved, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, setTenantScopedClassResolver };
|
|
65
97
|
|
|
66
98
|
//# sourceMappingURL=tenant-resolver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-resolver.js","names":[],"sources":["../../src/dispatch/tenant-resolver.ts"],"sourcesContent":["/**\n * DispatchBus tenant resolver — dependency-inversion hook\n *\n * `@happyvertical/smrt-core` cannot depend on `@happyvertical/smrt-tenancy`\n * (tenancy depends on core, not the other way around). To let the DispatchBus\n * stamp and filter dispatches by the active tenant without creating a circular\n * dependency, core exposes an injectable resolver slot that tenancy fills at\n * `enableTenancy()` time — the same inversion pattern used by\n * {@link GlobalInterceptors}.\n *\n * When tenancy is not enabled (non-tenant deployments, existing tests), the\n * resolver defaults to a no-op that returns `undefined`, so the DispatchBus\n * behaves exactly as before: no tenant column is stamped and no tenant filter\n * is applied.\n *\n * Stored on `globalThis` so all module instances share one resolver, which is\n * critical in the monorepo where the same package can be loaded from multiple\n * paths (mirrors {@link ObjectRegistry} / {@link GlobalInterceptors}).\n */\n\n/**\n * Resolver function that returns the active tenant id for the current async\n * execution scope, or `undefined`/`null` when there is no tenant context\n * (system/global scope).\n */\nexport type DispatchTenantResolver = () => string | null | undefined;\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtDispatchTenantResolver: DispatchTenantResolver | undefined;\n}\n\n/**\n * Register the tenant resolver the DispatchBus uses to derive the active\n * tenant id on emit/subscribe/process.\n *\n * Called by `@happyvertical/smrt-tenancy`'s `enableTenancy()`; application\n * code never needs to call this directly. Passing `undefined` clears the\n * resolver (restoring the no-op default), which `disableTenancy()` does.\n *\n * @param resolver - Function returning the active tenant id, or `undefined` to\n * clear and fall back to the no-op default.\n */\nexport function setDispatchTenantResolver(\n resolver: DispatchTenantResolver | undefined,\n): void {\n globalThis.__smrtDispatchTenantResolver = resolver;\n}\n\n/**\n * Resolved tenant scope for a DispatchBus operation.\n *\n * The DispatchBus must distinguish three states, because they have different\n * read/write semantics (S5 #1398):\n *\n * - **Tenancy disabled** (`enforced: false`): no resolver is registered. The bus\n * applies no tenant filter and stamps no tenant id — identical to pre-tenancy\n * behavior. This is the backward-compatibility path for non-tenant\n * deployments and existing tests.\n * - **Tenancy enabled, active tenant** (`enforced: true`, `tenantId: T`): reads\n * are restricted to `(tenant_id = T OR tenant_id IS NULL)` and emits stamp\n * `tenant_id = T`.\n * - **Tenancy enabled, no active tenant** (`enforced: true`, `tenantId: null`):\n * reads are restricted to `tenant_id IS NULL` only (global rows). This is a\n * *fail-closed* state — when tenancy is on but no tenant context is active\n * (e.g. processing outside `withTenant()`), reads MUST NOT leak other\n * tenants' rows. Emits stamp `tenant_id = NULL` (global).\n *\n * Critically, `enforced: true` + `tenantId: null` is NOT collapsed into the\n * disabled state: when tenancy is enabled, a missing tenant context restricts\n * reads to global rows rather than opening up all tenants.\n */\nexport interface DispatchTenantScope {\n /**\n * Whether tenant enforcement is active (a resolver is registered). When\n * `false`, the bus applies no tenant filter at all (pre-tenancy behavior).\n */\n enforced: boolean;\n /**\n * The active tenant id, or `null` when there is no active tenant context.\n * Only meaningful when `enforced` is `true`.\n */\n tenantId: string | null;\n}\n\n/**\n * Resolve the active tenant scope for the current async execution scope.\n *\n * This is the trust anchor for DispatchBus tenant isolation. It distinguishes\n * \"tenancy disabled\" (no resolver registered → no filtering) from \"tenancy\n * enabled but no active tenant\" (resolver registered, returns null/undefined →\n * fail-closed to global-only reads). See {@link DispatchTenantScope}.\n *\n * @returns The resolved tenant scope.\n */\nexport function resolveDispatchTenantScope(): DispatchTenantScope {\n const resolver = globalThis.__smrtDispatchTenantResolver;\n if (!resolver) {\n // No resolver registered → tenancy is off. No filtering, no stamping.\n return { enforced: false, tenantId: null };\n }\n try {\n const resolved = resolver();\n return { enforced: true, tenantId: resolved ?? null };\n } catch {\n // A misbehaving resolver must never break dispatch processing. Tenancy is\n // still considered enforced (a resolver IS registered), so fail closed to\n // global-only reads rather than leaking all tenants.\n return { enforced: true, tenantId: null };\n }\n}\n\n/**\n * Resolve only the active tenant id for the current async execution scope.\n *\n * Returns `undefined` when no resolver is registered (tenancy disabled) or when\n * the resolver reports no active tenant. Prefer {@link resolveDispatchTenantScope}\n * for read-filtering decisions, which need to distinguish those two cases.\n *\n * @returns The active tenant id, or `undefined`/`null` when there is no tenant\n * scope.\n */\nexport function resolveDispatchTenantId(): string | null | undefined {\n const resolver = globalThis.__smrtDispatchTenantResolver;\n if (!resolver) {\n return undefined;\n }\n try {\n return resolver();\n } catch {\n // A misbehaving resolver must never break dispatch emission; treat a\n // throwing resolver as \"no tenant context\".\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"tenant-resolver.js","names":[],"sources":["../../src/dispatch/tenant-resolver.ts"],"sourcesContent":["/**\n * DispatchBus tenant resolver — dependency-inversion hook\n *\n * `@happyvertical/smrt-core` cannot depend on `@happyvertical/smrt-tenancy`\n * (tenancy depends on core, not the other way around). To let the DispatchBus\n * stamp and filter dispatches by the active tenant without creating a circular\n * dependency, core exposes an injectable resolver slot that tenancy fills at\n * `enableTenancy()` time — the same inversion pattern used by\n * {@link GlobalInterceptors}.\n *\n * When tenancy is not enabled (non-tenant deployments, existing tests), the\n * resolver defaults to a no-op that returns `undefined`, so the DispatchBus\n * behaves exactly as before: no tenant column is stamped and no tenant filter\n * is applied.\n *\n * Stored on `globalThis` so all module instances share one resolver, which is\n * critical in the monorepo where the same package can be loaded from multiple\n * paths (mirrors {@link ObjectRegistry} / {@link GlobalInterceptors}).\n */\n\n/**\n * Resolver function that returns the active tenant id for the current async\n * execution scope, or `undefined`/`null` when there is no tenant context\n * (system/global scope).\n */\nexport type DispatchTenantResolver = () => string | null | undefined;\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtDispatchTenantResolver: DispatchTenantResolver | undefined;\n // eslint-disable-next-line no-var\n var __smrtTenantScopedClassResolver:\n | ((className: string) => boolean)\n | undefined;\n}\n\n/**\n * Register the tenant resolver the DispatchBus uses to derive the active\n * tenant id on emit/subscribe/process.\n *\n * Called by `@happyvertical/smrt-tenancy`'s `enableTenancy()`; application\n * code never needs to call this directly. Passing `undefined` clears the\n * resolver (restoring the no-op default), which `disableTenancy()` does.\n *\n * @param resolver - Function returning the active tenant id, or `undefined` to\n * clear and fall back to the no-op default.\n */\nexport function setDispatchTenantResolver(\n resolver: DispatchTenantResolver | undefined,\n): void {\n globalThis.__smrtDispatchTenantResolver = resolver;\n}\n\n/**\n * Resolved tenant scope for a DispatchBus operation.\n *\n * The DispatchBus must distinguish three states, because they have different\n * read/write semantics (S5 #1398):\n *\n * - **Tenancy disabled** (`enforced: false`): no resolver is registered. The bus\n * applies no tenant filter and stamps no tenant id — identical to pre-tenancy\n * behavior. This is the backward-compatibility path for non-tenant\n * deployments and existing tests.\n * - **Tenancy enabled, active tenant** (`enforced: true`, `tenantId: T`): reads\n * are restricted to `(tenant_id = T OR tenant_id IS NULL)` and emits stamp\n * `tenant_id = T`.\n * - **Tenancy enabled, no active tenant** (`enforced: true`, `tenantId: null`):\n * reads are restricted to `tenant_id IS NULL` only (global rows). This is a\n * *fail-closed* state — when tenancy is on but no tenant context is active\n * (e.g. processing outside `withTenant()`), reads MUST NOT leak other\n * tenants' rows. Emits stamp `tenant_id = NULL` (global).\n *\n * Critically, `enforced: true` + `tenantId: null` is NOT collapsed into the\n * disabled state: when tenancy is enabled, a missing tenant context restricts\n * reads to global rows rather than opening up all tenants.\n */\nexport interface DispatchTenantScope {\n /**\n * Whether tenant enforcement is active (a resolver is registered). When\n * `false`, the bus applies no tenant filter at all (pre-tenancy behavior).\n */\n enforced: boolean;\n /**\n * The active tenant id, or `null` when there is no active tenant context.\n * Only meaningful when `enforced` is `true`.\n */\n tenantId: string | null;\n}\n\n/**\n * Resolve the active tenant scope for the current async execution scope.\n *\n * This is the trust anchor for DispatchBus tenant isolation. It distinguishes\n * \"tenancy disabled\" (no resolver registered → no filtering) from \"tenancy\n * enabled but no active tenant\" (resolver registered, returns null/undefined →\n * fail-closed to global-only reads). See {@link DispatchTenantScope}.\n *\n * @returns The resolved tenant scope.\n */\nexport function resolveDispatchTenantScope(): DispatchTenantScope {\n const resolver = globalThis.__smrtDispatchTenantResolver;\n if (!resolver) {\n // No resolver registered → tenancy is off. No filtering, no stamping.\n return { enforced: false, tenantId: null };\n }\n try {\n const resolved = resolver();\n return { enforced: true, tenantId: resolved ?? null };\n } catch {\n // A misbehaving resolver must never break dispatch processing. Tenancy is\n // still considered enforced (a resolver IS registered), so fail closed to\n // global-only reads rather than leaking all tenants.\n return { enforced: true, tenantId: null };\n }\n}\n\n/**\n * Resolve only the active tenant id for the current async execution scope.\n *\n * Returns `undefined` when no resolver is registered (tenancy disabled) or when\n * the resolver reports no active tenant. Prefer {@link resolveDispatchTenantScope}\n * for read-filtering decisions, which need to distinguish those two cases.\n *\n * @returns The active tenant id, or `undefined`/`null` when there is no tenant\n * scope.\n */\nexport function resolveDispatchTenantId(): string | null | undefined {\n const resolver = globalThis.__smrtDispatchTenantResolver;\n if (!resolver) {\n return undefined;\n }\n try {\n return resolver();\n } catch {\n // A misbehaving resolver must never break dispatch emission; treat a\n // throwing resolver as \"no tenant context\".\n return undefined;\n }\n}\n\n/**\n * Register the resolver that reports whether a class is tenant-scoped, covering\n * BOTH registration forms (S #1782). Core's `ObjectRegistry.isTenantScoped`\n * recognizes `@smrt({ tenantScoped })` and the manifest-merged `@TenantScoped()`\n * config, but the standalone `@TenantScoped()` decorator (smrt-tenancy) records\n * its config only in the tenancy registry at decoration time — invisible to core\n * until/unless a manifest carries it. Tenancy fills this slot at\n * `enableTenancy()` so core-side fail-closed guards (the generated REST read\n * scope) recognize tenant-scoped classes regardless of registration form or\n * manifest timing. Mirrors {@link setDispatchTenantResolver}.\n *\n * @param resolver - Predicate returning `true` for tenant-scoped class names, or\n * `undefined` to clear (which `disableTenancy()` does).\n */\nexport function setTenantScopedClassResolver(\n resolver: ((className: string) => boolean) | undefined,\n): void {\n globalThis.__smrtTenantScopedClassResolver = resolver;\n}\n\n/**\n * Whether the tenancy layer reports `className` as tenant-scoped. Returns\n * `false` when tenancy is disabled (no resolver) or the resolver throws.\n *\n * @param className - Class name (simple or qualified) to check.\n */\nexport function isTenantScopedClassResolved(className: string): boolean {\n const resolver = globalThis.__smrtTenantScopedClassResolver;\n if (!resolver) {\n return false;\n }\n try {\n return resolver(className) === true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;AA+CA,SAAgB,0BACd,UACM;CACN,WAAW,+BAA+B;AAC5C;;;;;;;;;;;AAgDA,SAAgB,6BAAkD;CAChE,MAAM,WAAW,WAAW;CAC5B,IAAI,CAAC,UAEH,OAAO;EAAE,UAAU;EAAO,UAAU;CAAK;CAE3C,IAAI;EAEF,OAAO;GAAE,UAAU;GAAM,UADR,SACkB,KAAY;EAAK;CACtD,QAAQ;EAIN,OAAO;GAAE,UAAU;GAAM,UAAU;EAAK;CAC1C;AACF;;;;;;;;;;;AAYA,SAAgB,0BAAqD;CACnE,MAAM,WAAW,WAAW;CAC5B,IAAI,CAAC,UACH;CAEF,IAAI;EACF,OAAO,SAAS;CAClB,QAAQ;EAGN;CACF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,6BACd,UACM;CACN,WAAW,kCAAkC;AAC/C;;;;;;;AAQA,SAAgB,4BAA4B,WAA4B;CACtE,MAAM,WAAW,WAAW;CAC5B,IAAI,CAAC,UACH,OAAO;CAET,IAAI;EACF,OAAO,SAAS,SAAS,MAAM;CACjC,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional GET v1 for generated read routes (#1757).
|
|
3
|
+
*
|
|
4
|
+
* Generated `list`/`get` responses carry a strong ETag computed from the
|
|
5
|
+
* serialized JSON body, and a matching `If-None-Match` answers
|
|
6
|
+
* `304 Not Modified` with an empty body. v1 deliberately still runs the query
|
|
7
|
+
* — the win is transfer, parse, and re-render, not the database round trip
|
|
8
|
+
* (a later slice upgrades the ETag source to the change-feed table version).
|
|
9
|
+
*
|
|
10
|
+
* Cache-Control policy (fail-private, mirroring the #1540 posture):
|
|
11
|
+
* - Default reads: `private, no-cache` — responses may be stored by the
|
|
12
|
+
* browser but MUST be revalidated before reuse, and shared caches never
|
|
13
|
+
* store them.
|
|
14
|
+
* - `@smrt({ api: { public: true | 'read', cache: { sMaxage } } })` reads:
|
|
15
|
+
* `public, max-age=0, s-maxage=<n>` — CDNs/shared caches may serve the
|
|
16
|
+
* response for `n` seconds while browsers still revalidate (cheap 304s).
|
|
17
|
+
* Models without the public flag NEVER emit shared-cache headers, even when
|
|
18
|
+
* `cache.sMaxage` is configured.
|
|
19
|
+
* - Tenant-scoped models (`@smrt({ tenantScoped })` / `@TenantScoped()`, any
|
|
20
|
+
* mode) NEVER emit shared-cache headers: their bodies vary with the tenant
|
|
21
|
+
* context, which URL-keyed shared caches cannot see. `sMaxage` is ignored
|
|
22
|
+
* with a one-time warning.
|
|
23
|
+
*
|
|
24
|
+
* Consumed by both the runtime REST generator (`./rest.ts`) and — as an
|
|
25
|
+
* emitted code snippet — the SvelteKit route generator
|
|
26
|
+
* (`../vite-plugin/sveltekit-generator.ts`). Keeping every piece here keeps
|
|
27
|
+
* the two generators' diffs minimal and the policy in one place.
|
|
28
|
+
*/
|
|
29
|
+
/** Default Cache-Control for generated reads: private conditional revalidation. */
|
|
30
|
+
export declare const PRIVATE_READ_CACHE_CONTROL = "private, no-cache";
|
|
31
|
+
/**
|
|
32
|
+
* Compute the strong ETag for a serialized response body.
|
|
33
|
+
*
|
|
34
|
+
* SHA-256 of the exact JSON text, base64url-encoded and quoted per RFC 9110.
|
|
35
|
+
* Deterministic for a given body, so any change to the underlying data (which
|
|
36
|
+
* changes the serialized JSON) changes the ETag.
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeBodyEtag(body: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether an `If-None-Match` request header matches the response ETag.
|
|
41
|
+
*
|
|
42
|
+
* Implements RFC 9110 §13.1.2 weak comparison: `*` matches anything, the
|
|
43
|
+
* header may carry a comma-separated list, and a `W/` prefix is ignored.
|
|
44
|
+
*/
|
|
45
|
+
export declare function ifNoneMatchSatisfied(header: string | null | undefined, etag: string): boolean;
|
|
46
|
+
/** Model-level context that constrains the cache policy beyond `api` config. */
|
|
47
|
+
export interface ReadCacheControlOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Whether the model is tenant-scoped (`@smrt({ tenantScoped })` or the
|
|
50
|
+
* `@TenantScoped()` decorator, ANY mode including `'optional'`). Tenant
|
|
51
|
+
* scoping keys the response body on request identity (session cookie), which
|
|
52
|
+
* shared caches cannot see — they key on the URL alone — so honoring
|
|
53
|
+
* `sMaxage` would serve one tenant's rows to other tenants or to anonymous
|
|
54
|
+
* visitors. Fail-closed: tenant-scoped models NEVER emit shared-cache
|
|
55
|
+
* headers (#1757 review finding).
|
|
56
|
+
*/
|
|
57
|
+
tenantScoped?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the Cache-Control header for a generated read response from a
|
|
61
|
+
* model's `@smrt({ api })` config (defensively typed — the config arrives as
|
|
62
|
+
* `unknown` from the registry at runtime and from the manifest at build time).
|
|
63
|
+
*
|
|
64
|
+
* Only models that opted out of auth via `public: true` (or `'read'`, which
|
|
65
|
+
* makes reads public) may emit shared-cache headers, and only when they also
|
|
66
|
+
* configure a positive `cache.sMaxage`. Everything else — including a
|
|
67
|
+
* non-public model that configures `sMaxage` — stays `private, no-cache`.
|
|
68
|
+
*
|
|
69
|
+
* Tenant-scoped models are ALWAYS `private, no-cache` regardless of config:
|
|
70
|
+
* their response bodies vary with the tenant context (resolved from session
|
|
71
|
+
* cookies, invisible to URL-keyed shared caches), so shared caching would
|
|
72
|
+
* leak one tenant's rows to other tenants or anonymous visitors.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveReadCacheControl(apiConfig: unknown, options?: ReadCacheControlOptions): string;
|
|
75
|
+
/**
|
|
76
|
+
* Warn (once per model) when a tenant-scoped model is also marked publicly
|
|
77
|
+
* readable (`@smrt({ api: { public: true | 'read' } })`).
|
|
78
|
+
*
|
|
79
|
+
* Anonymous / no-tenant-context reads on such a model fail closed to NULL-tenant
|
|
80
|
+
* (global) rows only (#1782): they never expose any tenant's rows. That is the
|
|
81
|
+
* intended, safe behavior, but silently it reads as "the public endpoint returns
|
|
82
|
+
* nothing" — so surface the combination and its consequence at generation /
|
|
83
|
+
* serve time. Called from both the REST runtime and the SvelteKit route
|
|
84
|
+
* generator so the message appears wherever the model is exposed.
|
|
85
|
+
*/
|
|
86
|
+
export declare function warnIfTenantScopedPublicRead(modelName: string, apiConfig: unknown, tenantScoped: boolean): void;
|
|
87
|
+
/**
|
|
88
|
+
* Warn (once per model) when a tenant-scoped model configures
|
|
89
|
+
* `api.cache.sMaxage`: the knob is deliberately neutralized to private
|
|
90
|
+
* caching, and silently ignoring it would leave developers wondering why no
|
|
91
|
+
* CDN caching happens. Called from both the REST runtime and the SvelteKit
|
|
92
|
+
* route generator so the message surfaces wherever the model is served.
|
|
93
|
+
*/
|
|
94
|
+
export declare function warnIfSharedCacheNeutralized(modelName: string, apiConfig: unknown, tenantScoped: boolean): void;
|
|
95
|
+
/**
|
|
96
|
+
* Build the JSON response for a generated read, honoring `If-None-Match`.
|
|
97
|
+
*
|
|
98
|
+
* Returns `304 Not Modified` with an EMPTY body when the request's
|
|
99
|
+
* `If-None-Match` matches the body ETag; otherwise a 200 with the serialized
|
|
100
|
+
* payload. Both carry the ETag and the resolved Cache-Control so clients can
|
|
101
|
+
* revalidate the representation they hold.
|
|
102
|
+
*/
|
|
103
|
+
export declare function conditionalJsonResponse(request: Request, payload: unknown, cacheControl: string): Response;
|
|
104
|
+
/** Generation-time context for the emitted SvelteKit route helper. */
|
|
105
|
+
export interface ConditionalGetRouteHelperOptions extends ReadCacheControlOptions {
|
|
106
|
+
/** Model name used for the one-time sMaxage-neutralized warning. */
|
|
107
|
+
modelName?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Emit the conditional-GET helper inlined into generated SvelteKit route
|
|
111
|
+
* files, following the generator's existing inline-helper convention
|
|
112
|
+
* (auth guard, tenant context, writable policy). The Cache-Control policy is
|
|
113
|
+
* resolved at generation time from the object's `@smrt({ api })` config plus
|
|
114
|
+
* the model's tenant scoping, and baked in as a constant.
|
|
115
|
+
*
|
|
116
|
+
* Kept textually in lockstep with the runtime helpers above — the `.spec`
|
|
117
|
+
* suite drives both through the same HTTP semantics.
|
|
118
|
+
*/
|
|
119
|
+
export declare function generateConditionalGetRouteHelper(apiConfig: unknown, options?: ConditionalGetRouteHelperOptions): string;
|
|
120
|
+
//# sourceMappingURL=conditional-get.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditional-get.d.ts","sourceRoot":"","sources":["../../src/generators/conditional-get.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,mFAAmF;AACnF,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAE9D;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,IAAI,EAAE,MAAM,GACX,OAAO,CAQT;AAOD,gFAAgF;AAChF,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA8BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,OAAO,GAAE,uBAA4B,GACpC,MAAM,CAMR;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,YAAY,EAAE,OAAO,GACpB,IAAI,CAYN;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,YAAY,EAAE,OAAO,GACpB,IAAI,CAUN;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,GACnB,QAAQ,CAsBV;AAED,sEAAsE;AACtE,MAAM,WAAW,gCACf,SAAQ,uBAAuB;IAC/B,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,OAAO,EAClB,OAAO,GAAE,gCAAqC,GAC7C,MAAM,CA2DR"}
|