@happyvertical/smrt-core 0.37.7 → 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.
Files changed (41) hide show
  1. package/AGENTS.md +15 -6
  2. package/dist/dispatch/index.d.ts +1 -1
  3. package/dist/dispatch/index.d.ts.map +1 -1
  4. package/dist/dispatch/index.js +2 -2
  5. package/dist/dispatch/tenant-resolver.d.ts +23 -0
  6. package/dist/dispatch/tenant-resolver.d.ts.map +1 -1
  7. package/dist/dispatch/tenant-resolver.js +33 -1
  8. package/dist/dispatch/tenant-resolver.js.map +1 -1
  9. package/dist/generators/conditional-get.d.ts +12 -0
  10. package/dist/generators/conditional-get.d.ts.map +1 -1
  11. package/dist/generators/conditional-get.js +30 -2
  12. package/dist/generators/conditional-get.js.map +1 -1
  13. package/dist/generators/rest.d.ts +30 -0
  14. package/dist/generators/rest.d.ts.map +1 -1
  15. package/dist/generators/rest.js +62 -7
  16. package/dist/generators/rest.js.map +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/manifest/manifest-loader.d.ts +10 -1
  19. package/dist/manifest/manifest-loader.d.ts.map +1 -1
  20. package/dist/manifest/manifest-loader.js +18 -5
  21. package/dist/manifest/manifest-loader.js.map +1 -1
  22. package/dist/manifest/static-manifest.js +2 -2
  23. package/dist/manifest/static-manifest.js.map +1 -1
  24. package/dist/manifest/store.js +1 -1
  25. package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
  26. package/dist/manifest/test-manifest-stub.js +277 -2
  27. package/dist/manifest/test-manifest-stub.js.map +1 -1
  28. package/dist/manifest.json +2 -2
  29. package/dist/registry/shared-state.d.ts +8 -1
  30. package/dist/registry/shared-state.d.ts.map +1 -1
  31. package/dist/registry/shared-state.js +11 -3
  32. package/dist/registry/shared-state.js.map +1 -1
  33. package/dist/smrt-knowledge.json +6 -6
  34. package/dist/utils/stack-frames.d.ts +50 -0
  35. package/dist/utils/stack-frames.d.ts.map +1 -0
  36. package/dist/utils/stack-frames.js +64 -0
  37. package/dist/utils/stack-frames.js.map +1 -0
  38. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  39. package/dist/vite-plugin/sveltekit-generator.js +41 -17
  40. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  41. package/package.json +4 -4
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1783048762765,
3
+ "timestamp": 1783059743436,
4
4
  "packageName": "@happyvertical/smrt-core",
5
- "packageVersion": "0.37.7",
5
+ "packageVersion": "0.37.8",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-core:SmrtClass": {
8
8
  "name": "smrtclass",
@@ -43,9 +43,16 @@ export declare function getInheritanceConfig(): {
43
43
  * This allows the same class to be re-registered when modules are re-evaluated
44
44
  * (e.g., during vitest test file collection with isolation enabled).
45
45
  *
46
+ * Frames from decorator-lowering runtime helpers (`@oxc-project/runtime` under
47
+ * Vite 8, `tslib`/`@swc/helpers`/`@babel/runtime` elsewhere) are skipped too, so
48
+ * the lowered `@smrt()` application resolves to the consumer module that declares
49
+ * the class rather than the helper frame the compiler inserted (#1785).
50
+ *
51
+ * @param stackOverride - Optional stack string to parse instead of a fresh
52
+ * `Error().stack`. Used by tests to simulate a lowered call pattern.
46
53
  * @returns The file path where the @smrt() decorator was called, or undefined
47
54
  */
48
- export declare function getSourceFileFromStack(): string | undefined;
55
+ export declare function getSourceFileFromStack(stackOverride?: string): string | undefined;
49
56
  export declare function getClasses(): Map<string, RegisteredClass>;
50
57
  export declare function getCollections(): Map<string, typeof SmrtCollection>;
51
58
  export declare function getCollectionTableNames(): Map<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"shared-state.d.ts","sourceRoot":"","sources":["../../src/registry/shared-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shared-state.d.ts","sourceRoot":"","sources":["../../src/registry/shared-state.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { getSmrtModuleConfig } from "../config/global-config.js";
2
2
  import { LRUCache } from "../utils/lru-cache.js";
3
+ import { isDecoratorRuntimeFramePath } from "../utils/stack-frames.js";
3
4
  import { createLogger } from "@happyvertical/logger";
4
5
  //#region src/registry/shared-state.ts
5
6
  /**
@@ -49,16 +50,23 @@ function getInheritanceConfig() {
49
50
  * This allows the same class to be re-registered when modules are re-evaluated
50
51
  * (e.g., during vitest test file collection with isolation enabled).
51
52
  *
53
+ * Frames from decorator-lowering runtime helpers (`@oxc-project/runtime` under
54
+ * Vite 8, `tslib`/`@swc/helpers`/`@babel/runtime` elsewhere) are skipped too, so
55
+ * the lowered `@smrt()` application resolves to the consumer module that declares
56
+ * the class rather than the helper frame the compiler inserted (#1785).
57
+ *
58
+ * @param stackOverride - Optional stack string to parse instead of a fresh
59
+ * `Error().stack`. Used by tests to simulate a lowered call pattern.
52
60
  * @returns The file path where the @smrt() decorator was called, or undefined
53
61
  */
54
- function getSourceFileFromStack() {
55
- const stackLines = ((/* @__PURE__ */ new Error()).stack || "").split("\n");
62
+ function getSourceFileFromStack(stackOverride) {
63
+ const stackLines = (stackOverride ?? (/* @__PURE__ */ new Error()).stack ?? "").split("\n");
56
64
  for (const line of stackLines) {
57
65
  const fileMatch = line.match(/(?:file:\/\/)?([^)\s]+\.(?:js|ts|mjs|mts|jsx|tsx|cjs|cts))(?::\d+:\d+)?/);
58
66
  if (fileMatch) {
59
67
  const normalizedPath = fileMatch[1].replace(/^file:\/+/, "").replace(/\\/g, "/");
60
68
  const lowerPath = normalizedPath.toLowerCase();
61
- if (lowerPath.includes("/node_modules/@happyvertical/smrt-core/") || lowerPath.includes("/packages/core/src/") && !lowerPath.includes("__tests__") || lowerPath.includes("/manifest/manifest-loader")) continue;
69
+ if (lowerPath.includes("/node_modules/@happyvertical/smrt-core/") || lowerPath.includes("/packages/core/src/") && !lowerPath.includes("__tests__") || lowerPath.includes("/manifest/manifest-loader") || isDecoratorRuntimeFramePath(normalizedPath)) continue;
62
70
  return normalizedPath;
63
71
  }
64
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shared-state.js","names":[],"sources":["../../src/registry/shared-state.ts"],"sourcesContent":["/**\n * Shared state accessors for the SMRT ObjectRegistry.\n *\n * All registry sub-modules share state via globalThis to ensure\n * cross-module consistency in monorepo environments.\n *\n * Extracted from registry.ts as part of issue #1006.\n * @see https://github.com/happyvertical/smrt/issues/1006\n * @see https://github.com/happyvertical/smrt/issues/543\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { SmrtCollection } from '../collection';\nimport {\n getSmrtModuleConfig,\n type SmrtGlobalConfig,\n} from '../config/global-config.js';\nimport type { SmrtObject } from '../object';\nimport { LRUCache } from '../utils/lru-cache';\nimport type { RegisteredClass, SmrtObjectConstructor } from './types';\n\n// Re-export the globalThis augmentation so it's visible everywhere\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtRegistryClasses: Map<string, RegisteredClass> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryCollections: Map<string, typeof SmrtCollection> | undefined;\n // eslint-disable-next-line no-var\n // Heterogeneous cache: stores collections of many different item types keyed\n // by class name. `SmrtCollection` is invariant in its item type (it has\n // `create(options: SmrtCreateInput<ModelType>)`), so this cannot be narrowed\n // to `SmrtCollection<SmrtObject>` — the type argument stays `any` (read sites\n // cast back to the concrete collection type).\n var __smrtRegistryCollectionCache:\n // biome-ignore lint/suspicious/noExplicitAny: heterogeneous cache keyed by class name; `SmrtCollection` is invariant in `ModelType` so the type arg cannot narrow to `SmrtObject` (read sites cast back). S4 #1579.\n | LRUCache<string, SmrtCollection<any>>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryDbInstanceIds: WeakMap<object, number> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryNextDbId: number | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryInheritanceChainCache:\n | LRUCache<string, string[]>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryFieldDecorators:\n | Map<string, Map<string, Record<string, unknown>>>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryStiSiblingsLoaded: Set<string> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryCollectionTableNames: Map<string, string> | undefined;\n // Release B (#1133) removed __smrtRegistryClassNameMap — case-insensitive\n // lookups now iterate the `classes` Map directly. The globalThis slot is\n // left undeclared so any leftover code that reaches for it gets a clean\n // TS error instead of silently stale data.\n // eslint-disable-next-line no-var\n var __smrtRegistrySchemasInitialized: WeakSet<object> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryConstructorIndex:\n | WeakMap<SmrtObjectConstructor, string>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryDiscoveryAttemptCache: Map<string, boolean> | undefined;\n}\n\n/**\n * Cached verbose flag evaluated once at module load time.\n * Checks SMRT_VERBOSE=true or DEBUG containing 'smrt'.\n * Avoids repeated env var parsing on every log statement.\n * @see https://github.com/happyvertical/smrt/issues/782\n */\nexport const VERBOSE_ENABLED =\n process.env.SMRT_VERBOSE === 'true' ||\n (process.env.DEBUG?.includes('smrt') ?? false);\n\n// verboseLog() is gated by VERBOSE_ENABLED, so the level must allow debug when\n// it's set (a fixed 'info' would filter the trace out and break SMRT_VERBOSE).\nconst logger = createLogger({ level: VERBOSE_ENABLED ? 'debug' : 'info' });\n\n/**\n * Log a message only when verbose mode is enabled.\n * Used throughout registry modules to reduce noise during normal operation.\n * @param args - Arguments to pass to console.log\n */\nexport function verboseLog(...args: unknown[]): void {\n if (VERBOSE_ENABLED) {\n const [message, ...rest] = args;\n logger.debug(\n typeof message === 'string' ? message : String(message),\n rest.length > 0 ? { args: rest } : undefined,\n );\n }\n}\n\n/**\n * Get inheritance config synchronously.\n * Uses the config package's sync accessor which returns already-loaded config.\n */\nexport function getInheritanceConfig() {\n const config = getSmrtModuleConfig<SmrtGlobalConfig>('smrt', {});\n return {\n cacheSize: config.inheritance?.cacheSize ?? 200,\n onMissingAncestor:\n config.inheritance?.onMissingAncestor ?? ('warn' as 'warn' | 'error'),\n };\n}\n\n/**\n * Extract the source file path from the call stack.\n *\n * Used to identify where a class was defined for collision detection.\n * This allows the same class to be re-registered when modules are re-evaluated\n * (e.g., during vitest test file collection with isolation enabled).\n *\n * @returns The file path where the @smrt() decorator was called, or undefined\n */\nexport function getSourceFileFromStack(): string | undefined {\n const error = new Error();\n const stack = error.stack || '';\n const stackLines = stack.split('\\n');\n\n // Look for the first file path that's NOT from smrt-core internal files\n // Stack trace format: \" at FunctionName (file:///path/to/file.ts:line:col)\"\n // or \" at file:///path/to/file.ts:line:col\"\n for (const line of stackLines) {\n // Match file paths in stack trace (include all JS/TS file extensions)\n const fileMatch = line.match(\n /(?:file:\\/\\/)?([^)\\s]+\\.(?:js|ts|mjs|mts|jsx|tsx|cjs|cts))(?::\\d+:\\d+)?/,\n );\n if (fileMatch) {\n const rawPath = fileMatch[1];\n // Normalize the path:\n // - remove any leading file:// or file:/// prefix\n // - convert Windows backslashes to POSIX-style forward slashes\n const normalizedPath = rawPath\n .replace(/^file:\\/+/, '')\n .replace(/\\\\/g, '/');\n const lowerPath = normalizedPath.toLowerCase();\n\n // Skip smrt-core internal files using specific path patterns\n // to avoid false positives with user files containing \"registry\" in name\n if (\n // Installed package form: node_modules/@happyvertical/smrt-core/...\n lowerPath.includes('/node_modules/@happyvertical/smrt-core/') ||\n // Monorepo/workspace form: packages/core/src/...\n (lowerPath.includes('/packages/core/src/') &&\n !lowerPath.includes('__tests__')) ||\n // Specific manifest loader internals\n lowerPath.includes('/manifest/manifest-loader')\n ) {\n continue;\n }\n return normalizedPath;\n }\n }\n return undefined;\n}\n\n// ── Shared state accessor functions ──────────────────────────────────────\n\nexport function getClasses(): Map<string, RegisteredClass> {\n if (!globalThis.__smrtRegistryClasses) {\n globalThis.__smrtRegistryClasses = new Map<string, RegisteredClass>();\n }\n return globalThis.__smrtRegistryClasses;\n}\n\nexport function getCollections(): Map<string, typeof SmrtCollection> {\n if (!globalThis.__smrtRegistryCollections) {\n globalThis.__smrtRegistryCollections = new Map<\n string,\n typeof SmrtCollection\n >();\n }\n return globalThis.__smrtRegistryCollections;\n}\n\nexport function getCollectionTableNames(): Map<string, string> {\n if (!globalThis.__smrtRegistryCollectionTableNames) {\n globalThis.__smrtRegistryCollectionTableNames = new Map<string, string>();\n }\n return globalThis.__smrtRegistryCollectionTableNames;\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: heterogeneous cache; `SmrtCollection` is invariant in `ModelType` so the type arg cannot narrow to `SmrtObject` (read sites cast back). S4 #1579.\nexport function getCollectionCache(): LRUCache<string, SmrtCollection<any>> {\n if (!globalThis.__smrtRegistryCollectionCache) {\n globalThis.__smrtRegistryCollectionCache = new LRUCache<\n string,\n // biome-ignore lint/suspicious/noExplicitAny: same heterogeneous-cache invariance as the return type above. S4 #1579.\n SmrtCollection<any>\n >(100);\n }\n return globalThis.__smrtRegistryCollectionCache;\n}\n\nexport function getDbInstanceIds(): WeakMap<object, number> {\n if (!globalThis.__smrtRegistryDbInstanceIds) {\n globalThis.__smrtRegistryDbInstanceIds = new WeakMap<object, number>();\n }\n return globalThis.__smrtRegistryDbInstanceIds;\n}\n\nexport function getNextDbId(): number {\n if (globalThis.__smrtRegistryNextDbId === undefined) {\n globalThis.__smrtRegistryNextDbId = 1;\n }\n return globalThis.__smrtRegistryNextDbId;\n}\n\nexport function setNextDbId(value: number): void {\n globalThis.__smrtRegistryNextDbId = value;\n}\n\nexport function getFieldDecorators(): Map<\n string,\n Map<string, Record<string, unknown>>\n> {\n if (!globalThis.__smrtRegistryFieldDecorators) {\n globalThis.__smrtRegistryFieldDecorators = new Map<\n string,\n Map<string, Record<string, unknown>>\n >();\n }\n return globalThis.__smrtRegistryFieldDecorators;\n}\n\nexport function getStiSiblingsLoaded(): Set<string> {\n if (!globalThis.__smrtRegistryStiSiblingsLoaded) {\n globalThis.__smrtRegistryStiSiblingsLoaded = new Set<string>();\n }\n return globalThis.__smrtRegistryStiSiblingsLoaded;\n}\n\n// getClassNameMap removed in Release B (#1133). The eagerly-maintained\n// lowercase-simple-name → qualified-key[] index was replaced by on-demand\n// iteration over the `classes` Map in name-resolver.ts. Any remaining\n// consumer should switch to `findClassesByName`, `getCanonicalClassName`,\n// or `findClass`.\n\nexport function getSchemasInitialized(): WeakSet<object> {\n if (!globalThis.__smrtRegistrySchemasInitialized) {\n globalThis.__smrtRegistrySchemasInitialized = new WeakSet<object>();\n }\n return globalThis.__smrtRegistrySchemasInitialized;\n}\n\nexport function getConstructorIndex(): WeakMap<SmrtObjectConstructor, string> {\n if (!globalThis.__smrtRegistryConstructorIndex) {\n globalThis.__smrtRegistryConstructorIndex = new WeakMap<\n SmrtObjectConstructor,\n string\n >();\n }\n return globalThis.__smrtRegistryConstructorIndex;\n}\n\nexport function getInheritanceCache(): LRUCache<string, string[]> {\n if (!globalThis.__smrtRegistryInheritanceChainCache) {\n const config = getInheritanceConfig();\n globalThis.__smrtRegistryInheritanceChainCache = new LRUCache<\n string,\n string[]\n >(config.cacheSize);\n }\n return globalThis.__smrtRegistryInheritanceChainCache;\n}\n\nexport function getDiscoveryAttemptCache(): Map<string, boolean> {\n if (!globalThis.__smrtRegistryDiscoveryAttemptCache) {\n globalThis.__smrtRegistryDiscoveryAttemptCache = new Map<string, boolean>();\n }\n return globalThis.__smrtRegistryDiscoveryAttemptCache;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyEA,IAAa,kBACX,QAAQ,IAAI,iBAAiB,WAC5B,QAAQ,IAAI,OAAO,SAAS,MAAM,KAAK;AAI1C,IAAM,SAAS,aAAa,EAAE,OAAO,kBAAkB,UAAU,OAAO,CAAC;;;;;;AAOzE,SAAgB,WAAW,GAAG,MAAuB;CACnD,IAAI,iBAAiB;EACnB,MAAM,CAAC,SAAS,GAAG,QAAQ;EAC3B,OAAO,MACL,OAAO,YAAY,WAAW,UAAU,OAAO,OAAO,GACtD,KAAK,SAAS,IAAI,EAAE,MAAM,KAAK,IAAI,KAAA,CACrC;CACF;AACF;;;;;AAMA,SAAgB,uBAAuB;CACrC,MAAM,SAAS,oBAAsC,QAAQ,CAAC,CAAC;CAC/D,OAAO;EACL,WAAW,OAAO,aAAa,aAAa;EAC5C,mBACE,OAAO,aAAa,qBAAsB;CAC9C;AACF;;;;;;;;;;AAWA,SAAgB,yBAA6C;CAG3D,MAAM,+BADQ,IADI,MACJ,EAAA,CAAM,SAAS,GAAA,CACJ,MAAM,IAAI;CAKnC,KAAK,MAAM,QAAQ,YAAY;EAE7B,MAAM,YAAY,KAAK,MACrB,yEACF;EACA,IAAI,WAAW;GAKb,MAAM,iBAJU,UAAU,EAIH,CACpB,QAAQ,aAAa,EAAE,CAAC,CACxB,QAAQ,OAAO,GAAG;GACrB,MAAM,YAAY,eAAe,YAAY;GAI7C,IAEE,UAAU,SAAS,yCAAyC,KAE3D,UAAU,SAAS,qBAAqB,KACvC,CAAC,UAAU,SAAS,WAAW,KAEjC,UAAU,SAAS,2BAA2B,GAE9C;GAEF,OAAO;EACT;CACF;AAEF;AAIA,SAAgB,aAA2C;CACzD,IAAI,CAAC,WAAW,uBACd,WAAW,wCAAwB,IAAI,IAA6B;CAEtE,OAAO,WAAW;AACpB;AAEA,SAAgB,iBAAqD;CACnE,IAAI,CAAC,WAAW,2BACd,WAAW,4CAA4B,IAAI,IAGzC;CAEJ,OAAO,WAAW;AACpB;AAEA,SAAgB,0BAA+C;CAC7D,IAAI,CAAC,WAAW,oCACd,WAAW,qDAAqC,IAAI,IAAoB;CAE1E,OAAO,WAAW;AACpB;AAGA,SAAgB,qBAA4D;CAC1E,IAAI,CAAC,WAAW,+BACd,WAAW,gCAAgC,IAAI,SAI7C,GAAG;CAEP,OAAO,WAAW;AACpB;AAEA,SAAgB,mBAA4C;CAC1D,IAAI,CAAC,WAAW,6BACd,WAAW,8CAA8B,IAAI,QAAwB;CAEvE,OAAO,WAAW;AACpB;AAEA,SAAgB,cAAsB;CACpC,IAAI,WAAW,2BAA2B,KAAA,GACxC,WAAW,yBAAyB;CAEtC,OAAO,WAAW;AACpB;AAEA,SAAgB,YAAY,OAAqB;CAC/C,WAAW,yBAAyB;AACtC;AAEA,SAAgB,qBAGd;CACA,IAAI,CAAC,WAAW,+BACd,WAAW,gDAAgC,IAAI,IAG7C;CAEJ,OAAO,WAAW;AACpB;AAEA,SAAgB,uBAAoC;CAClD,IAAI,CAAC,WAAW,iCACd,WAAW,kDAAkC,IAAI,IAAY;CAE/D,OAAO,WAAW;AACpB;AAeA,SAAgB,sBAA8D;CAC5E,IAAI,CAAC,WAAW,gCACd,WAAW,iDAAiC,IAAI,QAG9C;CAEJ,OAAO,WAAW;AACpB;AAEA,SAAgB,sBAAkD;CAChE,IAAI,CAAC,WAAW,qCAAqC;EACnD,MAAM,SAAS,qBAAqB;EACpC,WAAW,sCAAsC,IAAI,SAGnD,OAAO,SAAS;CACpB;CACA,OAAO,WAAW;AACpB;AAEA,SAAgB,2BAAiD;CAC/D,IAAI,CAAC,WAAW,qCACd,WAAW,sDAAsC,IAAI,IAAqB;CAE5E,OAAO,WAAW;AACpB"}
1
+ {"version":3,"file":"shared-state.js","names":[],"sources":["../../src/registry/shared-state.ts"],"sourcesContent":["/**\n * Shared state accessors for the SMRT ObjectRegistry.\n *\n * All registry sub-modules share state via globalThis to ensure\n * cross-module consistency in monorepo environments.\n *\n * Extracted from registry.ts as part of issue #1006.\n * @see https://github.com/happyvertical/smrt/issues/1006\n * @see https://github.com/happyvertical/smrt/issues/543\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { SmrtCollection } from '../collection';\nimport {\n getSmrtModuleConfig,\n type SmrtGlobalConfig,\n} from '../config/global-config.js';\nimport type { SmrtObject } from '../object';\nimport { LRUCache } from '../utils/lru-cache';\nimport { isDecoratorRuntimeFramePath } from '../utils/stack-frames';\nimport type { RegisteredClass, SmrtObjectConstructor } from './types';\n\n// Re-export the globalThis augmentation so it's visible everywhere\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtRegistryClasses: Map<string, RegisteredClass> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryCollections: Map<string, typeof SmrtCollection> | undefined;\n // eslint-disable-next-line no-var\n // Heterogeneous cache: stores collections of many different item types keyed\n // by class name. `SmrtCollection` is invariant in its item type (it has\n // `create(options: SmrtCreateInput<ModelType>)`), so this cannot be narrowed\n // to `SmrtCollection<SmrtObject>` — the type argument stays `any` (read sites\n // cast back to the concrete collection type).\n var __smrtRegistryCollectionCache:\n // biome-ignore lint/suspicious/noExplicitAny: heterogeneous cache keyed by class name; `SmrtCollection` is invariant in `ModelType` so the type arg cannot narrow to `SmrtObject` (read sites cast back). S4 #1579.\n | LRUCache<string, SmrtCollection<any>>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryDbInstanceIds: WeakMap<object, number> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryNextDbId: number | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryInheritanceChainCache:\n | LRUCache<string, string[]>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryFieldDecorators:\n | Map<string, Map<string, Record<string, unknown>>>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryStiSiblingsLoaded: Set<string> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryCollectionTableNames: Map<string, string> | undefined;\n // Release B (#1133) removed __smrtRegistryClassNameMap — case-insensitive\n // lookups now iterate the `classes` Map directly. The globalThis slot is\n // left undeclared so any leftover code that reaches for it gets a clean\n // TS error instead of silently stale data.\n // eslint-disable-next-line no-var\n var __smrtRegistrySchemasInitialized: WeakSet<object> | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryConstructorIndex:\n | WeakMap<SmrtObjectConstructor, string>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtRegistryDiscoveryAttemptCache: Map<string, boolean> | undefined;\n}\n\n/**\n * Cached verbose flag evaluated once at module load time.\n * Checks SMRT_VERBOSE=true or DEBUG containing 'smrt'.\n * Avoids repeated env var parsing on every log statement.\n * @see https://github.com/happyvertical/smrt/issues/782\n */\nexport const VERBOSE_ENABLED =\n process.env.SMRT_VERBOSE === 'true' ||\n (process.env.DEBUG?.includes('smrt') ?? false);\n\n// verboseLog() is gated by VERBOSE_ENABLED, so the level must allow debug when\n// it's set (a fixed 'info' would filter the trace out and break SMRT_VERBOSE).\nconst logger = createLogger({ level: VERBOSE_ENABLED ? 'debug' : 'info' });\n\n/**\n * Log a message only when verbose mode is enabled.\n * Used throughout registry modules to reduce noise during normal operation.\n * @param args - Arguments to pass to console.log\n */\nexport function verboseLog(...args: unknown[]): void {\n if (VERBOSE_ENABLED) {\n const [message, ...rest] = args;\n logger.debug(\n typeof message === 'string' ? message : String(message),\n rest.length > 0 ? { args: rest } : undefined,\n );\n }\n}\n\n/**\n * Get inheritance config synchronously.\n * Uses the config package's sync accessor which returns already-loaded config.\n */\nexport function getInheritanceConfig() {\n const config = getSmrtModuleConfig<SmrtGlobalConfig>('smrt', {});\n return {\n cacheSize: config.inheritance?.cacheSize ?? 200,\n onMissingAncestor:\n config.inheritance?.onMissingAncestor ?? ('warn' as 'warn' | 'error'),\n };\n}\n\n/**\n * Extract the source file path from the call stack.\n *\n * Used to identify where a class was defined for collision detection.\n * This allows the same class to be re-registered when modules are re-evaluated\n * (e.g., during vitest test file collection with isolation enabled).\n *\n * Frames from decorator-lowering runtime helpers (`@oxc-project/runtime` under\n * Vite 8, `tslib`/`@swc/helpers`/`@babel/runtime` elsewhere) are skipped too, so\n * the lowered `@smrt()` application resolves to the consumer module that declares\n * the class rather than the helper frame the compiler inserted (#1785).\n *\n * @param stackOverride - Optional stack string to parse instead of a fresh\n * `Error().stack`. Used by tests to simulate a lowered call pattern.\n * @returns The file path where the @smrt() decorator was called, or undefined\n */\nexport function getSourceFileFromStack(\n stackOverride?: string,\n): string | undefined {\n const stack = stackOverride ?? new Error().stack ?? '';\n const stackLines = stack.split('\\n');\n\n // Look for the first file path that's NOT from smrt-core internal files\n // Stack trace format: \" at FunctionName (file:///path/to/file.ts:line:col)\"\n // or \" at file:///path/to/file.ts:line:col\"\n for (const line of stackLines) {\n // Match file paths in stack trace (include all JS/TS file extensions)\n const fileMatch = line.match(\n /(?:file:\\/\\/)?([^)\\s]+\\.(?:js|ts|mjs|mts|jsx|tsx|cjs|cts))(?::\\d+:\\d+)?/,\n );\n if (fileMatch) {\n const rawPath = fileMatch[1];\n // Normalize the path:\n // - remove any leading file:// or file:/// prefix\n // - convert Windows backslashes to POSIX-style forward slashes\n const normalizedPath = rawPath\n .replace(/^file:\\/+/, '')\n .replace(/\\\\/g, '/');\n const lowerPath = normalizedPath.toLowerCase();\n\n // Skip smrt-core internal files using specific path patterns\n // to avoid false positives with user files containing \"registry\" in name\n if (\n // Installed package form: node_modules/@happyvertical/smrt-core/...\n lowerPath.includes('/node_modules/@happyvertical/smrt-core/') ||\n // Monorepo/workspace form: packages/core/src/...\n (lowerPath.includes('/packages/core/src/') &&\n !lowerPath.includes('__tests__')) ||\n // Specific manifest loader internals\n lowerPath.includes('/manifest/manifest-loader') ||\n // Decorator-lowering runtime helper (oxc/tslib/swc/babel) — the frame\n // the compiler inserts between @smrt() and the declaring module (#1785)\n isDecoratorRuntimeFramePath(normalizedPath)\n ) {\n continue;\n }\n return normalizedPath;\n }\n }\n return undefined;\n}\n\n// ── Shared state accessor functions ──────────────────────────────────────\n\nexport function getClasses(): Map<string, RegisteredClass> {\n if (!globalThis.__smrtRegistryClasses) {\n globalThis.__smrtRegistryClasses = new Map<string, RegisteredClass>();\n }\n return globalThis.__smrtRegistryClasses;\n}\n\nexport function getCollections(): Map<string, typeof SmrtCollection> {\n if (!globalThis.__smrtRegistryCollections) {\n globalThis.__smrtRegistryCollections = new Map<\n string,\n typeof SmrtCollection\n >();\n }\n return globalThis.__smrtRegistryCollections;\n}\n\nexport function getCollectionTableNames(): Map<string, string> {\n if (!globalThis.__smrtRegistryCollectionTableNames) {\n globalThis.__smrtRegistryCollectionTableNames = new Map<string, string>();\n }\n return globalThis.__smrtRegistryCollectionTableNames;\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: heterogeneous cache; `SmrtCollection` is invariant in `ModelType` so the type arg cannot narrow to `SmrtObject` (read sites cast back). S4 #1579.\nexport function getCollectionCache(): LRUCache<string, SmrtCollection<any>> {\n if (!globalThis.__smrtRegistryCollectionCache) {\n globalThis.__smrtRegistryCollectionCache = new LRUCache<\n string,\n // biome-ignore lint/suspicious/noExplicitAny: same heterogeneous-cache invariance as the return type above. S4 #1579.\n SmrtCollection<any>\n >(100);\n }\n return globalThis.__smrtRegistryCollectionCache;\n}\n\nexport function getDbInstanceIds(): WeakMap<object, number> {\n if (!globalThis.__smrtRegistryDbInstanceIds) {\n globalThis.__smrtRegistryDbInstanceIds = new WeakMap<object, number>();\n }\n return globalThis.__smrtRegistryDbInstanceIds;\n}\n\nexport function getNextDbId(): number {\n if (globalThis.__smrtRegistryNextDbId === undefined) {\n globalThis.__smrtRegistryNextDbId = 1;\n }\n return globalThis.__smrtRegistryNextDbId;\n}\n\nexport function setNextDbId(value: number): void {\n globalThis.__smrtRegistryNextDbId = value;\n}\n\nexport function getFieldDecorators(): Map<\n string,\n Map<string, Record<string, unknown>>\n> {\n if (!globalThis.__smrtRegistryFieldDecorators) {\n globalThis.__smrtRegistryFieldDecorators = new Map<\n string,\n Map<string, Record<string, unknown>>\n >();\n }\n return globalThis.__smrtRegistryFieldDecorators;\n}\n\nexport function getStiSiblingsLoaded(): Set<string> {\n if (!globalThis.__smrtRegistryStiSiblingsLoaded) {\n globalThis.__smrtRegistryStiSiblingsLoaded = new Set<string>();\n }\n return globalThis.__smrtRegistryStiSiblingsLoaded;\n}\n\n// getClassNameMap removed in Release B (#1133). The eagerly-maintained\n// lowercase-simple-name → qualified-key[] index was replaced by on-demand\n// iteration over the `classes` Map in name-resolver.ts. Any remaining\n// consumer should switch to `findClassesByName`, `getCanonicalClassName`,\n// or `findClass`.\n\nexport function getSchemasInitialized(): WeakSet<object> {\n if (!globalThis.__smrtRegistrySchemasInitialized) {\n globalThis.__smrtRegistrySchemasInitialized = new WeakSet<object>();\n }\n return globalThis.__smrtRegistrySchemasInitialized;\n}\n\nexport function getConstructorIndex(): WeakMap<SmrtObjectConstructor, string> {\n if (!globalThis.__smrtRegistryConstructorIndex) {\n globalThis.__smrtRegistryConstructorIndex = new WeakMap<\n SmrtObjectConstructor,\n string\n >();\n }\n return globalThis.__smrtRegistryConstructorIndex;\n}\n\nexport function getInheritanceCache(): LRUCache<string, string[]> {\n if (!globalThis.__smrtRegistryInheritanceChainCache) {\n const config = getInheritanceConfig();\n globalThis.__smrtRegistryInheritanceChainCache = new LRUCache<\n string,\n string[]\n >(config.cacheSize);\n }\n return globalThis.__smrtRegistryInheritanceChainCache;\n}\n\nexport function getDiscoveryAttemptCache(): Map<string, boolean> {\n if (!globalThis.__smrtRegistryDiscoveryAttemptCache) {\n globalThis.__smrtRegistryDiscoveryAttemptCache = new Map<string, boolean>();\n }\n return globalThis.__smrtRegistryDiscoveryAttemptCache;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0EA,IAAa,kBACX,QAAQ,IAAI,iBAAiB,WAC5B,QAAQ,IAAI,OAAO,SAAS,MAAM,KAAK;AAI1C,IAAM,SAAS,aAAa,EAAE,OAAO,kBAAkB,UAAU,OAAO,CAAC;;;;;;AAOzE,SAAgB,WAAW,GAAG,MAAuB;CACnD,IAAI,iBAAiB;EACnB,MAAM,CAAC,SAAS,GAAG,QAAQ;EAC3B,OAAO,MACL,OAAO,YAAY,WAAW,UAAU,OAAO,OAAO,GACtD,KAAK,SAAS,IAAI,EAAE,MAAM,KAAK,IAAI,KAAA,CACrC;CACF;AACF;;;;;AAMA,SAAgB,uBAAuB;CACrC,MAAM,SAAS,oBAAsC,QAAQ,CAAC,CAAC;CAC/D,OAAO;EACL,WAAW,OAAO,aAAa,aAAa;EAC5C,mBACE,OAAO,aAAa,qBAAsB;CAC9C;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,uBACd,eACoB;CAEpB,MAAM,cADQ,kCAAiB,IAAI,MAAM,EAAA,CAAE,SAAS,GAAA,CAC3B,MAAM,IAAI;CAKnC,KAAK,MAAM,QAAQ,YAAY;EAE7B,MAAM,YAAY,KAAK,MACrB,yEACF;EACA,IAAI,WAAW;GAKb,MAAM,iBAJU,UAAU,EAIH,CACpB,QAAQ,aAAa,EAAE,CAAC,CACxB,QAAQ,OAAO,GAAG;GACrB,MAAM,YAAY,eAAe,YAAY;GAI7C,IAEE,UAAU,SAAS,yCAAyC,KAE3D,UAAU,SAAS,qBAAqB,KACvC,CAAC,UAAU,SAAS,WAAW,KAEjC,UAAU,SAAS,2BAA2B,KAG9C,4BAA4B,cAAc,GAE1C;GAEF,OAAO;EACT;CACF;AAEF;AAIA,SAAgB,aAA2C;CACzD,IAAI,CAAC,WAAW,uBACd,WAAW,wCAAwB,IAAI,IAA6B;CAEtE,OAAO,WAAW;AACpB;AAEA,SAAgB,iBAAqD;CACnE,IAAI,CAAC,WAAW,2BACd,WAAW,4CAA4B,IAAI,IAGzC;CAEJ,OAAO,WAAW;AACpB;AAEA,SAAgB,0BAA+C;CAC7D,IAAI,CAAC,WAAW,oCACd,WAAW,qDAAqC,IAAI,IAAoB;CAE1E,OAAO,WAAW;AACpB;AAGA,SAAgB,qBAA4D;CAC1E,IAAI,CAAC,WAAW,+BACd,WAAW,gCAAgC,IAAI,SAI7C,GAAG;CAEP,OAAO,WAAW;AACpB;AAEA,SAAgB,mBAA4C;CAC1D,IAAI,CAAC,WAAW,6BACd,WAAW,8CAA8B,IAAI,QAAwB;CAEvE,OAAO,WAAW;AACpB;AAEA,SAAgB,cAAsB;CACpC,IAAI,WAAW,2BAA2B,KAAA,GACxC,WAAW,yBAAyB;CAEtC,OAAO,WAAW;AACpB;AAEA,SAAgB,YAAY,OAAqB;CAC/C,WAAW,yBAAyB;AACtC;AAEA,SAAgB,qBAGd;CACA,IAAI,CAAC,WAAW,+BACd,WAAW,gDAAgC,IAAI,IAG7C;CAEJ,OAAO,WAAW;AACpB;AAEA,SAAgB,uBAAoC;CAClD,IAAI,CAAC,WAAW,iCACd,WAAW,kDAAkC,IAAI,IAAY;CAE/D,OAAO,WAAW;AACpB;AAeA,SAAgB,sBAA8D;CAC5E,IAAI,CAAC,WAAW,gCACd,WAAW,iDAAiC,IAAI,QAG9C;CAEJ,OAAO,WAAW;AACpB;AAEA,SAAgB,sBAAkD;CAChE,IAAI,CAAC,WAAW,qCAAqC;EACnD,MAAM,SAAS,qBAAqB;EACpC,WAAW,sCAAsC,IAAI,SAGnD,OAAO,SAAS;CACpB;CACA,OAAO,WAAW;AACpB;AAEA,SAAgB,2BAAiD;CAC/D,IAAI,CAAC,WAAW,qCACd,WAAW,sDAAsC,IAAI,IAAqB;CAE5E,OAAO,WAAW;AACpB"}
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-03T03:19:20.783Z",
3
+ "generatedAt": "2026-07-03T06:22:21.290Z",
4
4
  "packageName": "@happyvertical/smrt-core",
5
- "packageVersion": "0.37.7",
5
+ "packageVersion": "0.37.8",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "2e61119f977c1eb4abfb1a09b7b13f8c4711b6feba80e539ac1f55b41adcd3e2",
10
- "packageJson": "f23470e3d070e8985072849f5dedad47ba5c5eddddb30fe23ccf761ffb067011",
11
- "agents": "77c66a0b0e98a9f219bb5d901fd226366df01ffe8bdac5ad23d8785d08dee7cd"
9
+ "manifest": "6eb936bc13c04bf302820a0f448401920e94bc0c83d202cf938c1cc995dfb031",
10
+ "packageJson": "d9ca665cf6cd288680ab2375245af2978934a8456dbc55ac6cc82dec15309882",
11
+ "agents": "30b544c7ce9bfc7680b4c6ac001076f245816b2a5c379afc88e6c6c218a82ac9"
12
12
  },
13
13
  "exports": [
14
14
  ".",
@@ -323,5 +323,5 @@
323
323
  "polymorphicAssociations": 1,
324
324
  "uuidColumns": 3
325
325
  },
326
- "agentDoc": "# @happyvertical/smrt-core\n\nORM, code generation, AI integration, and the DispatchBus. Everything else builds on this.\n\n## Key Classes\n\n| Class | File | Purpose |\n|-------|------|---------|\n| SmrtObject | `src/object.ts` | Base persistent object — save, delete, is(), do(), loadFromId/Slug |\n| SmrtCollection | `src/collection.ts` | CRUD collection — list, get, create, delete, getOrUpsert |\n| ObjectRegistry | `src/registry.ts` | Global singleton (globalThis) — class metadata, fields, STI chains, manifests |\n| DispatchBus | `src/dispatch/bus.ts` | Inter-agent messaging — emit, subscribe (persistent), process |\n| GlobalInterceptors | `src/interceptors.ts` | Plugin system — beforeList/Get/Save/Delete hooks (used by tenancy) |\n\n## SmrtObject Lifecycle\n\n`constructor(options)` → `initialize()` → ready for `save()`/`delete()`/`loadFromId()`\n\n- `initialize()`: loads field initializers, applies option values (options override initializers), loads from DB if id/slug provided\n- `save()`: upsert with STI validation, interceptor execution, auto-embeddings. Persisted objects (`isPersisted` — set by DB hydration and successful saves) upsert on `['id']` so natural-key edits (e.g. slug renames) update in place; new objects upsert on the natural-key conflict columns for ingestion-style dedup (#1472)\n- `is(criteria)` / `do(instructions)` / `describe()`: AI operations via function calling. They inject the object's own `toPublicJSON()` (sensitive fields stripped) as a \"content body\" so the model reasons over the instance. Options: `includeData: false` skips injection (for callers that already curate the relevant fields into the instruction); `maxDataLength` overrides the truncation budget. Neither key is forwarded to `ai.message()`. (#1567)\n- `getSlug()`: auto-generates from name → title → label → id\n- `loadRelated(fieldName)`: lazy-loads relationships (cached in `_loadedRelationships` Map)\n\n## SmrtCollection Query\n\n```typescript\nawait collection.list({\n where: { status: 'active', price: { op: '>', value: 10 } },\n limit: 50, offset: 0, orderBy: 'created_at DESC'\n});\n```\n\n**WHERE operators**: `=`, `>`, `<`, `>=`, `<=`, `!=`, `in`, `not in`, `like`, `is null`, `is not null`. Arrays auto-detect `IN`. Dot notation for JSON paths: `metadata.userId`.\n\nSTI child collections auto-filter by `_meta_type`.\n\n## @smrt() Decorator Options\n\nKey options: `tableName`, `tableStrategy` ('cti'|'sti'), `conflictColumns`, `api`/`mcp`/`cli` (generation config), `ai` (callable methods), `hooks` (beforeSave/afterSave/beforeDelete/afterDelete), `embeddings` (auto-generate), `tenantScoped`, `agent`.\n\nRegistration sets `SMRT_TABLE_NAME` static property (survives minification).\n\n## Domain Knowledge Artifacts\n\n`smrtPlugin()` writes runtime manifests and agent/developer knowledge artifacts:\n\n- local dev/build: `.smrt/manifest.json` and `.smrt/smrt-knowledge.json`\n- package build: `dist/manifest.json` and `dist/smrt-knowledge.json`\n\nKeep `manifest.json` runtime-focused. `smrt-knowledge.json` is the deterministic\nagent contract for downstream review and architecture tools.\n\nConfig precedence for knowledge is defaults → top-level `knowledge` in\n`smrt.config.ts` → `packages[packageName].knowledge` → plugin option →\nobject-level `@smrt({ knowledge })`.\n\nObject-level `knowledge: false` excludes an object from authored context only;\nit must not change runtime manifest registration. Use\n`knowledge: { tags, summary, risks }` for review-sensitive domain objects.\n\nHTTP knowledge routes are disabled by default. If `knowledge.api.enabled` is\ntrue, generated SvelteKit routes must stay GET-only and guarded by dev mode or\nadmin auth.\n\n## DispatchBus\n\n- `emit(signalType, payload, metadata)` → creates persistent Dispatch record\n- `on(pattern, handler)` → in-memory handler (immediate)\n- `subscribe({ signalType, subscriber })` → persistent subscription (survives restarts)\n- `process(subscriberName, handler)` → process pending dispatches\n- Wildcards: `campaign.*` matches `campaign.completed` (single segment only)\n- Tables: `_smrt_dispatch`, `_smrt_dispatch_subscriptions`\n- Status: `pending → processing → completed` (or `failed`)\n\n## Single Table Inheritance (STI)\n\n- Base: `@smrt({ tableStrategy: 'sti' })` — children inherit, share one table\n- Discriminator: `_meta_type` column with qualified names (`@happyvertical/smrt-content:Article`)\n- Child fields: `@meta()` decorator → stored in `_meta_data` JSONB (not as columns)\n- Polymorphic queries: collection loads `_meta_type`, creates correct subclass dynamically\n- Validation: fail-fast on save if `_meta_type` missing or mismatched\n\n## Code Generators\n\n| Generator | Location | Output |\n|-----------|----------|--------|\n| REST API | `src/generators/rest.ts` | OpenAPI-compliant CRUD endpoints |\n| CLI | `src/generators/cli.ts` | `objectname:action` admin commands — writable allowlist, exhaustive-include, `--from-file`, fail-closed tenant context |\n| MCP Server | `src/generators/mcp.ts` | Model Context Protocol tools |\n\nGenerated reads (`list`/`get`) on the REST and SvelteKit generators support conditional GET (#1757, helpers in `src/generators/conditional-get.ts`): strong body-hash ETag, `If-None-Match` → 304 with an empty body, `private, no-cache` by default. Public models may opt into shared caching via `@smrt({ api: { public: true | 'read', cache: { sMaxage } } })` → `public, max-age=0, s-maxage=<n>`; non-public models never emit shared-cache headers. Tenant-scoped models (any mode) never emit them either — bodies vary with session-cookie tenant context that URL-keyed shared caches cannot see; `sMaxage` is neutralized to `private, no-cache` with a one-time warning.\n\n## Child Accessors (R10)\n\n`src/child-accessors.ts` installs a consistent `get<FieldName>()` instance method for every `@oneToMany` field at `@smrt()` registration time (e.g. `@oneToMany('OrderItem') items` → `order.getItems()`), delegating to `loadRelatedMany`. Two invariants:\n\n- **Additive** — never overwrites a hand-rolled method of the same name (checks the whole prototype chain). `Profile.getMetadata()` (key-value) and `ProfileRelationship.getTerms()` are preserved.\n- **Runtime-only** — attached to the prototype, invisible to the build-time manifest, so it never leaks into the REST/CLI/MCP surface.\n\nWhen the target declares multiple FKs back to the parent, annotate `@oneToMany(Target, { foreignKey: '<inverseField>' })`; `loadRelatedMany` and the eager `include:` loader both honor it (else first-match).\n\n## Vite Plugin\n\n```typescript\n// vite.config.ts — required for @smrt() decorators\nexport default defineConfig({\n esbuild: {\n tsconfigRaw: {\n compilerOptions: { experimentalDecorators: true, emitDecoratorMetadata: true }\n }\n }\n});\n```\n\n## Gotchas\n\n- **Never override toJSON()** — handles STI discriminator + meta field extraction. Use `transformJSON()`\n- **Property init order**: TypeScript initializers run first, then `initialize()` applies option values (options win)\n- **No runtime schema creation**: application tables must be prepared explicitly via migrations/tooling; runtime only verifies and fails clearly\n- **Retry logic**: `db.get()` (3 retries, 250ms) and `db.upsert()` (3 retries, 500ms) have built-in retry\n- **Field caching**: `_cachedFields` populated during `Collection.create()` — eliminates async `getFields()` per query\n- **Smart cloning**: arrays/objects shallow-cloned in property init to prevent aliasing (Issue #22)\n- **Table verification cache**: `isTableVerified(dbUrl, tableName)` avoids redundant `tableExists()` calls\n- **Manifest required**: build-time AST scanning creates manifest. Without vitest plugin → \"No field metadata\"\n- **Vite plugin loads scanner from `dist/` first**: `src/vite-plugin/import-build-aware.ts` prefers `dist/` when it exists on disk; it only falls back to `src/` on fresh clones. So if you edit `src/scanner/*.ts` or `src/schema/generator.ts` and want those edits reflected in consumer manifest generation, you must rebuild (`pnpm build` or have `pnpm dev` / `pnpm build:watch` running in core). This is intentional — sniffing `.ts` vs `.js` via `import.meta.url` was non-deterministic under tsx and broke 12–13 publishes (#1139).\n"
326
+ "agentDoc": "# @happyvertical/smrt-core\n\nORM, code generation, AI integration, and the DispatchBus. Everything else builds on this.\n\n## Key Classes\n\n| Class | File | Purpose |\n|-------|------|---------|\n| SmrtObject | `src/object.ts` | Base persistent object — save, delete, is(), do(), loadFromId/Slug |\n| SmrtCollection | `src/collection.ts` | CRUD collection — list, get, create, delete, getOrUpsert |\n| ObjectRegistry | `src/registry.ts` | Global singleton (globalThis) — class metadata, fields, STI chains, manifests |\n| DispatchBus | `src/dispatch/bus.ts` | Inter-agent messaging — emit, subscribe (persistent), process |\n| GlobalInterceptors | `src/interceptors.ts` | Plugin system — beforeList/Get/Save/Delete hooks (used by tenancy) |\n\n## SmrtObject Lifecycle\n\n`constructor(options)` → `initialize()` → ready for `save()`/`delete()`/`loadFromId()`\n\n- `initialize()`: loads field initializers, applies option values (options override initializers), loads from DB if id/slug provided\n- `save()`: upsert with STI validation, interceptor execution, auto-embeddings. Persisted objects (`isPersisted` — set by DB hydration and successful saves) upsert on `['id']` so natural-key edits (e.g. slug renames) update in place; new objects upsert on the natural-key conflict columns for ingestion-style dedup (#1472)\n- `is(criteria)` / `do(instructions)` / `describe()`: AI operations via function calling. They inject the object's own `toPublicJSON()` (sensitive fields stripped) as a \"content body\" so the model reasons over the instance. Options: `includeData: false` skips injection (for callers that already curate the relevant fields into the instruction); `maxDataLength` overrides the truncation budget. Neither key is forwarded to `ai.message()`. (#1567)\n- `getSlug()`: auto-generates from name → title → label → id\n- `loadRelated(fieldName)`: lazy-loads relationships (cached in `_loadedRelationships` Map)\n\n## SmrtCollection Query\n\n```typescript\nawait collection.list({\n where: { status: 'active', price: { op: '>', value: 10 } },\n limit: 50, offset: 0, orderBy: 'created_at DESC'\n});\n```\n\n**WHERE operators**: `=`, `>`, `<`, `>=`, `<=`, `!=`, `in`, `not in`, `like`, `is null`, `is not null`. Arrays auto-detect `IN`. Dot notation for JSON paths: `metadata.userId`.\n\nSTI child collections auto-filter by `_meta_type`.\n\n## @smrt() Decorator Options\n\nKey options: `tableName`, `tableStrategy` ('cti'|'sti'), `conflictColumns`, `api`/`mcp`/`cli` (generation config), `ai` (callable methods), `hooks` (beforeSave/afterSave/beforeDelete/afterDelete), `embeddings` (auto-generate), `tenantScoped`, `agent`.\n\nRegistration sets `SMRT_TABLE_NAME` static property (survives minification).\n\n## Domain Knowledge Artifacts\n\n`smrtPlugin()` writes runtime manifests and agent/developer knowledge artifacts:\n\n- local dev/build: `.smrt/manifest.json` and `.smrt/smrt-knowledge.json`\n- package build: `dist/manifest.json` and `dist/smrt-knowledge.json`\n\nKeep `manifest.json` runtime-focused. `smrt-knowledge.json` is the deterministic\nagent contract for downstream review and architecture tools.\n\nConfig precedence for knowledge is defaults → top-level `knowledge` in\n`smrt.config.ts` → `packages[packageName].knowledge` → plugin option →\nobject-level `@smrt({ knowledge })`.\n\nObject-level `knowledge: false` excludes an object from authored context only;\nit must not change runtime manifest registration. Use\n`knowledge: { tags, summary, risks }` for review-sensitive domain objects.\n\nHTTP knowledge routes are disabled by default. If `knowledge.api.enabled` is\ntrue, generated SvelteKit routes must stay GET-only and guarded by dev mode or\nadmin auth.\n\n## DispatchBus\n\n- `emit(signalType, payload, metadata)` → creates persistent Dispatch record\n- `on(pattern, handler)` → in-memory handler (immediate)\n- `subscribe({ signalType, subscriber })` → persistent subscription (survives restarts)\n- `process(subscriberName, handler)` → process pending dispatches\n- Wildcards: `campaign.*` matches `campaign.completed` (single segment only)\n- Tables: `_smrt_dispatch`, `_smrt_dispatch_subscriptions`\n- Status: `pending → processing → completed` (or `failed`)\n\n## Single Table Inheritance (STI)\n\n- Base: `@smrt({ tableStrategy: 'sti' })` — children inherit, share one table\n- Discriminator: `_meta_type` column with qualified names (`@happyvertical/smrt-content:Article`)\n- Child fields: `@meta()` decorator → stored in `_meta_data` JSONB (not as columns)\n- Polymorphic queries: collection loads `_meta_type`, creates correct subclass dynamically\n- Validation: fail-fast on save if `_meta_type` missing or mismatched\n\n## Code Generators\n\n| Generator | Location | Output |\n|-----------|----------|--------|\n| REST API | `src/generators/rest.ts` | OpenAPI-compliant CRUD endpoints |\n| CLI | `src/generators/cli.ts` | `objectname:action` admin commands — writable allowlist, exhaustive-include, `--from-file`, fail-closed tenant context |\n| MCP Server | `src/generators/mcp.ts` | Model Context Protocol tools |\n\nGenerated reads (`list`/`get`) on the REST and SvelteKit generators support conditional GET (#1757, helpers in `src/generators/conditional-get.ts`): strong body-hash ETag, `If-None-Match` → 304 with an empty body, `private, no-cache` by default. Public models may opt into shared caching via `@smrt({ api: { public: true | 'read', cache: { sMaxage } } })` → `public, max-age=0, s-maxage=<n>`; non-public models never emit shared-cache headers. Tenant-scoped models (any mode) never emit them either — bodies vary with session-cookie tenant context that URL-keyed shared caches cannot see; `sMaxage` is neutralized to `private, no-cache` with a one-time warning.\n\n## Child Accessors (R10)\n\n`src/child-accessors.ts` installs a consistent `get<FieldName>()` instance method for every `@oneToMany` field at `@smrt()` registration time (e.g. `@oneToMany('OrderItem') items` → `order.getItems()`), delegating to `loadRelatedMany`. Two invariants:\n\n- **Additive** — never overwrites a hand-rolled method of the same name (checks the whole prototype chain). `Profile.getMetadata()` (key-value) and `ProfileRelationship.getTerms()` are preserved.\n- **Runtime-only** — attached to the prototype, invisible to the build-time manifest, so it never leaks into the REST/CLI/MCP surface.\n\nWhen the target declares multiple FKs back to the parent, annotate `@oneToMany(Target, { foreignKey: '<inverseField>' })`; `loadRelatedMany` and the eager `include:` loader both honor it (else first-match).\n\n## Vite Plugin\n\n```typescript\n// vite.config.ts — required for @smrt() decorators (Vite 8+, oxc transform)\nexport default defineConfig({\n oxc: {\n decorator: {\n legacy: true,\n emitDecoratorMetadata: true,\n },\n },\n});\n```\n\nUnder Vite 8 the oxc transform does not honor the pre-Vite-8 `esbuild.tsconfigRaw`\nrecipe (or tsconfig `experimentalDecorators` reached through SvelteKit's\n`extends \"./.svelte-kit/tsconfig.json\"` chain), so that recipe throws\n`SyntaxError: Invalid or unexpected token` on the first SSR request. Configure\ndecorators through `oxc.decorator` instead. Consumers still pinned on vite<8 need\nthe legacy `esbuild.tsconfigRaw` form with `experimentalDecorators: true,\nemitDecoratorMetadata: true`.\n\n## Gotchas\n\n- **Never override toJSON()** — handles STI discriminator + meta field extraction. Use `transformJSON()`\n- **Property init order**: TypeScript initializers run first, then `initialize()` applies option values (options win)\n- **No runtime schema creation**: application tables must be prepared explicitly via migrations/tooling; runtime only verifies and fails clearly\n- **Retry logic**: `db.get()` (3 retries, 250ms) and `db.upsert()` (3 retries, 500ms) have built-in retry\n- **Field caching**: `_cachedFields` populated during `Collection.create()` — eliminates async `getFields()` per query\n- **Smart cloning**: arrays/objects shallow-cloned in property init to prevent aliasing (Issue #22)\n- **Table verification cache**: `isTableVerified(dbUrl, tableName)` avoids redundant `tableExists()` calls\n- **Manifest required**: build-time AST scanning creates manifest. Without vitest plugin → \"No field metadata\"\n- **Vite plugin loads scanner from `dist/` first**: `src/vite-plugin/import-build-aware.ts` prefers `dist/` when it exists on disk; it only falls back to `src/` on fresh clones. So if you edit `src/scanner/*.ts` or `src/schema/generator.ts` and want those edits reflected in consumer manifest generation, you must rebuild (`pnpm build` or have `pnpm dev` / `pnpm build:watch` running in core). This is intentional — sniffing `.ts` vs `.js` via `import.meta.url` was non-deterministic under tsx and broke 12–13 publishes (#1139).\n"
327
327
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Decorator-runtime stack-frame detection (#1785).
3
+ *
4
+ * ObjectRegistry attributes a `@smrt()` class to its declaring package by
5
+ * walking the registration call stack for the first frame OUTSIDE smrt-core —
6
+ * the module that applied the decorator. Decorator *lowering* breaks that
7
+ * assumption: the compiler inserts a runtime-helper frame between the `@smrt()`
8
+ * decorator and the consumer module that declares the class.
9
+ *
10
+ * Under Vite 8, oxc's legacy-decorator lowering routes the decorator application
11
+ * through `@oxc-project/runtime`'s helper (e.g.
12
+ * `.../@oxc-project/runtime/src/helpers/esm/applyDecoratedDescriptor.js`), so a
13
+ * naive walk stops at that frame and misattributes the class to
14
+ * `@oxc-project/runtime` instead of the consumer package. `tslib` (tsc's
15
+ * `__decorate`), `@swc/helpers`, and `@babel/runtime` insert the identical kind
16
+ * of frame under their respective toolchains.
17
+ *
18
+ * These are pure plumbing packages — a class is applied THROUGH them, never
19
+ * DECLARED in them — so both stack walks (source-file identity in
20
+ * `registry/shared-state.ts` and package attribution in
21
+ * `manifest/manifest-loader.ts`) skip them and continue to the real declaring
22
+ * module. Kept as a leaf module (no smrt imports) so both walkers can share it
23
+ * without risking an import cycle.
24
+ */
25
+ /**
26
+ * Runtime-helper packages that decorator lowering routes the decorator
27
+ * application through. Frames from these packages are never the module that
28
+ * declares a decorated class.
29
+ */
30
+ export declare const DECORATOR_RUNTIME_PACKAGES: readonly string[];
31
+ /**
32
+ * Whether a file path (normalized to forward slashes) lives inside a
33
+ * decorator-runtime helper package — i.e. a stack frame introduced by decorator
34
+ * lowering rather than the module that declares the decorated class.
35
+ *
36
+ * Matches both the plain installed form (`.../node_modules/tslib/...`) and the
37
+ * pnpm form (`.../node_modules/.pnpm/tslib@2/node_modules/tslib/...`) because
38
+ * the real `/<package>/` segment appears in both. Case-insensitive.
39
+ *
40
+ * @param path - A file path from a stack frame (already normalized to `/`).
41
+ */
42
+ export declare function isDecoratorRuntimeFramePath(path: string): boolean;
43
+ /**
44
+ * Whether a scoped package name (as captured from a `node_modules/@scope/name`
45
+ * stack match) is a decorator-runtime helper package.
46
+ *
47
+ * @param name - A scoped package name, e.g. `@oxc-project/runtime`.
48
+ */
49
+ export declare function isDecoratorRuntimePackageName(name: string): boolean;
50
+ //# sourceMappingURL=stack-frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-frames.d.ts","sourceRoot":"","sources":["../../src/utils/stack-frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAKvD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnE"}
@@ -0,0 +1,64 @@
1
+ //#region src/utils/stack-frames.ts
2
+ /**
3
+ * Decorator-runtime stack-frame detection (#1785).
4
+ *
5
+ * ObjectRegistry attributes a `@smrt()` class to its declaring package by
6
+ * walking the registration call stack for the first frame OUTSIDE smrt-core —
7
+ * the module that applied the decorator. Decorator *lowering* breaks that
8
+ * assumption: the compiler inserts a runtime-helper frame between the `@smrt()`
9
+ * decorator and the consumer module that declares the class.
10
+ *
11
+ * Under Vite 8, oxc's legacy-decorator lowering routes the decorator application
12
+ * through `@oxc-project/runtime`'s helper (e.g.
13
+ * `.../@oxc-project/runtime/src/helpers/esm/applyDecoratedDescriptor.js`), so a
14
+ * naive walk stops at that frame and misattributes the class to
15
+ * `@oxc-project/runtime` instead of the consumer package. `tslib` (tsc's
16
+ * `__decorate`), `@swc/helpers`, and `@babel/runtime` insert the identical kind
17
+ * of frame under their respective toolchains.
18
+ *
19
+ * These are pure plumbing packages — a class is applied THROUGH them, never
20
+ * DECLARED in them — so both stack walks (source-file identity in
21
+ * `registry/shared-state.ts` and package attribution in
22
+ * `manifest/manifest-loader.ts`) skip them and continue to the real declaring
23
+ * module. Kept as a leaf module (no smrt imports) so both walkers can share it
24
+ * without risking an import cycle.
25
+ */
26
+ /**
27
+ * Runtime-helper packages that decorator lowering routes the decorator
28
+ * application through. Frames from these packages are never the module that
29
+ * declares a decorated class.
30
+ */
31
+ var DECORATOR_RUNTIME_PACKAGES = [
32
+ "@oxc-project/runtime",
33
+ "@swc/helpers",
34
+ "@babel/runtime",
35
+ "tslib"
36
+ ];
37
+ /**
38
+ * Whether a file path (normalized to forward slashes) lives inside a
39
+ * decorator-runtime helper package — i.e. a stack frame introduced by decorator
40
+ * lowering rather than the module that declares the decorated class.
41
+ *
42
+ * Matches both the plain installed form (`.../node_modules/tslib/...`) and the
43
+ * pnpm form (`.../node_modules/.pnpm/tslib@2/node_modules/tslib/...`) because
44
+ * the real `/<package>/` segment appears in both. Case-insensitive.
45
+ *
46
+ * @param path - A file path from a stack frame (already normalized to `/`).
47
+ */
48
+ function isDecoratorRuntimeFramePath(path) {
49
+ const lower = path.toLowerCase();
50
+ return DECORATOR_RUNTIME_PACKAGES.some((pkg) => lower.includes(`/${pkg}/`));
51
+ }
52
+ /**
53
+ * Whether a scoped package name (as captured from a `node_modules/@scope/name`
54
+ * stack match) is a decorator-runtime helper package.
55
+ *
56
+ * @param name - A scoped package name, e.g. `@oxc-project/runtime`.
57
+ */
58
+ function isDecoratorRuntimePackageName(name) {
59
+ return DECORATOR_RUNTIME_PACKAGES.includes(name);
60
+ }
61
+ //#endregion
62
+ export { DECORATOR_RUNTIME_PACKAGES, isDecoratorRuntimeFramePath, isDecoratorRuntimePackageName };
63
+
64
+ //# sourceMappingURL=stack-frames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-frames.js","names":[],"sources":["../../src/utils/stack-frames.ts"],"sourcesContent":["/**\n * Decorator-runtime stack-frame detection (#1785).\n *\n * ObjectRegistry attributes a `@smrt()` class to its declaring package by\n * walking the registration call stack for the first frame OUTSIDE smrt-core —\n * the module that applied the decorator. Decorator *lowering* breaks that\n * assumption: the compiler inserts a runtime-helper frame between the `@smrt()`\n * decorator and the consumer module that declares the class.\n *\n * Under Vite 8, oxc's legacy-decorator lowering routes the decorator application\n * through `@oxc-project/runtime`'s helper (e.g.\n * `.../@oxc-project/runtime/src/helpers/esm/applyDecoratedDescriptor.js`), so a\n * naive walk stops at that frame and misattributes the class to\n * `@oxc-project/runtime` instead of the consumer package. `tslib` (tsc's\n * `__decorate`), `@swc/helpers`, and `@babel/runtime` insert the identical kind\n * of frame under their respective toolchains.\n *\n * These are pure plumbing packages — a class is applied THROUGH them, never\n * DECLARED in them — so both stack walks (source-file identity in\n * `registry/shared-state.ts` and package attribution in\n * `manifest/manifest-loader.ts`) skip them and continue to the real declaring\n * module. Kept as a leaf module (no smrt imports) so both walkers can share it\n * without risking an import cycle.\n */\n\n/**\n * Runtime-helper packages that decorator lowering routes the decorator\n * application through. Frames from these packages are never the module that\n * declares a decorated class.\n */\nexport const DECORATOR_RUNTIME_PACKAGES: readonly string[] = [\n '@oxc-project/runtime',\n '@swc/helpers',\n '@babel/runtime',\n 'tslib',\n];\n\n/**\n * Whether a file path (normalized to forward slashes) lives inside a\n * decorator-runtime helper package — i.e. a stack frame introduced by decorator\n * lowering rather than the module that declares the decorated class.\n *\n * Matches both the plain installed form (`.../node_modules/tslib/...`) and the\n * pnpm form (`.../node_modules/.pnpm/tslib@2/node_modules/tslib/...`) because\n * the real `/<package>/` segment appears in both. Case-insensitive.\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isDecoratorRuntimeFramePath(path: string): boolean {\n const lower = path.toLowerCase();\n return DECORATOR_RUNTIME_PACKAGES.some((pkg) => lower.includes(`/${pkg}/`));\n}\n\n/**\n * Whether a scoped package name (as captured from a `node_modules/@scope/name`\n * stack match) is a decorator-runtime helper package.\n *\n * @param name - A scoped package name, e.g. `@oxc-project/runtime`.\n */\nexport function isDecoratorRuntimePackageName(name: string): boolean {\n return DECORATOR_RUNTIME_PACKAGES.includes(name);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,6BAAgD;CAC3D;CACA;CACA;CACA;AACF;;;;;;;;;;;;AAaA,SAAgB,4BAA4B,MAAuB;CACjE,MAAM,QAAQ,KAAK,YAAY;CAC/B,OAAO,2BAA2B,MAAM,QAAQ,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;AAC5E;;;;;;;AAQA,SAAgB,8BAA8B,MAAuB;CACnE,OAAO,2BAA2B,SAAS,IAAI;AACjD"}
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit-generator.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sveltekit-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAQvE,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAydD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD;AAiUD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAqDf;AAorBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,qBAAqB,GAC/B,GAAG,CAAC,MAAM,CAAC,CAwCb;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,mBAAmB,GAC5B,wBAAwB,EAAE,CA+B5B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,mBAAmB,GAC5B,IAAI,CAkBN"}
1
+ {"version":3,"file":"sveltekit-generator.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sveltekit-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAQvE,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC;AA0eD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD;AAiUD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAqDf;AAorBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,qBAAqB,GAC/B,GAAG,CAAC,MAAM,CAAC,CAwCb;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,mBAAmB,GAC5B,wBAAwB,EAAE,CA+B5B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,mBAAmB,GAC5B,IAAI,CAkBN"}
@@ -271,24 +271,28 @@ function isTenantScoped(objectDef) {
271
271
  return !!objectDef.decoratorConfig?.tenantScoped;
272
272
  }
273
273
  /**
274
- * Emit the tenant-context establishment helper for `@TenantScoped` objects
275
- * (#1540, facet 2). Generated handlers call `establishTenantContext(locals)`
276
- * after the auth guard so every collection query runs inside the tenant context
277
- * derived from the authenticated principal — engaging the `@TenantScoped`
278
- * interceptors that auto-filter by tenant.
274
+ * Emit the tenant-context establishment + fail-closed read-scope helpers for
275
+ * `@TenantScoped` objects (#1540 facet 2; #1782). Generated handlers call
276
+ * `establishTenantContext(locals)` after the auth guard so every query runs
277
+ * inside the tenant context derived from the authenticated principal — engaging
278
+ * the `@TenantScoped` interceptors that auto-filter by tenant.
279
279
  *
280
- * Without this, an `optional`-mode scoped object queried over a generated route
281
- * with no active context returns rows from ALL tenants (the interceptor only
282
- * hard-fails `required` mode). We read the canonical `locals.tenantId` set by
283
- * the smrt-users auth hook (with `user`/`session` fallbacks). It is a no-op when
284
- * a context is already active (e.g. an upstream tenancy handle) or when the
285
- * principal carries no tenant (global/admin preserving optional-mode global
286
- * access). Imported only for tenant-scoped routes, which already depend on
287
- * `@happyvertical/smrt-tenancy`.
280
+ * `establishTenantContext` reads the canonical `locals.tenantId` set by the
281
+ * smrt-users auth hook (with `user`/`session` fallbacks). It is a no-op when a
282
+ * context is already active (e.g. an upstream tenancy handle) or when the
283
+ * request carries no tenant (anonymous / global principal).
284
+ *
285
+ * When no context could be established, an `optional`-mode read would otherwise
286
+ * fall through the interceptor UNFILTERED and return rows from ALL tenants (the
287
+ * interceptor only hard-fails `required` mode) — the #1782 leak. So read
288
+ * handlers additionally call `tenantReadScope()`, which restricts reads to
289
+ * NULL-tenant (global) rows only when tenancy is enabled but no context is
290
+ * active. Both helpers are imported only for tenant-scoped routes, which already
291
+ * depend on `@happyvertical/smrt-tenancy`.
288
292
  */
289
293
  function generateTenantContextHelper() {
290
294
  return `
291
- import { enterTenantContext, hasTenantContext } from '@happyvertical/smrt-tenancy';
295
+ import { enterTenantContext, hasTenantContext, isTenancyEnabled } from '@happyvertical/smrt-tenancy';
292
296
 
293
297
  function establishTenantContext(locals: unknown): void {
294
298
  if (hasTenantContext()) return;
@@ -301,6 +305,19 @@ function establishTenantContext(locals: unknown): void {
301
305
  enterTenantContext({ tenantId });
302
306
  }
303
307
  }
308
+
309
+ // Fail-closed read scope (#1782): a public/anonymous read on a @TenantScoped
310
+ // model has no tenant context, so the tenancy interceptor (optional mode) would
311
+ // pass the query through UNFILTERED and return every tenant's rows. When tenancy
312
+ // is enabled but no context was established, restrict reads to NULL-tenant
313
+ // (global) rows only — mirroring the dispatch resolver + _changes convention:
314
+ // tenancy enforced with no context => global rows only. Returns undefined when a
315
+ // context is active (the interceptor filters by it) or tenancy is disabled.
316
+ function tenantReadScope(): { tenantId: null } | undefined {
317
+ return isTenancyEnabled() && !hasTenantContext()
318
+ ? { tenantId: null }
319
+ : undefined;
320
+ }
304
321
  `;
305
322
  }
306
323
  /**
@@ -996,6 +1013,10 @@ ${generateAuthGuardHelper(objectDef)}${isTenantScoped(objectDef) ? generateTenan
996
1013
  tenantScoped: isTenantScoped(objectDef),
997
1014
  modelName: className
998
1015
  }) : ""}`;
1016
+ const listAndCount = isTenantScoped(objectDef) ? ` const readScope = tenantReadScope();
1017
+ const items = await collection.list({ limit, offset, where: readScope });
1018
+ const count = await collection.count({ where: readScope });` : ` const items = await collection.list({ limit, offset });
1019
+ const count = await collection.count();`;
999
1020
  const getHandler = hasGet ? `
1000
1021
  // List all ${className.toLowerCase()}s
1001
1022
  export const GET: RequestHandler = async ({ locals, url, request }) => {
@@ -1004,8 +1025,7 @@ ${routeGuardPreamble(objectDef, false)}
1004
1025
  const offset = Number(url.searchParams.get('offset')) || 0;
1005
1026
 
1006
1027
  ${generateCollectionLoad(className, { typeName: modelType.typeName })}
1007
- const items = await collection.list({ limit, offset });
1008
- const count = await collection.count();
1028
+ ${listAndCount}
1009
1029
  ${serializers.listItemSerializerName ? `
1010
1030
  const serializedItems = await Promise.all(
1011
1031
  items.map((item) => ${serializers.listItemSerializerName}(item)),
@@ -1092,12 +1112,16 @@ ${generateAuthGuardHelper(objectDef)}${isTenantScoped(objectDef) ? generateTenan
1092
1112
  tenantScoped: isTenantScoped(objectDef),
1093
1113
  modelName: className
1094
1114
  }) : ""}`;
1115
+ const getForRead = isTenantScoped(objectDef) ? ` const readScope = tenantReadScope();
1116
+ const item = await collection.get(
1117
+ readScope ? { id: params.id, ...readScope } : params.id,
1118
+ );` : ` const item = await collection.get(params.id);`;
1095
1119
  const getHandler = hasGet ? `
1096
1120
  // Get single ${simpleClassName.toLowerCase()}
1097
1121
  export const GET: RequestHandler = async ({ locals, params, request }) => {
1098
1122
  ${routeGuardPreamble(objectDef, false)}
1099
1123
  ${generateCollectionLoad(className, { typeName: modelType.typeName })}
1100
- const item = await collection.get(params.id);
1124
+ ${getForRead}
1101
1125
  ${generateNotFoundError(className)}
1102
1126
  ${serializers.itemSerializerName ? `
1103
1127
  const serializedItem = await ${serializers.itemSerializerName}(item);