@happyvertical/smrt-core 0.47.0 → 0.47.2

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 (84) hide show
  1. package/AGENTS.md +2 -0
  2. package/agents/generators.md +19 -5
  3. package/agents/registry-snapshot.md +28 -0
  4. package/agents/schema-paths.md +12 -0
  5. package/agents/system-diagnostics.md +43 -0
  6. package/dist/browser.js +4 -2
  7. package/dist/change-feed.d.ts +8 -0
  8. package/dist/change-feed.d.ts.map +1 -1
  9. package/dist/change-feed.js +1 -1
  10. package/dist/change-feed.js.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +5 -2
  14. package/dist/manifest/static-manifest.js +1 -1
  15. package/dist/manifest/static-manifest.js.map +1 -1
  16. package/dist/manifest/store.js +1 -1
  17. package/dist/manifest.json +1 -1
  18. package/dist/migrations/differ.d.ts +78 -0
  19. package/dist/migrations/differ.d.ts.map +1 -1
  20. package/dist/migrations/differ.js +191 -1
  21. package/dist/migrations/differ.js.map +1 -1
  22. package/dist/migrations/generator.d.ts.map +1 -1
  23. package/dist/migrations/generator.js +2 -1
  24. package/dist/migrations/generator.js.map +1 -1
  25. package/dist/postgres-permissions.d.ts +3 -0
  26. package/dist/postgres-permissions.d.ts.map +1 -1
  27. package/dist/postgres-permissions.js +49 -6
  28. package/dist/postgres-permissions.js.map +1 -1
  29. package/dist/prebuild/index.d.ts.map +1 -1
  30. package/dist/prebuild/index.js +4 -3
  31. package/dist/prebuild/index.js.map +1 -1
  32. package/dist/registry/class-registration.d.ts.map +1 -1
  33. package/dist/registry/class-registration.js +3 -1
  34. package/dist/registry/class-registration.js.map +1 -1
  35. package/dist/registry/schema-builder.js +1 -1
  36. package/dist/registry.js +1 -1
  37. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  38. package/dist/scanner/manifest-generator.js +22 -1
  39. package/dist/scanner/manifest-generator.js.map +1 -1
  40. package/dist/schema/foreign-key-ddl.d.ts +18 -0
  41. package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
  42. package/dist/schema/foreign-key-ddl.js +15 -2
  43. package/dist/schema/foreign-key-ddl.js.map +1 -1
  44. package/dist/schema/foreign-key-orphan-report.d.ts +57 -0
  45. package/dist/schema/foreign-key-orphan-report.d.ts.map +1 -0
  46. package/dist/schema/foreign-key-orphan-report.js +142 -0
  47. package/dist/schema/foreign-key-orphan-report.js.map +1 -0
  48. package/dist/schema/index.d.ts +1 -0
  49. package/dist/schema/index.d.ts.map +1 -1
  50. package/dist/schema/index.js +2 -1
  51. package/dist/schema/live-parity.d.ts +8 -1
  52. package/dist/schema/live-parity.d.ts.map +1 -1
  53. package/dist/schema/live-parity.js +98 -2
  54. package/dist/schema/live-parity.js.map +1 -1
  55. package/dist/schema/types.d.ts +11 -1
  56. package/dist/schema/types.d.ts.map +1 -1
  57. package/dist/schema.js +2 -1
  58. package/dist/smrt-knowledge.json +22 -10
  59. package/dist/system/diagnostics.d.ts +299 -0
  60. package/dist/system/diagnostics.d.ts.map +1 -0
  61. package/dist/system/diagnostics.js +530 -0
  62. package/dist/system/diagnostics.js.map +1 -0
  63. package/dist/system/index.d.ts +2 -0
  64. package/dist/system/index.d.ts.map +1 -1
  65. package/dist/system/index.js +3 -1
  66. package/dist/system/registry-snapshot.d.ts +112 -0
  67. package/dist/system/registry-snapshot.d.ts.map +1 -0
  68. package/dist/system/registry-snapshot.js +171 -0
  69. package/dist/system/registry-snapshot.js.map +1 -0
  70. package/dist/testing/database.d.ts.map +1 -1
  71. package/dist/testing/database.js +10 -2
  72. package/dist/testing/database.js.map +1 -1
  73. package/dist/utils/scanner-module.d.ts +16 -0
  74. package/dist/utils/scanner-module.d.ts.map +1 -1
  75. package/dist/vite-plugin/index.d.ts +34 -1
  76. package/dist/vite-plugin/index.d.ts.map +1 -1
  77. package/dist/vite-plugin/index.js +85 -5
  78. package/dist/vite-plugin/index.js.map +1 -1
  79. package/dist/vite-plugin/web-collections.d.ts +6 -0
  80. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  81. package/dist/vite-plugin/web-collections.js +9 -4
  82. package/dist/vite-plugin/web-collections.js.map +1 -1
  83. package/dist/vite-plugin.js +2 -2
  84. package/package.json +10 -10
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Sanitized, serializable snapshot of the booted `ObjectRegistry` (#1831).
3
+ *
4
+ * The registry holds live constructors, validators, tool payloads, and
5
+ * absolute source paths. None of that may cross a process or wire boundary:
6
+ * a development agent observing a booted runtime gets *this* DTO, built by
7
+ * projection, never the raw `RegisteredClass`. Every value here is plain JSON
8
+ * (strings, numbers, booleans, arrays, plain objects), which the test suite
9
+ * asserts structurally rather than by allow-list alone.
10
+ *
11
+ * The snapshot is a read of in-process state: it never touches a database,
12
+ * never boots project code, and never mutates the registry.
13
+ */
14
+ /** Provenance label for facts read from the booted in-process registry. */
15
+ export declare const BOOTED_PROVENANCE = "booted (registry)";
16
+ export interface RegistrySnapshotField {
17
+ name: string;
18
+ type: string;
19
+ required: boolean;
20
+ /** Relationship target for foreignKey / crossPackageRef / *ToMany fields. */
21
+ related: string | null;
22
+ transient: boolean;
23
+ /** Inherited from a base class rather than declared on the object. */
24
+ inherited: boolean;
25
+ /** Declared on a tenant-scoped/sensitive field policy — the value is never here. */
26
+ sqlType: string | null;
27
+ }
28
+ export interface RegistrySnapshotMethodParameter {
29
+ name: string;
30
+ type: string;
31
+ optional: boolean;
32
+ }
33
+ export interface RegistrySnapshotMethod {
34
+ name: string;
35
+ async: boolean;
36
+ isStatic: boolean;
37
+ isPublic: boolean;
38
+ parameters: RegistrySnapshotMethodParameter[];
39
+ returnType: string;
40
+ inherited: boolean;
41
+ }
42
+ export interface RegistrySnapshotObject {
43
+ name: string;
44
+ qualifiedName: string | null;
45
+ packageName: string | null;
46
+ tableName: string | null;
47
+ collection: string | null;
48
+ extends: string | null;
49
+ extendsTypeArg: string | null;
50
+ inheritanceChain: string[];
51
+ visibility: string | null;
52
+ /** Source path relative to `projectRoot`; absolute paths never leave the process. */
53
+ sourceFile: string | null;
54
+ tenantScoped: {
55
+ mode: 'required' | 'optional';
56
+ field: string;
57
+ } | null;
58
+ fieldCount: number;
59
+ methodCount: number;
60
+ fields: RegistrySnapshotField[];
61
+ methods: RegistrySnapshotMethod[];
62
+ }
63
+ export interface RegistrySnapshotDiagnostic {
64
+ severity: 'warn' | 'error';
65
+ code: string;
66
+ message: string;
67
+ }
68
+ export interface RegistrySnapshotSummary {
69
+ objectCount: number;
70
+ packages: Array<{
71
+ name: string;
72
+ objectCount: number;
73
+ }>;
74
+ tables: string[];
75
+ diagnosticCount: number;
76
+ }
77
+ export interface RuntimeRegistrySnapshot {
78
+ provenance: typeof BOOTED_PROVENANCE;
79
+ generatedAt: string;
80
+ summary: RegistrySnapshotSummary;
81
+ objects: RegistrySnapshotObject[];
82
+ diagnostics: RegistrySnapshotDiagnostic[];
83
+ }
84
+ export interface RegistrySnapshotOptions {
85
+ /** Root used to relativize source paths. Defaults to `process.cwd()`. */
86
+ projectRoot?: string;
87
+ /** Restrict `objects` to these simple or qualified names (summary stays global). */
88
+ objects?: string[];
89
+ /** Include per-object field and method detail (default `true`). */
90
+ detail?: boolean;
91
+ /** Clock for `generatedAt` (defaults to `new Date()`). */
92
+ now?: Date;
93
+ }
94
+ /**
95
+ * Project the booted registry into a sanitized, JSON-serializable snapshot.
96
+ *
97
+ * Pure read of in-process registry state: no database, no project code, no
98
+ * registry mutation. Objects are sorted by qualified name for stable output.
99
+ */
100
+ export declare function snapshotRegistry(options?: RegistrySnapshotOptions): RuntimeRegistrySnapshot;
101
+ /**
102
+ * Reduce absolute paths embedded in a message to the same relative/basename
103
+ * form {@link relativeSourcePath} produces, so diagnostic text cannot leak
104
+ * filesystem layout the structured fields already hide.
105
+ */
106
+ export declare function sanitizeMessagePaths(message: string, projectRoot: string): string;
107
+ /**
108
+ * Assert a value is plain JSON: no functions, classes, symbols, or cycles.
109
+ * Exposed so callers and tests can prove a snapshot is wire-safe.
110
+ */
111
+ export declare function assertPlainJson(value: unknown, path?: string): void;
112
+ //# sourceMappingURL=registry-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-snapshot.d.ts","sourceRoot":"","sources":["../../src/system/registry-snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,171 @@
1
+ import { ObjectRegistry } from "../registry.js";
2
+ import { basename, isAbsolute, relative, sep, win32 } from "node:path";
3
+ //#region src/system/registry-snapshot.ts
4
+ /**
5
+ * Sanitized, serializable snapshot of the booted `ObjectRegistry` (#1831).
6
+ *
7
+ * The registry holds live constructors, validators, tool payloads, and
8
+ * absolute source paths. None of that may cross a process or wire boundary:
9
+ * a development agent observing a booted runtime gets *this* DTO, built by
10
+ * projection, never the raw `RegisteredClass`. Every value here is plain JSON
11
+ * (strings, numbers, booleans, arrays, plain objects), which the test suite
12
+ * asserts structurally rather than by allow-list alone.
13
+ *
14
+ * The snapshot is a read of in-process state: it never touches a database,
15
+ * never boots project code, and never mutates the registry.
16
+ */
17
+ /** Provenance label for facts read from the booted in-process registry. */
18
+ var BOOTED_PROVENANCE = "booted (registry)";
19
+ function toStringOrNull(value) {
20
+ return typeof value === "string" && value.length > 0 ? value : null;
21
+ }
22
+ function relativeSourcePath(filePath, projectRoot) {
23
+ if (typeof filePath !== "string" || filePath.length === 0) return null;
24
+ const rel = relative(projectRoot, filePath);
25
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return basename(filePath) || null;
26
+ return rel.split(sep).join("/");
27
+ }
28
+ function toSnapshotField(name, field, inherited) {
29
+ return {
30
+ name,
31
+ type: String(field.type ?? "unknown"),
32
+ required: field.required === true,
33
+ related: toStringOrNull(field.related),
34
+ transient: field.transient === true,
35
+ inherited,
36
+ sqlType: toStringOrNull(field.sqlType)
37
+ };
38
+ }
39
+ function toSnapshotMethod(method, inherited) {
40
+ return {
41
+ name: method.name,
42
+ async: method.async === true,
43
+ isStatic: method.isStatic === true,
44
+ isPublic: method.isPublic === true,
45
+ parameters: (method.parameters ?? []).map((parameter) => ({
46
+ name: String(parameter.name),
47
+ type: String(parameter.type ?? "unknown"),
48
+ optional: parameter.optional === true
49
+ })),
50
+ returnType: String(method.returnType ?? "unknown"),
51
+ inherited
52
+ };
53
+ }
54
+ function toSnapshotObject(registered, projectRoot, detail) {
55
+ const fields = [];
56
+ const methods = [];
57
+ if (detail) {
58
+ for (const [name, field] of registered.inheritedFields ?? []) fields.push(toSnapshotField(name, field, true));
59
+ for (const [name, field] of registered.fields) fields.push(toSnapshotField(name, field, false));
60
+ for (const [, method] of registered.inheritedMethods ?? []) methods.push(toSnapshotMethod(method, true));
61
+ for (const [, method] of registered.methods) methods.push(toSnapshotMethod(method, false));
62
+ fields.sort((a, b) => a.name.localeCompare(b.name));
63
+ methods.sort((a, b) => a.name.localeCompare(b.name));
64
+ }
65
+ return {
66
+ name: registered.name,
67
+ qualifiedName: toStringOrNull(registered.qualifiedName),
68
+ packageName: toStringOrNull(registered.packageName),
69
+ tableName: ObjectRegistry.getTableName(registered.name) ?? null,
70
+ collection: toStringOrNull(registered.collection),
71
+ extends: toStringOrNull(registered.extends),
72
+ extendsTypeArg: toStringOrNull(registered.extendsTypeArg),
73
+ inheritanceChain: [...registered.inheritanceChain ?? []],
74
+ visibility: toStringOrNull(registered.visibility),
75
+ sourceFile: relativeSourcePath(registered.sourceFilePath, projectRoot),
76
+ tenantScoped: registered.tenantScopedConfig ? {
77
+ mode: registered.tenantScopedConfig.mode,
78
+ field: registered.tenantScopedConfig.field
79
+ } : null,
80
+ fieldCount: registered.fields.size + (registered.inheritedFields?.size ?? 0),
81
+ methodCount: registered.methods.size + (registered.inheritedMethods?.size ?? 0),
82
+ fields,
83
+ methods
84
+ };
85
+ }
86
+ function matchesFilter(registered, filter) {
87
+ if (!filter) return true;
88
+ return filter.has(registered.name) || typeof registered.qualifiedName === "string" && filter.has(registered.qualifiedName);
89
+ }
90
+ /**
91
+ * Project the booted registry into a sanitized, JSON-serializable snapshot.
92
+ *
93
+ * Pure read of in-process registry state: no database, no project code, no
94
+ * registry mutation. Objects are sorted by qualified name for stable output.
95
+ */
96
+ function snapshotRegistry(options = {}) {
97
+ const projectRoot = options.projectRoot ?? process.cwd();
98
+ const detail = options.detail !== false;
99
+ const filter = options.objects && options.objects.length > 0 ? new Set(options.objects) : null;
100
+ const all = [...ObjectRegistry.getAllClasses().values()].sort((a, b) => String(a.qualifiedName ?? a.name).localeCompare(String(b.qualifiedName ?? b.name)));
101
+ const packageCounts = /* @__PURE__ */ new Map();
102
+ const tables = /* @__PURE__ */ new Set();
103
+ for (const registered of all) {
104
+ const pkg = toStringOrNull(registered.packageName) ?? "(unqualified)";
105
+ packageCounts.set(pkg, (packageCounts.get(pkg) ?? 0) + 1);
106
+ const table = ObjectRegistry.getTableName(registered.name);
107
+ if (table) tables.add(table);
108
+ }
109
+ const diagnostics = ObjectRegistry.getDiagnostics().map((diagnostic) => ({
110
+ severity: diagnostic.severity,
111
+ code: diagnostic.code,
112
+ message: sanitizeMessagePaths(diagnostic.message, projectRoot)
113
+ }));
114
+ return {
115
+ provenance: BOOTED_PROVENANCE,
116
+ generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
117
+ summary: {
118
+ objectCount: all.length,
119
+ packages: [...packageCounts.entries()].map(([name, objectCount]) => ({
120
+ name,
121
+ objectCount
122
+ })).sort((a, b) => a.name.localeCompare(b.name)),
123
+ tables: [...tables].sort(),
124
+ diagnosticCount: diagnostics.length
125
+ },
126
+ objects: all.filter((registered) => matchesFilter(registered, filter)).map((registered) => toSnapshotObject(registered, projectRoot, detail)),
127
+ diagnostics
128
+ };
129
+ }
130
+ /** POSIX or Windows absolute path tokens inside free text. */
131
+ var ABSOLUTE_PATH_TOKEN = /(?:[A-Za-z]:)?(?:[\\/][^\s\\/'"`(),;]+){2,}/g;
132
+ /**
133
+ * Reduce absolute paths embedded in a message to the same relative/basename
134
+ * form {@link relativeSourcePath} produces, so diagnostic text cannot leak
135
+ * filesystem layout the structured fields already hide.
136
+ */
137
+ function sanitizeMessagePaths(message, projectRoot) {
138
+ return message.replace(ABSOLUTE_PATH_TOKEN, (token) => {
139
+ if (isAbsolute(token)) return relativeSourcePath(token, projectRoot) ?? token;
140
+ if (win32.isAbsolute(token)) return win32.basename(token) || token;
141
+ return token;
142
+ });
143
+ }
144
+ /**
145
+ * Assert a value is plain JSON: no functions, classes, symbols, or cycles.
146
+ * Exposed so callers and tests can prove a snapshot is wire-safe.
147
+ */
148
+ function assertPlainJson(value, path = "$") {
149
+ const seen = /* @__PURE__ */ new WeakSet();
150
+ const walk = (node, at) => {
151
+ if (node === null) return;
152
+ const kind = typeof node;
153
+ if (kind === "string" || kind === "number" || kind === "boolean") return;
154
+ if (kind !== "object") throw new Error(`non-JSON value (${kind}) at ${at}`);
155
+ const obj = node;
156
+ if (seen.has(obj)) throw new Error(`cycle at ${at}`);
157
+ seen.add(obj);
158
+ if (Array.isArray(obj)) {
159
+ for (const [index, item] of obj.entries()) walk(item, `${at}[${index}]`);
160
+ return;
161
+ }
162
+ const proto = Object.getPrototypeOf(obj);
163
+ if (proto !== Object.prototype && proto !== null) throw new Error(`non-plain object at ${at}`);
164
+ for (const [key, item] of Object.entries(obj)) walk(item, `${at}.${key}`);
165
+ };
166
+ walk(value, path);
167
+ }
168
+ //#endregion
169
+ export { BOOTED_PROVENANCE, assertPlainJson, sanitizeMessagePaths, snapshotRegistry };
170
+
171
+ //# sourceMappingURL=registry-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-snapshot.js","names":[],"sources":["../../src/system/registry-snapshot.ts"],"sourcesContent":["/**\n * Sanitized, serializable snapshot of the booted `ObjectRegistry` (#1831).\n *\n * The registry holds live constructors, validators, tool payloads, and\n * absolute source paths. None of that may cross a process or wire boundary:\n * a development agent observing a booted runtime gets *this* DTO, built by\n * projection, never the raw `RegisteredClass`. Every value here is plain JSON\n * (strings, numbers, booleans, arrays, plain objects), which the test suite\n * asserts structurally rather than by allow-list alone.\n *\n * The snapshot is a read of in-process state: it never touches a database,\n * never boots project code, and never mutates the registry.\n */\n\nimport { basename, isAbsolute, relative, sep, win32 } from 'node:path';\nimport type { RegisteredClass, RegisteredField } from '../registry/types.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { MethodDefinition } from '../scanner/types.js';\n\n/** Provenance label for facts read from the booted in-process registry. */\nexport const BOOTED_PROVENANCE = 'booted (registry)';\n\nexport interface RegistrySnapshotField {\n name: string;\n type: string;\n required: boolean;\n /** Relationship target for foreignKey / crossPackageRef / *ToMany fields. */\n related: string | null;\n transient: boolean;\n /** Inherited from a base class rather than declared on the object. */\n inherited: boolean;\n /** Declared on a tenant-scoped/sensitive field policy — the value is never here. */\n sqlType: string | null;\n}\n\nexport interface RegistrySnapshotMethodParameter {\n name: string;\n type: string;\n optional: boolean;\n}\n\nexport interface RegistrySnapshotMethod {\n name: string;\n async: boolean;\n isStatic: boolean;\n isPublic: boolean;\n parameters: RegistrySnapshotMethodParameter[];\n returnType: string;\n inherited: boolean;\n}\n\nexport interface RegistrySnapshotObject {\n name: string;\n qualifiedName: string | null;\n packageName: string | null;\n tableName: string | null;\n collection: string | null;\n extends: string | null;\n extendsTypeArg: string | null;\n inheritanceChain: string[];\n visibility: string | null;\n /** Source path relative to `projectRoot`; absolute paths never leave the process. */\n sourceFile: string | null;\n tenantScoped: {\n mode: 'required' | 'optional';\n field: string;\n } | null;\n fieldCount: number;\n methodCount: number;\n fields: RegistrySnapshotField[];\n methods: RegistrySnapshotMethod[];\n}\n\nexport interface RegistrySnapshotDiagnostic {\n severity: 'warn' | 'error';\n code: string;\n message: string;\n}\n\nexport interface RegistrySnapshotSummary {\n objectCount: number;\n packages: Array<{ name: string; objectCount: number }>;\n tables: string[];\n diagnosticCount: number;\n}\n\nexport interface RuntimeRegistrySnapshot {\n provenance: typeof BOOTED_PROVENANCE;\n generatedAt: string;\n summary: RegistrySnapshotSummary;\n objects: RegistrySnapshotObject[];\n diagnostics: RegistrySnapshotDiagnostic[];\n}\n\nexport interface RegistrySnapshotOptions {\n /** Root used to relativize source paths. Defaults to `process.cwd()`. */\n projectRoot?: string;\n /** Restrict `objects` to these simple or qualified names (summary stays global). */\n objects?: string[];\n /** Include per-object field and method detail (default `true`). */\n detail?: boolean;\n /** Clock for `generatedAt` (defaults to `new Date()`). */\n now?: Date;\n}\n\nfunction toStringOrNull(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nfunction relativeSourcePath(\n filePath: unknown,\n projectRoot: string,\n): string | null {\n if (typeof filePath !== 'string' || filePath.length === 0) return null;\n const rel = relative(projectRoot, filePath);\n // A path outside the project root (installed package, symlinked workspace,\n // or another drive on Windows — `relative` keeps those absolute) would\n // still leak the layout through `../..` walking; report only the basename.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {\n return basename(filePath) || null;\n }\n return rel.split(sep).join('/');\n}\n\nfunction toSnapshotField(\n name: string,\n field: RegisteredField,\n inherited: boolean,\n): RegistrySnapshotField {\n return {\n name,\n type: String(field.type ?? 'unknown'),\n required: field.required === true,\n related: toStringOrNull(field.related),\n transient: field.transient === true,\n inherited,\n sqlType: toStringOrNull(field.sqlType),\n };\n}\n\nfunction toSnapshotMethod(\n method: MethodDefinition,\n inherited: boolean,\n): RegistrySnapshotMethod {\n return {\n name: method.name,\n async: method.async === true,\n isStatic: method.isStatic === true,\n isPublic: method.isPublic === true,\n parameters: (method.parameters ?? []).map((parameter) => ({\n name: String(parameter.name),\n type: String(parameter.type ?? 'unknown'),\n optional: parameter.optional === true,\n })),\n returnType: String(method.returnType ?? 'unknown'),\n inherited,\n };\n}\n\nfunction toSnapshotObject(\n registered: RegisteredClass,\n projectRoot: string,\n detail: boolean,\n): RegistrySnapshotObject {\n const fields: RegistrySnapshotField[] = [];\n const methods: RegistrySnapshotMethod[] = [];\n if (detail) {\n for (const [name, field] of registered.inheritedFields ?? []) {\n fields.push(toSnapshotField(name, field, true));\n }\n for (const [name, field] of registered.fields) {\n fields.push(toSnapshotField(name, field, false));\n }\n for (const [, method] of registered.inheritedMethods ?? []) {\n methods.push(toSnapshotMethod(method, true));\n }\n for (const [, method] of registered.methods) {\n methods.push(toSnapshotMethod(method, false));\n }\n fields.sort((a, b) => a.name.localeCompare(b.name));\n methods.sort((a, b) => a.name.localeCompare(b.name));\n }\n return {\n name: registered.name,\n qualifiedName: toStringOrNull(registered.qualifiedName),\n packageName: toStringOrNull(registered.packageName),\n tableName: ObjectRegistry.getTableName(registered.name) ?? null,\n collection: toStringOrNull(registered.collection),\n extends: toStringOrNull(registered.extends),\n extendsTypeArg: toStringOrNull(registered.extendsTypeArg),\n inheritanceChain: [...(registered.inheritanceChain ?? [])],\n visibility: toStringOrNull(registered.visibility),\n sourceFile: relativeSourcePath(registered.sourceFilePath, projectRoot),\n tenantScoped: registered.tenantScopedConfig\n ? {\n mode: registered.tenantScopedConfig.mode,\n field: registered.tenantScopedConfig.field,\n }\n : null,\n fieldCount:\n registered.fields.size + (registered.inheritedFields?.size ?? 0),\n methodCount:\n registered.methods.size + (registered.inheritedMethods?.size ?? 0),\n fields,\n methods,\n };\n}\n\nfunction matchesFilter(\n registered: RegisteredClass,\n filter: Set<string> | null,\n): boolean {\n if (!filter) return true;\n return (\n filter.has(registered.name) ||\n (typeof registered.qualifiedName === 'string' &&\n filter.has(registered.qualifiedName))\n );\n}\n\n/**\n * Project the booted registry into a sanitized, JSON-serializable snapshot.\n *\n * Pure read of in-process registry state: no database, no project code, no\n * registry mutation. Objects are sorted by qualified name for stable output.\n */\nexport function snapshotRegistry(\n options: RegistrySnapshotOptions = {},\n): RuntimeRegistrySnapshot {\n const projectRoot = options.projectRoot ?? process.cwd();\n const detail = options.detail !== false;\n const filter =\n options.objects && options.objects.length > 0\n ? new Set(options.objects)\n : null;\n const all = [...ObjectRegistry.getAllClasses().values()].sort((a, b) =>\n String(a.qualifiedName ?? a.name).localeCompare(\n String(b.qualifiedName ?? b.name),\n ),\n );\n\n const packageCounts = new Map<string, number>();\n const tables = new Set<string>();\n for (const registered of all) {\n const pkg = toStringOrNull(registered.packageName) ?? '(unqualified)';\n packageCounts.set(pkg, (packageCounts.get(pkg) ?? 0) + 1);\n const table = ObjectRegistry.getTableName(registered.name);\n if (table) tables.add(table);\n }\n\n // `context` may carry absolute paths and is dropped. Messages such as\n // PACKAGE_MANIFEST_NOT_FOUND also interpolate paths, so every absolute\n // path token in the message text is reduced the same way source files are.\n const diagnostics: RegistrySnapshotDiagnostic[] =\n ObjectRegistry.getDiagnostics().map((diagnostic) => ({\n severity: diagnostic.severity,\n code: diagnostic.code,\n message: sanitizeMessagePaths(diagnostic.message, projectRoot),\n }));\n\n return {\n provenance: BOOTED_PROVENANCE,\n generatedAt: (options.now ?? new Date()).toISOString(),\n summary: {\n objectCount: all.length,\n packages: [...packageCounts.entries()]\n .map(([name, objectCount]) => ({ name, objectCount }))\n .sort((a, b) => a.name.localeCompare(b.name)),\n tables: [...tables].sort(),\n diagnosticCount: diagnostics.length,\n },\n objects: all\n .filter((registered) => matchesFilter(registered, filter))\n .map((registered) => toSnapshotObject(registered, projectRoot, detail)),\n diagnostics,\n };\n}\n\n/** POSIX or Windows absolute path tokens inside free text. */\nconst ABSOLUTE_PATH_TOKEN = /(?:[A-Za-z]:)?(?:[\\\\/][^\\s\\\\/'\"`(),;]+){2,}/g;\n\n/**\n * Reduce absolute paths embedded in a message to the same relative/basename\n * form {@link relativeSourcePath} produces, so diagnostic text cannot leak\n * filesystem layout the structured fields already hide.\n */\nexport function sanitizeMessagePaths(\n message: string,\n projectRoot: string,\n): string {\n return message.replace(ABSOLUTE_PATH_TOKEN, (token) => {\n if (isAbsolute(token)) {\n return relativeSourcePath(token, projectRoot) ?? token;\n }\n // A Windows path quoted on a POSIX host (or vice versa) is still a layout\n // leak; the platform-specific check catches it and keeps the basename.\n if (win32.isAbsolute(token)) {\n return win32.basename(token) || token;\n }\n return token;\n });\n}\n\n/**\n * Assert a value is plain JSON: no functions, classes, symbols, or cycles.\n * Exposed so callers and tests can prove a snapshot is wire-safe.\n */\nexport function assertPlainJson(value: unknown, path = '$'): void {\n const seen = new WeakSet<object>();\n const walk = (node: unknown, at: string): void => {\n if (node === null) return;\n const kind = typeof node;\n if (kind === 'string' || kind === 'number' || kind === 'boolean') return;\n if (kind !== 'object') {\n throw new Error(`non-JSON value (${kind}) at ${at}`);\n }\n const obj = node as object;\n if (seen.has(obj)) throw new Error(`cycle at ${at}`);\n seen.add(obj);\n if (Array.isArray(obj)) {\n for (const [index, item] of obj.entries()) {\n walk(item, `${at}[${index}]`);\n }\n return;\n }\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) {\n throw new Error(`non-plain object at ${at}`);\n }\n for (const [key, item] of Object.entries(obj)) walk(item, `${at}.${key}`);\n };\n walk(value, path);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/testing/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAyJ5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IAEjD;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,EACjC,EAAE,EAAE,iBAAiB,EACrB,iBAAiB,UAAQ,GACxB,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAwC3C;AA4DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAiO5B"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/testing/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AA0J5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IAEjD;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,EACjC,EAAE,EAAE,iBAAiB,EACrB,iBAAiB,UAAQ,GACxB,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAwC3C;AA4DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAgQ5B"}
@@ -3,9 +3,10 @@ import { detectEngine } from "../schema/ddl/index.js";
3
3
  import { getSystemTableDDL } from "../system/schema.js";
4
4
  import { ensurePostgresChangeFeedAppendFunction } from "../change-feed.js";
5
5
  import { ensureLegacySystemTableCompatibility } from "../system/compatibility.js";
6
+ import { recordRegistryDiagnostic } from "../registry/diagnostics.js";
6
7
  import { SchemaGenerator } from "../schema/generator.js";
7
- import { isCollectionRegistration, resolveCollectionItemClassName, resolveRelatedRegistration } from "../registry/collection-resolution.js";
8
8
  import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
9
+ import { isCollectionRegistration, resolveCollectionItemClassName, resolveRelatedRegistration } from "../registry/collection-resolution.js";
9
10
  import { ObjectRegistry } from "../registry.js";
10
11
  import { planForeignKeyCreation } from "../schema/foreign-key-planner.js";
11
12
  import { getDatabase } from "@happyvertical/sql";
@@ -156,7 +157,14 @@ async function getTestDatabase(options = {}) {
156
157
  if (includeSystemTables) await initializeSystemTables(db);
157
158
  const isImplicitClassList = classes === void 0;
158
159
  const classNames = resolveRequestedSchemaClassNames(classes ?? ObjectRegistry.getQualifiedClassNames());
159
- if (classNames.length === 0) return db;
160
+ if (classNames.length === 0) {
161
+ if (isImplicitClassList) {
162
+ const emptyRegistryMessage = "getTestDatabase() found zero registered classes in ObjectRegistry and created only system tables. If you expected model tables, the registry is likely empty in this process — for smrt-vitest consumers, confirm registration (smrtVitestPlugin() manifest loading, or your setupFiles) actually runs in the same process as your test code, or pass an explicit `classes` list to getTestDatabase() to opt out of this check.";
163
+ console.warn(`[getTestDatabase] ${emptyRegistryMessage}`);
164
+ recordRegistryDiagnostic("warn", "TEST_DATABASE_EMPTY_IMPLICIT_REGISTRY", emptyRegistryMessage);
165
+ }
166
+ return db;
167
+ }
160
168
  const schemaGenerator = new SchemaGenerator();
161
169
  const ddlEngine = resolveTestDatabaseDDLEngine(type, db, existingDb !== void 0);
162
170
  const schemas = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","names":[],"sources":["../../src/testing/database.ts"],"sourcesContent":["/**\n * Test database utilities for SMRT framework\n *\n * Provides `getTestDatabase()` which creates an in-memory database with all\n * registered SMRT object schemas. Uses the same schema generation logic as\n * the migration system to ensure consistency between test and production.\n *\n * @example\n * ```typescript\n * import { getTestDatabase } from '@happyvertical/smrt-core/testing';\n *\n * beforeEach(async () => {\n * const db = await getTestDatabase();\n * collection = await MyCollection.create({ db });\n * });\n * ```\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport { ensurePostgresChangeFeedAppendFunction } from '../change-feed.js';\nimport {\n type CollectionRegistrationLookup,\n isCollectionRegistration,\n resolveCollectionItemClassName,\n resolveRelatedRegistration,\n} from '../registry/collection-resolution.js';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport { ObjectRegistry } from '../registry.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport {\n schemaForeignKeys,\n schemaForeignKeysForEngine,\n} from '../schema/foreign-key-ddl.js';\nimport { planForeignKeyCreation } from '../schema/foreign-key-planner.js';\nimport { SchemaGenerator } from '../schema/generator.js';\nimport type { SchemaDefinition } from '../schema/types.js';\nimport { ensureLegacySystemTableCompatibility } from '../system/compatibility.js';\nimport { getSystemTableDDL } from '../system/schema.js';\n\ntype TestDatabaseConnectionOptions = Parameters<typeof getDatabase>[0] & {\n __smrtSkipVitestSchemaPreparation?: boolean;\n};\n\nfunction resolveSTIBaseRegistration(className: string, stiBaseName: string) {\n const registered = ObjectRegistry.getClass(className);\n\n if (stiBaseName.includes(':')) {\n return ObjectRegistry.getClass(stiBaseName);\n }\n\n if (registered?.packageName) {\n const samePackageBase = ObjectRegistry.getClassInPackage(\n registered.packageName,\n stiBaseName,\n );\n if (samePackageBase) {\n return samePackageBase;\n }\n }\n\n return ObjectRegistry.getClass(stiBaseName);\n}\n\nfunction resolveSTIBaseLookupName(\n className: string,\n stiBaseName: string,\n): string {\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n return stiBase?.qualifiedName || stiBase?.name || stiBaseName;\n}\n\nfunction isSTIChild(className: string): boolean {\n const stiBaseName = ObjectRegistry.getSTIBase(className);\n if (!stiBaseName) {\n return false;\n }\n\n const registered = ObjectRegistry.getClass(className);\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n\n if (registered && stiBase) {\n return registered !== stiBase;\n }\n\n return stiBaseName !== className;\n}\n\ntype RegisteredSchemaClass = NonNullable<\n ReturnType<typeof ObjectRegistry.getClass>\n>;\n\nconst collectionRegistrationLookup: CollectionRegistrationLookup = {\n findClass: (className) => ObjectRegistry.getClass(className),\n findClassInPackage: (packageName, className) =>\n ObjectRegistry.getClassInPackage(packageName, className),\n getInheritanceChain: (className) =>\n ObjectRegistry.getInheritanceChain(className),\n};\n\nfunction resolveCollectionSchemaClassName(\n className: string,\n registered: RegisteredSchemaClass,\n): string {\n const itemClassName = resolveCollectionItemClassName(\n className,\n registered,\n collectionRegistrationLookup,\n );\n if (itemClassName) {\n const itemRegistration = resolveRelatedRegistration(\n itemClassName,\n registered,\n collectionRegistrationLookup,\n );\n const itemLookupName =\n itemRegistration?.qualifiedName ||\n itemRegistration?.name ||\n itemClassName;\n const stiBase = ObjectRegistry.getSTIBase(itemLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(itemLookupName, stiBase)\n : itemLookupName;\n }\n\n const tableName =\n registered.schema?.tableName ||\n registered.config.tableName ||\n ObjectRegistry.getTableName(className);\n if (!tableName) {\n return className;\n }\n\n const collectionPackage = registered.packageName;\n\n for (const candidate of ObjectRegistry.getAllClasses().values()) {\n if (\n candidate === registered ||\n isCollectionRegistration(\n candidate.qualifiedName || candidate.name,\n candidate,\n collectionRegistrationLookup,\n )\n ) {\n continue;\n }\n\n const candidateTableName =\n candidate.schema?.tableName || candidate.config.tableName;\n if (candidateTableName !== tableName) {\n continue;\n }\n\n if (\n collectionPackage &&\n candidate.packageName &&\n candidate.packageName !== collectionPackage\n ) {\n continue;\n }\n\n const candidateLookupName = candidate.qualifiedName || candidate.name;\n const stiBase = ObjectRegistry.getSTIBase(candidateLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(candidateLookupName, stiBase)\n : candidateLookupName;\n }\n\n return className;\n}\n\n/**\n * Options for creating a test database\n */\nexport interface TestDatabaseOptions {\n /**\n * Database type (default: 'sqlite')\n * - 'sqlite': SQLite database\n * - 'json': JSON adapter (stores data as JSON files with DuckDB for querying)\n * - 'duckdb': Native DuckDB database\n * - 'postgres': PostgreSQL database (normally supplied through `db`)\n */\n type?: 'sqlite' | 'json' | 'duckdb' | 'postgres';\n\n /**\n * Database URL (default: ':memory:')\n * Use ':memory:' for in-memory databases (fastest for tests)\n * Or provide a file path for persistent test databases\n */\n url?: string;\n\n /**\n * Pre-existing database to initialize schemas in.\n * If provided, `type` and `url` are ignored.\n */\n db?: DatabaseInterface;\n\n /**\n * Specific classes to setup schemas for.\n * If not provided, sets up schemas for all registered classes.\n */\n classes?: string[];\n\n /**\n * Whether to include system tables (default: true)\n * System tables include _smrt_contexts, _smrt_migrations, etc.\n */\n includeSystemTables?: boolean;\n\n /**\n * Explicitly omit physical FK constraints for adapter/query tests whose\n * subject is unrelated to referential enforcement. Defaults to `false`.\n * This is required (and deliberately noisy at the call site) when a DuckDB\n * fixture uses generated `ON UPDATE CASCADE`, which DuckDB cannot enforce.\n */\n omitForeignKeyConstraints?: boolean;\n}\n\n/** Resolve the DDL dialect used when preparing an existing test database. */\nexport function resolveTestDatabaseDDLEngine(\n type: TestDatabaseOptions['type'],\n db: DatabaseInterface,\n inferFromDatabase = false,\n): 'sqlite' | 'json' | 'duckdb' | 'postgres' {\n // When this helper created the adapter, the requested type is authoritative.\n // Native DuckDB and JSON-on-DuckDB both expose exportTable(), so capability\n // inference must not override an explicit native DuckDB request.\n if (!inferFromDatabase) {\n return type ?? 'sqlite';\n }\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n exportTable?: unknown;\n inferSchemaFromJSON?: unknown;\n getTableLoadErrors?: unknown;\n };\n\n if (\n typeof configuredDb.inferSchemaFromJSON === 'function' ||\n typeof configuredDb.getTableLoadErrors === 'function'\n ) {\n return 'json';\n }\n\n if (typeof configuredDb.exportTable === 'function') {\n // The native DuckDB adapter exposes schema evolution capabilities that the\n // JSON adapter does not. Keep exportTable-only test doubles compatible with\n // the historical JSON structural marker.\n if (\n typeof configuredDb.getTableSchema === 'function' &&\n typeof configuredDb.alterTable?.addColumn === 'function'\n ) {\n return 'duckdb';\n }\n return 'json';\n }\n\n return detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n}\n\nfunction omitTestForeignKeyConstraints(\n schema: SchemaDefinition,\n): SchemaDefinition {\n const foreignKeyTargets = new Set(\n schemaForeignKeys(schema).map((foreignKey) => foreignKey.referencesTable),\n );\n return {\n ...schema,\n columns: Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => [\n name,\n column.foreignKey ? { ...column, foreignKey: undefined } : column,\n ]),\n ),\n foreignKeys: [],\n dependencies: schema.dependencies.filter(\n (dependency) => !foreignKeyTargets.has(dependency),\n ),\n };\n}\n\nfunction resolveRequestedSchemaClassName(className: string): string {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n return className;\n }\n\n if (\n !isCollectionRegistration(\n className,\n registered,\n collectionRegistrationLookup,\n )\n ) {\n const stiBase = ObjectRegistry.getSTIBase(className);\n return stiBase ? resolveSTIBaseLookupName(className, stiBase) : className;\n }\n\n return resolveCollectionSchemaClassName(className, registered);\n}\n\nfunction resolveRequestedSchemaClassNames(classNames: string[]): string[] {\n const resolved: string[] = [];\n const seen = new Set<string>();\n\n for (const className of classNames) {\n const schemaClassName = resolveRequestedSchemaClassName(className);\n if (seen.has(schemaClassName)) {\n continue;\n }\n\n seen.add(schemaClassName);\n resolved.push(schemaClassName);\n }\n\n return resolved;\n}\n\n/**\n * Creates an in-memory test database with schemas pre-created\n *\n * This utility is designed for testing. It creates an in-memory database\n * and initializes all registered SMRT object schemas using the **same\n * schema generation logic** as the production migration system.\n *\n * **Key features:**\n * - Uses `SchemaGenerator.generateSQL()` - the single source of truth for DDL\n * - Handles STI (Single Table Inheritance) correctly\n * - Creates system tables for framework functionality\n * - Safe for parallel test execution: each call creates an isolated\n * in-memory instance with its own embedded write-queue identity, so\n * unrelated `:memory:` databases never serialize writes against each\n * other (#2707). A `cache=shared` URL is the deliberate exception: it\n * asks SQLite/DuckDB to genuinely share the underlying database, so it\n * keeps sharing one write-queue identity too.\n *\n * @param options - Configuration options\n * @returns Promise resolving to configured DatabaseInterface\n *\n * @example\n * ```typescript\n * // Basic usage - all registered schemas\n * const db = await getTestDatabase();\n *\n * // Specific classes only\n * const db = await getTestDatabase({ classes: ['Council', 'Meeting'] });\n *\n * // JSON adapter instead of SQLite\n * const db = await getTestDatabase({ type: 'json' });\n *\n * // Native DuckDB adapter\n * const db = await getTestDatabase({ type: 'duckdb', url: ':memory:' });\n *\n * // File-based database for persistent tests\n * const db = await getTestDatabase({ type: 'sqlite', url: '/tmp/test.db' });\n *\n * // Initialize schemas in an existing database\n * const myDb = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * await getTestDatabase({ db: myDb });\n *\n * // Skip system tables (rare use case)\n * const db = await getTestDatabase({ includeSystemTables: false });\n * ```\n */\nexport async function getTestDatabase(\n options: TestDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const {\n type,\n url = ':memory:',\n db: existingDb,\n classes,\n includeSystemTables = true,\n omitForeignKeyConstraints = false,\n } = options;\n\n // Use existing database or create new one\n const db =\n existingDb ??\n (await getDatabase({\n type: type ?? 'sqlite',\n url,\n __smrtSkipVitestSchemaPreparation: true,\n } as TestDatabaseConnectionOptions));\n\n // Initialize system tables (same as production)\n if (includeSystemTables) {\n await initializeSystemTables(db);\n }\n\n // Get class names to setup. The framework-base filter below only applies\n // when the caller left `classes` implicit: an explicit list naming a\n // framework base class must still be honored (#2645).\n const isImplicitClassList = classes === undefined;\n const classNames = resolveRequestedSchemaClassNames(\n classes ?? ObjectRegistry.getQualifiedClassNames(),\n );\n\n // Skip if no classes registered\n if (classNames.length === 0) {\n return db;\n }\n\n // Use the same schema generation as production\n const schemaGenerator = new SchemaGenerator();\n const ddlEngine = resolveTestDatabaseDDLEngine(\n type,\n db,\n existingDb !== undefined,\n );\n\n // Collect every table before executing DDL so dependency ordering and cycle\n // handling are identical to production migration/schema paths (#2413).\n const schemas = new Map<\n string,\n { schema: SchemaDefinition; className: string }\n >();\n const authoritativeSchemas = ObjectRegistry.getAllSchemasAsDefinitions();\n\n for (const className of classNames) {\n // R11: the registration carries idType (native uuid vs text); read it\n // below when building runtimeSchemaConfig.\n const registered = ObjectRegistry.getClass(className);\n // Skip STI children - their schema is part of the base class table.\n // main (#1324): isSTIChild() compares RegisteredClass *identity* rather\n // than raw strings, so it stays correct under R5-canon (getSTIBase returns\n // qualified names) while also handling collection/override registrations.\n if (isSTIChild(className)) {\n continue;\n }\n\n // Framework abstract base classes (SmrtObject, SmrtClass, ...) are\n // scaffolding, not resources, and produce phantom smrt_objects/\n // smrt_classes/smrt_collections/smrt_hierarchicals/\n // smrt_polymorphic_associations tables when the default registry\n // snapshot happens to include them (#2645). Mirrors the production\n // `buildMergedTableSchemas()` filter in `registry/schema-builder.ts`, but\n // only for the implicit class list — an explicit `classes: [...]` caller\n // that names a framework base class is still honored.\n if (\n isImplicitClassList &&\n isFrameworkBaseClass(registered?.name, registered?.packageName)\n ) {\n continue;\n }\n\n const tableName = ObjectRegistry.getTableName(className);\n if (!tableName || schemas.has(tableName)) {\n continue;\n }\n\n const fields = await ObjectRegistry.getAllFields(className);\n const strategy = ObjectRegistry.getTableStrategy(className);\n // Mirrors `schema/utils.ts`: this bag is rebuilt by hand, so every `@smrt()`\n // option the generator reads has to be listed or tests silently diverge\n // from the production schema. `indexes` (#2357) was missing from both.\n const runtimeSchemaConfig = {\n conflictColumns: ObjectRegistry.getConflictColumns(className),\n idType: registered?.config.idType,\n indexes: registered?.config.indexes,\n registry: ObjectRegistry,\n };\n\n // Generate schema through the registry paths. Production (`manifest.json`\n // and therefore `smrt db:migrate`) uses the manifest paths\n // (`generateSTISchemaFromManifest` / `generateCTISchemaFromManifest`);\n // the two families are held to the same column and index sets by\n // `src/schema/schema-path-parity.test.ts` (#2359) — do not assume they\n // agree by construction.\n const schema =\n strategy === 'sti'\n ? await schemaGenerator.generateSTISchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n )\n : schemaGenerator.generateSchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n );\n\n // Manifest registrations can carry explicit FK actions that differ from\n // decorator defaults. The deterministic merged table schema preserves\n // those authoritative actions while still backfilling runtime-only fields.\n const authoritativeSchema = authoritativeSchemas[tableName];\n const effectiveColumns = authoritativeSchema\n ? Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => {\n const manifestColumn = authoritativeSchema.columns[name];\n return [\n name,\n manifestColumn\n ? { ...column, foreignKey: manifestColumn.foreignKey }\n : column,\n ];\n }),\n )\n : schema.columns;\n const effectiveForeignKeys = schemaForeignKeys({\n columns: effectiveColumns,\n foreignKeys: [],\n });\n const effectiveSchema: SchemaDefinition = {\n ...schema,\n columns: effectiveColumns,\n foreignKeys: effectiveForeignKeys,\n dependencies: effectiveForeignKeys\n .map((foreignKey) => foreignKey.referencesTable)\n .filter((dependency) => dependency !== tableName),\n };\n\n schemas.set(tableName, {\n schema: omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(effectiveSchema)\n : effectiveSchema,\n className,\n });\n }\n\n // Explicitly requested test classes still need their registered parents.\n // Add the authoritative dependency closure so a child-only request cannot\n // emit a physical reference to a table this helper never creates.\n const addDependencies = (schema: SchemaDefinition): void => {\n for (const foreignKey of schemaForeignKeysForEngine(schema, ddlEngine)) {\n if (schemas.has(foreignKey.referencesTable)) continue;\n const dependency = authoritativeSchemas[foreignKey.referencesTable];\n if (!dependency) continue;\n const effectiveDependency = omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(dependency)\n : dependency;\n schemas.set(dependency.tableName, {\n schema: effectiveDependency,\n className: dependency.tableName,\n });\n addDependencies(effectiveDependency);\n }\n };\n for (const { schema } of [...schemas.values()]) addDependencies(schema);\n\n const tablePlan = planForeignKeyCreation(\n [...schemas.values()].map(({ schema }) => schema),\n ddlEngine,\n );\n const ddlStrategy = (await import('../schema/ddl/index.js')).getDDLStrategy(\n ddlEngine,\n );\n\n for (const schema of tablePlan.schemas) {\n const className = schemas.get(schema.tableName)?.className ?? 'unknown';\n const ddl = schemaGenerator.generateSQL(schema, ddlEngine);\n\n try {\n await db.query(ddl);\n\n // Create indexes (use DDL strategy so jsonPath / where / etc. render)\n const indexStatements = ddlStrategy.generateIndexes(schema);\n for (const indexSQL of indexStatements) {\n try {\n await db.query(indexSQL);\n } catch (indexError) {\n // Log but don't fail on index creation errors\n // Some indexes may fail if columns don't exist (STI meta fields)\n console.warn(\n `[getTestDatabase] Warning: Failed to create index: ${indexError instanceof Error ? indexError.message : String(indexError)} (SQL: ${indexSQL})`,\n );\n }\n }\n } catch (error) {\n // Provide helpful error message for table creation failures\n throw new Error(\n `[getTestDatabase] Failed to create table '${schema.tableName}' for class '${className}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n for (const statement of tablePlan.deferredStatements) {\n try {\n await db.query(statement);\n } catch (error) {\n throw new Error(\n `[getTestDatabase] Failed to add deferred foreign-key constraint: ${error instanceof Error ? error.message : String(error)} (SQL: ${statement})`,\n { cause: error },\n );\n }\n }\n\n return db;\n}\n\n/**\n * Initialize SMRT system tables in a database\n *\n * System tables use _smrt_ prefix and store framework metadata.\n * All statements use `IF NOT EXISTS` for idempotency.\n *\n * @param db - Database interface to initialize\n */\nasync function initializeSystemTables(db: DatabaseInterface): Promise<void> {\n await ensureLegacySystemTableCompatibility(db);\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n };\n const engine = detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n\n // Split multi-statement SQL into individual statements\n const allStatements: string[] = [];\n for (const multiStatementSQL of getSystemTableDDL(engine)) {\n const statements = multiStatementSQL\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n allStatements.push(...statements);\n }\n\n // Use db.query() — system tables use CREATE TABLE/INDEX IF NOT EXISTS\n // which databases handle natively without per-column existence checks.\n for (const statement of allStatements) {\n await db.query(statement);\n }\n\n // PostgreSQL keeps the best-effort append boundary in a function that must\n // be executed whole rather than included in semicolon-split portable DDL.\n await ensurePostgresChangeFeedAppendFunction(db);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"database.js","names":[],"sources":["../../src/testing/database.ts"],"sourcesContent":["/**\n * Test database utilities for SMRT framework\n *\n * Provides `getTestDatabase()` which creates an in-memory database with all\n * registered SMRT object schemas. Uses the same schema generation logic as\n * the migration system to ensure consistency between test and production.\n *\n * @example\n * ```typescript\n * import { getTestDatabase } from '@happyvertical/smrt-core/testing';\n *\n * beforeEach(async () => {\n * const db = await getTestDatabase();\n * collection = await MyCollection.create({ db });\n * });\n * ```\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport { ensurePostgresChangeFeedAppendFunction } from '../change-feed.js';\nimport {\n type CollectionRegistrationLookup,\n isCollectionRegistration,\n resolveCollectionItemClassName,\n resolveRelatedRegistration,\n} from '../registry/collection-resolution.js';\nimport { recordRegistryDiagnostic } from '../registry/diagnostics.js';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport { ObjectRegistry } from '../registry.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport {\n schemaForeignKeys,\n schemaForeignKeysForEngine,\n} from '../schema/foreign-key-ddl.js';\nimport { planForeignKeyCreation } from '../schema/foreign-key-planner.js';\nimport { SchemaGenerator } from '../schema/generator.js';\nimport type { SchemaDefinition } from '../schema/types.js';\nimport { ensureLegacySystemTableCompatibility } from '../system/compatibility.js';\nimport { getSystemTableDDL } from '../system/schema.js';\n\ntype TestDatabaseConnectionOptions = Parameters<typeof getDatabase>[0] & {\n __smrtSkipVitestSchemaPreparation?: boolean;\n};\n\nfunction resolveSTIBaseRegistration(className: string, stiBaseName: string) {\n const registered = ObjectRegistry.getClass(className);\n\n if (stiBaseName.includes(':')) {\n return ObjectRegistry.getClass(stiBaseName);\n }\n\n if (registered?.packageName) {\n const samePackageBase = ObjectRegistry.getClassInPackage(\n registered.packageName,\n stiBaseName,\n );\n if (samePackageBase) {\n return samePackageBase;\n }\n }\n\n return ObjectRegistry.getClass(stiBaseName);\n}\n\nfunction resolveSTIBaseLookupName(\n className: string,\n stiBaseName: string,\n): string {\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n return stiBase?.qualifiedName || stiBase?.name || stiBaseName;\n}\n\nfunction isSTIChild(className: string): boolean {\n const stiBaseName = ObjectRegistry.getSTIBase(className);\n if (!stiBaseName) {\n return false;\n }\n\n const registered = ObjectRegistry.getClass(className);\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n\n if (registered && stiBase) {\n return registered !== stiBase;\n }\n\n return stiBaseName !== className;\n}\n\ntype RegisteredSchemaClass = NonNullable<\n ReturnType<typeof ObjectRegistry.getClass>\n>;\n\nconst collectionRegistrationLookup: CollectionRegistrationLookup = {\n findClass: (className) => ObjectRegistry.getClass(className),\n findClassInPackage: (packageName, className) =>\n ObjectRegistry.getClassInPackage(packageName, className),\n getInheritanceChain: (className) =>\n ObjectRegistry.getInheritanceChain(className),\n};\n\nfunction resolveCollectionSchemaClassName(\n className: string,\n registered: RegisteredSchemaClass,\n): string {\n const itemClassName = resolveCollectionItemClassName(\n className,\n registered,\n collectionRegistrationLookup,\n );\n if (itemClassName) {\n const itemRegistration = resolveRelatedRegistration(\n itemClassName,\n registered,\n collectionRegistrationLookup,\n );\n const itemLookupName =\n itemRegistration?.qualifiedName ||\n itemRegistration?.name ||\n itemClassName;\n const stiBase = ObjectRegistry.getSTIBase(itemLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(itemLookupName, stiBase)\n : itemLookupName;\n }\n\n const tableName =\n registered.schema?.tableName ||\n registered.config.tableName ||\n ObjectRegistry.getTableName(className);\n if (!tableName) {\n return className;\n }\n\n const collectionPackage = registered.packageName;\n\n for (const candidate of ObjectRegistry.getAllClasses().values()) {\n if (\n candidate === registered ||\n isCollectionRegistration(\n candidate.qualifiedName || candidate.name,\n candidate,\n collectionRegistrationLookup,\n )\n ) {\n continue;\n }\n\n const candidateTableName =\n candidate.schema?.tableName || candidate.config.tableName;\n if (candidateTableName !== tableName) {\n continue;\n }\n\n if (\n collectionPackage &&\n candidate.packageName &&\n candidate.packageName !== collectionPackage\n ) {\n continue;\n }\n\n const candidateLookupName = candidate.qualifiedName || candidate.name;\n const stiBase = ObjectRegistry.getSTIBase(candidateLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(candidateLookupName, stiBase)\n : candidateLookupName;\n }\n\n return className;\n}\n\n/**\n * Options for creating a test database\n */\nexport interface TestDatabaseOptions {\n /**\n * Database type (default: 'sqlite')\n * - 'sqlite': SQLite database\n * - 'json': JSON adapter (stores data as JSON files with DuckDB for querying)\n * - 'duckdb': Native DuckDB database\n * - 'postgres': PostgreSQL database (normally supplied through `db`)\n */\n type?: 'sqlite' | 'json' | 'duckdb' | 'postgres';\n\n /**\n * Database URL (default: ':memory:')\n * Use ':memory:' for in-memory databases (fastest for tests)\n * Or provide a file path for persistent test databases\n */\n url?: string;\n\n /**\n * Pre-existing database to initialize schemas in.\n * If provided, `type` and `url` are ignored.\n */\n db?: DatabaseInterface;\n\n /**\n * Specific classes to setup schemas for.\n * If not provided, sets up schemas for all registered classes.\n */\n classes?: string[];\n\n /**\n * Whether to include system tables (default: true)\n * System tables include _smrt_contexts, _smrt_migrations, etc.\n */\n includeSystemTables?: boolean;\n\n /**\n * Explicitly omit physical FK constraints for adapter/query tests whose\n * subject is unrelated to referential enforcement. Defaults to `false`.\n * This is required (and deliberately noisy at the call site) when a DuckDB\n * fixture uses generated `ON UPDATE CASCADE`, which DuckDB cannot enforce.\n */\n omitForeignKeyConstraints?: boolean;\n}\n\n/** Resolve the DDL dialect used when preparing an existing test database. */\nexport function resolveTestDatabaseDDLEngine(\n type: TestDatabaseOptions['type'],\n db: DatabaseInterface,\n inferFromDatabase = false,\n): 'sqlite' | 'json' | 'duckdb' | 'postgres' {\n // When this helper created the adapter, the requested type is authoritative.\n // Native DuckDB and JSON-on-DuckDB both expose exportTable(), so capability\n // inference must not override an explicit native DuckDB request.\n if (!inferFromDatabase) {\n return type ?? 'sqlite';\n }\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n exportTable?: unknown;\n inferSchemaFromJSON?: unknown;\n getTableLoadErrors?: unknown;\n };\n\n if (\n typeof configuredDb.inferSchemaFromJSON === 'function' ||\n typeof configuredDb.getTableLoadErrors === 'function'\n ) {\n return 'json';\n }\n\n if (typeof configuredDb.exportTable === 'function') {\n // The native DuckDB adapter exposes schema evolution capabilities that the\n // JSON adapter does not. Keep exportTable-only test doubles compatible with\n // the historical JSON structural marker.\n if (\n typeof configuredDb.getTableSchema === 'function' &&\n typeof configuredDb.alterTable?.addColumn === 'function'\n ) {\n return 'duckdb';\n }\n return 'json';\n }\n\n return detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n}\n\nfunction omitTestForeignKeyConstraints(\n schema: SchemaDefinition,\n): SchemaDefinition {\n const foreignKeyTargets = new Set(\n schemaForeignKeys(schema).map((foreignKey) => foreignKey.referencesTable),\n );\n return {\n ...schema,\n columns: Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => [\n name,\n column.foreignKey ? { ...column, foreignKey: undefined } : column,\n ]),\n ),\n foreignKeys: [],\n dependencies: schema.dependencies.filter(\n (dependency) => !foreignKeyTargets.has(dependency),\n ),\n };\n}\n\nfunction resolveRequestedSchemaClassName(className: string): string {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n return className;\n }\n\n if (\n !isCollectionRegistration(\n className,\n registered,\n collectionRegistrationLookup,\n )\n ) {\n const stiBase = ObjectRegistry.getSTIBase(className);\n return stiBase ? resolveSTIBaseLookupName(className, stiBase) : className;\n }\n\n return resolveCollectionSchemaClassName(className, registered);\n}\n\nfunction resolveRequestedSchemaClassNames(classNames: string[]): string[] {\n const resolved: string[] = [];\n const seen = new Set<string>();\n\n for (const className of classNames) {\n const schemaClassName = resolveRequestedSchemaClassName(className);\n if (seen.has(schemaClassName)) {\n continue;\n }\n\n seen.add(schemaClassName);\n resolved.push(schemaClassName);\n }\n\n return resolved;\n}\n\n/**\n * Creates an in-memory test database with schemas pre-created\n *\n * This utility is designed for testing. It creates an in-memory database\n * and initializes all registered SMRT object schemas using the **same\n * schema generation logic** as the production migration system.\n *\n * **Key features:**\n * - Uses `SchemaGenerator.generateSQL()` - the single source of truth for DDL\n * - Handles STI (Single Table Inheritance) correctly\n * - Creates system tables for framework functionality\n * - Safe for parallel test execution: each call creates an isolated\n * in-memory instance with its own embedded write-queue identity, so\n * unrelated `:memory:` databases never serialize writes against each\n * other (#2707). A `cache=shared` URL is the deliberate exception: it\n * asks SQLite/DuckDB to genuinely share the underlying database, so it\n * keeps sharing one write-queue identity too.\n *\n * @param options - Configuration options\n * @returns Promise resolving to configured DatabaseInterface\n *\n * @example\n * ```typescript\n * // Basic usage - all registered schemas\n * const db = await getTestDatabase();\n *\n * // Specific classes only\n * const db = await getTestDatabase({ classes: ['Council', 'Meeting'] });\n *\n * // JSON adapter instead of SQLite\n * const db = await getTestDatabase({ type: 'json' });\n *\n * // Native DuckDB adapter\n * const db = await getTestDatabase({ type: 'duckdb', url: ':memory:' });\n *\n * // File-based database for persistent tests\n * const db = await getTestDatabase({ type: 'sqlite', url: '/tmp/test.db' });\n *\n * // Initialize schemas in an existing database\n * const myDb = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * await getTestDatabase({ db: myDb });\n *\n * // Skip system tables (rare use case)\n * const db = await getTestDatabase({ includeSystemTables: false });\n * ```\n */\nexport async function getTestDatabase(\n options: TestDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const {\n type,\n url = ':memory:',\n db: existingDb,\n classes,\n includeSystemTables = true,\n omitForeignKeyConstraints = false,\n } = options;\n\n // Use existing database or create new one\n const db =\n existingDb ??\n (await getDatabase({\n type: type ?? 'sqlite',\n url,\n __smrtSkipVitestSchemaPreparation: true,\n } as TestDatabaseConnectionOptions));\n\n // Initialize system tables (same as production)\n if (includeSystemTables) {\n await initializeSystemTables(db);\n }\n\n // Get class names to setup. The framework-base filter below only applies\n // when the caller left `classes` implicit: an explicit list naming a\n // framework base class must still be honored (#2645).\n const isImplicitClassList = classes === undefined;\n const classNames = resolveRequestedSchemaClassNames(\n classes ?? ObjectRegistry.getQualifiedClassNames(),\n );\n\n // Skip if no classes registered. When the caller left `classes` implicit,\n // an empty registry usually means registration never reached this\n // process/module instance of ObjectRegistry (e.g. a Vitest plugin hook\n // registering manifests in a different worker/process than the test code\n // — #2750) rather than a deliberate \"system tables only\" setup. Warn (never\n // throw by default) so that failure surfaces here instead of as a much\n // later, harder-to-diagnose \"no such table\" error. An explicit empty\n // `classes: []` array is a deliberate choice and stays silent.\n if (classNames.length === 0) {\n if (isImplicitClassList) {\n const emptyRegistryMessage =\n 'getTestDatabase() found zero registered classes in ObjectRegistry and ' +\n 'created only system tables. If you expected model tables, the ' +\n 'registry is likely empty in this process — for smrt-vitest ' +\n 'consumers, confirm registration (smrtVitestPlugin() manifest ' +\n 'loading, or your setupFiles) actually runs in the same process ' +\n 'as your test code, or pass an explicit `classes` list to ' +\n 'getTestDatabase() to opt out of this check.';\n // recordRegistryDiagnostic('warn', ...) only buffers -- nothing reads\n // that buffer automatically (only strict-mode 'error' severity throws;\n // printing the buffer requires an explicit flushRegistryDiagnostics()\n // call this package never makes). Pair it with an immediate\n // console.warn so the failure is actually visible at the point of the\n // problem, which is the entire point of this diagnostic (#2750) --\n // the buffered record stays available for programmatic inspection via\n // getRegistryDiagnostics().\n console.warn(`[getTestDatabase] ${emptyRegistryMessage}`);\n recordRegistryDiagnostic(\n 'warn',\n 'TEST_DATABASE_EMPTY_IMPLICIT_REGISTRY',\n emptyRegistryMessage,\n );\n }\n return db;\n }\n\n // Use the same schema generation as production\n const schemaGenerator = new SchemaGenerator();\n const ddlEngine = resolveTestDatabaseDDLEngine(\n type,\n db,\n existingDb !== undefined,\n );\n\n // Collect every table before executing DDL so dependency ordering and cycle\n // handling are identical to production migration/schema paths (#2413).\n const schemas = new Map<\n string,\n { schema: SchemaDefinition; className: string }\n >();\n const authoritativeSchemas = ObjectRegistry.getAllSchemasAsDefinitions();\n\n for (const className of classNames) {\n // R11: the registration carries idType (native uuid vs text); read it\n // below when building runtimeSchemaConfig.\n const registered = ObjectRegistry.getClass(className);\n // Skip STI children - their schema is part of the base class table.\n // main (#1324): isSTIChild() compares RegisteredClass *identity* rather\n // than raw strings, so it stays correct under R5-canon (getSTIBase returns\n // qualified names) while also handling collection/override registrations.\n if (isSTIChild(className)) {\n continue;\n }\n\n // Framework abstract base classes (SmrtObject, SmrtClass, ...) are\n // scaffolding, not resources, and produce phantom smrt_objects/\n // smrt_classes/smrt_collections/smrt_hierarchicals/\n // smrt_polymorphic_associations tables when the default registry\n // snapshot happens to include them (#2645). Mirrors the production\n // `buildMergedTableSchemas()` filter in `registry/schema-builder.ts`, but\n // only for the implicit class list — an explicit `classes: [...]` caller\n // that names a framework base class is still honored.\n if (\n isImplicitClassList &&\n isFrameworkBaseClass(registered?.name, registered?.packageName)\n ) {\n continue;\n }\n\n const tableName = ObjectRegistry.getTableName(className);\n if (!tableName || schemas.has(tableName)) {\n continue;\n }\n\n const fields = await ObjectRegistry.getAllFields(className);\n const strategy = ObjectRegistry.getTableStrategy(className);\n // Mirrors `schema/utils.ts`: this bag is rebuilt by hand, so every `@smrt()`\n // option the generator reads has to be listed or tests silently diverge\n // from the production schema. `indexes` (#2357) was missing from both.\n const runtimeSchemaConfig = {\n conflictColumns: ObjectRegistry.getConflictColumns(className),\n idType: registered?.config.idType,\n indexes: registered?.config.indexes,\n registry: ObjectRegistry,\n };\n\n // Generate schema through the registry paths. Production (`manifest.json`\n // and therefore `smrt db:migrate`) uses the manifest paths\n // (`generateSTISchemaFromManifest` / `generateCTISchemaFromManifest`);\n // the two families are held to the same column and index sets by\n // `src/schema/schema-path-parity.test.ts` (#2359) — do not assume they\n // agree by construction.\n const schema =\n strategy === 'sti'\n ? await schemaGenerator.generateSTISchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n )\n : schemaGenerator.generateSchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n );\n\n // Manifest registrations can carry explicit FK actions that differ from\n // decorator defaults. The deterministic merged table schema preserves\n // those authoritative actions while still backfilling runtime-only fields.\n const authoritativeSchema = authoritativeSchemas[tableName];\n const effectiveColumns = authoritativeSchema\n ? Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => {\n const manifestColumn = authoritativeSchema.columns[name];\n return [\n name,\n manifestColumn\n ? { ...column, foreignKey: manifestColumn.foreignKey }\n : column,\n ];\n }),\n )\n : schema.columns;\n const effectiveForeignKeys = schemaForeignKeys({\n columns: effectiveColumns,\n foreignKeys: [],\n });\n const effectiveSchema: SchemaDefinition = {\n ...schema,\n columns: effectiveColumns,\n foreignKeys: effectiveForeignKeys,\n dependencies: effectiveForeignKeys\n .map((foreignKey) => foreignKey.referencesTable)\n .filter((dependency) => dependency !== tableName),\n };\n\n schemas.set(tableName, {\n schema: omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(effectiveSchema)\n : effectiveSchema,\n className,\n });\n }\n\n // Explicitly requested test classes still need their registered parents.\n // Add the authoritative dependency closure so a child-only request cannot\n // emit a physical reference to a table this helper never creates.\n const addDependencies = (schema: SchemaDefinition): void => {\n for (const foreignKey of schemaForeignKeysForEngine(schema, ddlEngine)) {\n if (schemas.has(foreignKey.referencesTable)) continue;\n const dependency = authoritativeSchemas[foreignKey.referencesTable];\n if (!dependency) continue;\n const effectiveDependency = omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(dependency)\n : dependency;\n schemas.set(dependency.tableName, {\n schema: effectiveDependency,\n className: dependency.tableName,\n });\n addDependencies(effectiveDependency);\n }\n };\n for (const { schema } of [...schemas.values()]) addDependencies(schema);\n\n const tablePlan = planForeignKeyCreation(\n [...schemas.values()].map(({ schema }) => schema),\n ddlEngine,\n );\n const ddlStrategy = (await import('../schema/ddl/index.js')).getDDLStrategy(\n ddlEngine,\n );\n\n for (const schema of tablePlan.schemas) {\n const className = schemas.get(schema.tableName)?.className ?? 'unknown';\n const ddl = schemaGenerator.generateSQL(schema, ddlEngine);\n\n try {\n await db.query(ddl);\n\n // Create indexes (use DDL strategy so jsonPath / where / etc. render)\n const indexStatements = ddlStrategy.generateIndexes(schema);\n for (const indexSQL of indexStatements) {\n try {\n await db.query(indexSQL);\n } catch (indexError) {\n // Log but don't fail on index creation errors\n // Some indexes may fail if columns don't exist (STI meta fields)\n console.warn(\n `[getTestDatabase] Warning: Failed to create index: ${indexError instanceof Error ? indexError.message : String(indexError)} (SQL: ${indexSQL})`,\n );\n }\n }\n } catch (error) {\n // Provide helpful error message for table creation failures\n throw new Error(\n `[getTestDatabase] Failed to create table '${schema.tableName}' for class '${className}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n for (const statement of tablePlan.deferredStatements) {\n try {\n await db.query(statement);\n } catch (error) {\n throw new Error(\n `[getTestDatabase] Failed to add deferred foreign-key constraint: ${error instanceof Error ? error.message : String(error)} (SQL: ${statement})`,\n { cause: error },\n );\n }\n }\n\n return db;\n}\n\n/**\n * Initialize SMRT system tables in a database\n *\n * System tables use _smrt_ prefix and store framework metadata.\n * All statements use `IF NOT EXISTS` for idempotency.\n *\n * @param db - Database interface to initialize\n */\nasync function initializeSystemTables(db: DatabaseInterface): Promise<void> {\n await ensureLegacySystemTableCompatibility(db);\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n };\n const engine = detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n\n // Split multi-statement SQL into individual statements\n const allStatements: string[] = [];\n for (const multiStatementSQL of getSystemTableDDL(engine)) {\n const statements = multiStatementSQL\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n allStatements.push(...statements);\n }\n\n // Use db.query() — system tables use CREATE TABLE/INDEX IF NOT EXISTS\n // which databases handle natively without per-column existence checks.\n for (const statement of allStatements) {\n await db.query(statement);\n }\n\n // PostgreSQL keeps the best-effort append boundary in a function that must\n // be executed whole rather than included in semicolon-split portable DDL.\n await ensurePostgresChangeFeedAppendFunction(db);\n}\n"],"mappings":"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"}
@@ -85,5 +85,21 @@ export interface ScannerModule {
85
85
  exclude: string[];
86
86
  followSymbolicLinks?: boolean;
87
87
  }): Promise<string[]>;
88
+ /**
89
+ * Report an emitted intent whose derived WebMCP tool name a generated model
90
+ * tool or a fixed UI tool also claims (#2725).
91
+ *
92
+ * The scanner owns the rule because it owns the tool-name derivation; core
93
+ * owns the INPUT because only core resolves the exposure policy. Names are
94
+ * passed rather than re-derived, so the scanner never becomes a second place
95
+ * that decides what a generated tool is called.
96
+ */
97
+ checkAgentSurfaceToolNames(surface: ScannerAgentSurface, options: {
98
+ generatedToolNames?: ReadonlyArray<{
99
+ name: string;
100
+ declaredBy?: string;
101
+ }>;
102
+ uiToolPrefixes?: readonly string[];
103
+ }): ScannerAgentSurface['diagnostics'];
88
104
  }
89
105
  //# sourceMappingURL=scanner-module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scanner-module.d.ts","sourceRoot":"","sources":["../../src/utils/scanner-module.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scanner-module.d.ts","sourceRoot":"","sources":["../../src/utils/scanner-module.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { DomainKnowledgeAgentSurface, DomainKnowledgeConfig } from '@happyvertic
2
2
  import { Plugin } from 'vite';
3
3
  import { SmrtGenerationSnapshotOptions } from '../generation-snapshot.js';
4
4
  import { SmartObjectManifest } from '../scanner/types.js';
5
- import { ScannerAgentSurface } from '../utils/scanner-module.js';
5
+ import { ScannerAgentSurface, ScannerModule } from '../utils/scanner-module.js';
6
6
  export { loadVerifiedSmrtGenerationSnapshot, type SerializeSmrtGenerationSnapshotOptions, type SmrtGenerationSnapshotArtifact, type SmrtGenerationSnapshotOptions, type SmrtGenerationSnapshotView, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot, } from '../generation-snapshot.js';
7
7
  export type { CliApiCoherenceViolation, SvelteKitOptions, } from './sveltekit-generator.js';
8
8
  export { findCliApiCoherenceViolations, generateSvelteKitRoutes, methodNameToKebab, resolveApiActionRouteConfig, resolveApiActionSet, validateCliIncludeAgainstApi, } from './sveltekit-generator.js';
@@ -112,6 +112,39 @@ export interface SmrtPluginOptions {
112
112
  * scan path.
113
113
  */
114
114
  export declare function toKnowledgeAgentSurface(surface: ScannerAgentSurface | undefined): DomainKnowledgeAgentSurface | undefined;
115
+ /**
116
+ * Compare the emitted intents against the tool names this build will really
117
+ * register, and project the result into the knowledge-artifact shape (#2725).
118
+ *
119
+ * The rule lives in the scanner, which owns the tool-name derivation. The INPUT
120
+ * lives here, because only core resolves the exposure policy:
121
+ * {@link buildWebMcpToolDefinitions} is the function that emits the runtime
122
+ * `webMcpToolDefinitions`, so its output is the set that actually registers.
123
+ * Handing the scanner every verb a class COULD expose would report collisions
124
+ * with tools nobody mounts.
125
+ *
126
+ * The check is passed in rather than imported: core reaches the scanner through
127
+ * {@link importScanner}, which resolves src or dist depending on how core itself
128
+ * was built, and a static import here would defeat that.
129
+ *
130
+ * `declaredBy` is `Class.action` — the same identity `smrt doctor` prints beside
131
+ * a generated model tool — so a reader can find the other side of the collision
132
+ * without cross-referencing anything.
133
+ */
134
+ export declare function collectAgentSurfaceToolNameCollisions(input: {
135
+ surface: ScannerAgentSurface;
136
+ manifest: SmartObjectManifest;
137
+ checkAgentSurfaceToolNames: ScannerModule['checkAgentSurfaceToolNames'];
138
+ kebabRoutes?: boolean;
139
+ /**
140
+ * Prefixes the fixed UI tools mount under. Nothing in a build knows this —
141
+ * `ui.prefix` is a runtime `<Provider webmcp={{ ui: { prefix } }}>` prop, and
142
+ * the scanner's default of `smrt_ui_` is correct for every app that leaves it
143
+ * alone. Threaded anyway so the seam is complete: the day a build-time source
144
+ * exists, only this call site changes.
145
+ */
146
+ uiToolPrefixes?: readonly string[];
147
+ }): DomainKnowledgeAgentSurface['diagnostics'];
115
148
  export declare function isRegisterShimModuleId(id: string): boolean;
116
149
  /**
117
150
  * Generate the replacement `__smrt-register__` module with the build-time
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"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"}