@happyvertical/smrt-core 0.51.24 → 0.51.28

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 (57) hide show
  1. package/AGENTS.md +17 -0
  2. package/agents/schema-paths.md +7 -0
  3. package/dist/index.js +1 -1
  4. package/dist/manifest/manifest-loader.d.ts +12 -0
  5. package/dist/manifest/manifest-loader.d.ts.map +1 -1
  6. package/dist/manifest/manifest-loader.js +54 -10
  7. package/dist/manifest/manifest-loader.js.map +1 -1
  8. package/dist/manifest/static-manifest.js +1 -1
  9. package/dist/manifest/static-manifest.js.map +1 -1
  10. package/dist/manifest/store.d.ts.map +1 -1
  11. package/dist/manifest/store.js +7 -2
  12. package/dist/manifest/store.js.map +1 -1
  13. package/dist/manifest.json +1 -1
  14. package/dist/migrations/differ.d.ts +6 -3
  15. package/dist/migrations/differ.d.ts.map +1 -1
  16. package/dist/migrations/differ.js +11 -5
  17. package/dist/migrations/differ.js.map +1 -1
  18. package/dist/object.d.ts.map +1 -1
  19. package/dist/object.js +26 -26
  20. package/dist/object.js.map +1 -1
  21. package/dist/registry/__tests__/helpers/consumer-workspace.d.ts +42 -0
  22. package/dist/registry/__tests__/helpers/consumer-workspace.d.ts.map +1 -0
  23. package/dist/registry/class-registration.d.ts +15 -0
  24. package/dist/registry/class-registration.d.ts.map +1 -1
  25. package/dist/registry/class-registration.js +171 -21
  26. package/dist/registry/class-registration.js.map +1 -1
  27. package/dist/registry/collection-resolution.d.ts +8 -2
  28. package/dist/registry/collection-resolution.d.ts.map +1 -1
  29. package/dist/registry/collection-resolution.js +12 -2
  30. package/dist/registry/collection-resolution.js.map +1 -1
  31. package/dist/registry/collision-policy.d.ts +10 -0
  32. package/dist/registry/collision-policy.d.ts.map +1 -1
  33. package/dist/registry/collision-policy.js +7 -1
  34. package/dist/registry/collision-policy.js.map +1 -1
  35. package/dist/registry/inheritance-resolver.js +1 -1
  36. package/dist/registry/name-resolver.d.ts.map +1 -1
  37. package/dist/registry/name-resolver.js +11 -0
  38. package/dist/registry/name-resolver.js.map +1 -1
  39. package/dist/registry/schema-builder.d.ts.map +1 -1
  40. package/dist/registry/schema-builder.js +71 -31
  41. package/dist/registry/schema-builder.js.map +1 -1
  42. package/dist/registry/shared-state.d.ts.map +1 -1
  43. package/dist/registry/shared-state.js +3 -4
  44. package/dist/registry/shared-state.js.map +1 -1
  45. package/dist/registry/types.d.ts +3 -1
  46. package/dist/registry/types.d.ts.map +1 -1
  47. package/dist/registry.d.ts.map +1 -1
  48. package/dist/registry.js +25 -8
  49. package/dist/registry.js.map +1 -1
  50. package/dist/scanner/manifest-generator.js +1 -1
  51. package/dist/smrt-knowledge.json +7 -7
  52. package/dist/testing/database.js +1 -1
  53. package/dist/utils/stack-frames.d.ts +26 -0
  54. package/dist/utils/stack-frames.d.ts.map +1 -1
  55. package/dist/utils/stack-frames.js +79 -2
  56. package/dist/utils/stack-frames.js.map +1 -1
  57. package/package.json +4 -4
@@ -0,0 +1,42 @@
1
+ export interface ConsumerWorkspace {
2
+ root: string;
3
+ path: (...segments: string[]) => string;
4
+ write: (relativePath: string, content: string) => string;
5
+ writePackage: (relativeDir: string, name: string) => void;
6
+ /** A fixture module declaring one class via `define({ smrt, SmrtObject })`. */
7
+ writeModel: (relativePath: string, className: string, config?: Record<string, unknown>) => string;
8
+ dispose: () => void;
9
+ }
10
+ export declare function createConsumerWorkspace(prefix: string): ConsumerWorkspace;
11
+ /** A manifest entry as an app's scanner writes it. */
12
+ export declare function manifestEntry(options: {
13
+ className: string;
14
+ packageName: string;
15
+ filePath: string;
16
+ tableName: string;
17
+ fields: Record<string, {
18
+ type: string;
19
+ }>;
20
+ }): {
21
+ name: string;
22
+ className: string;
23
+ qualifiedName: string;
24
+ packageName: string;
25
+ filePath: string;
26
+ extends: string;
27
+ fields: Record<string, {
28
+ type: string;
29
+ }>;
30
+ methods: {};
31
+ decoratorConfig: {
32
+ tableName: string;
33
+ };
34
+ schema: {
35
+ tableName: string;
36
+ ddl: string;
37
+ columns: {};
38
+ indexes: never[];
39
+ version: string;
40
+ };
41
+ };
42
+ //# sourceMappingURL=consumer-workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumer-workspace.d.ts","sourceRoot":"","sources":["../../../../src/registry/__tests__/helpers/consumer-workspace.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IACxC,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACzD,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,+EAA+E;IAC/E,UAAU,EAAE,CACV,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAiCzE;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1C;;;;;;;;cADgC,MAAM;;;;;;;;;;;;;EAqBtC"}
@@ -1,6 +1,21 @@
1
1
  import { SmrtObject } from '../object';
2
2
  import { SmartObjectDefinition } from '../scanner/types.js';
3
3
  import { RegisteredClass, RegisteredField, SmartObjectConfig } from './types.js';
4
+ /**
5
+ * Shared bundled-context detector. Source files in these output
6
+ * directories come from a bundler (Vite library mode, webpack, Next.js,
7
+ * Nuxt, svelte-kit) that can duplicate module code across chunks.
8
+ */
9
+ export declare function isBundledOutputPath(sourceFile: string | undefined): boolean;
10
+ /**
11
+ * Whether two source paths name the same file. A stack frame reports an
12
+ * absolute path; a manifest's `filePath` is absolute or relative to the
13
+ * project or workspace root it was scanned from (anytown scans
14
+ * `packages/cloud-network/src/models/*.ts` into the dashboard's manifest,
15
+ * #3110), so a relative path is resolved against those roots, never matched by
16
+ * suffix alone.
17
+ */
18
+ export declare function isSameSourcePath(left: string | undefined, right: string | undefined): boolean;
4
19
  /**
5
20
  * Register (or re-register) a class. Every registration can add, replace or
6
21
  * mutate a registered class in place, so it invalidates the generation-keyed
@@ -1 +1 @@
1
- {"version":3,"file":"class-registration.d.ts","sourceRoot":"","sources":["../../src/registry/class-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAcH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAKV,qBAAqB,EAGtB,MAAM,qBAAqB,CAAC;AAyC7B,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EAElB,MAAM,YAAY,CAAC;AA2gBpB;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,OAAO,UAAU,EACvB,MAAM,GAAE,iBAAsB,GAC7B,IAAI,CAON;AA+4BD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAMlB,qBAAqB,EAAE,WAAW,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC,GAC3E,IAAI,CAeN;AA+BD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EACpC,kBAAkB,EAAE,eAAe,CAAC,oBAAoB,CAAC,GAAG,SAAS,GACpE,IAAI,CAyCN;AAoHD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,eAAe,EACzB,gBAAgB,CAAC,EAAE;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,IAAI,CA4CN;AA2KD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,qBAAqB,EAChC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,IAAI,CAQN"}
1
+ {"version":3,"file":"class-registration.d.ts","sourceRoot":"","sources":["../../src/registry/class-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuBH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAKV,qBAAqB,EAGtB,MAAM,qBAAqB,CAAC;AA2C7B,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAkDpB;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAS3E;AA4BD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAeT;AA+mBD;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,OAAO,UAAU,EACvB,MAAM,GAAE,iBAAsB,GAC7B,IAAI,CAON;AA8gCD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAMlB,qBAAqB,EAAE,WAAW,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC,GAC3E,IAAI,CAeN;AA+BD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EACpC,kBAAkB,EAAE,eAAe,CAAC,oBAAoB,CAAC,GAAG,SAAS,GACpE,IAAI,CAyCN;AAoHD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,eAAe,EACzB,gBAAgB,CAAC,EAAE;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,IAAI,CA4CN;AAkLD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,qBAAqB,EAChC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,IAAI,CAQN"}
@@ -2,12 +2,12 @@ import { ConfigurationError } from "../errors.js";
2
2
  import { toSnakeCase } from "../utils/naming.js";
3
3
  import { declareChangeFeedSensitiveTable, setChangeFeedSensitiveClassResolver } from "../change-feed-sensitivity.js";
4
4
  import { bumpRegistryGeneration } from "./generation.js";
5
- import { createQualifiedName, isQualifiedName } from "../utils/qualified-names.js";
6
- import { cloneManifestSchemaColumns } from "../manifest/store.js";
5
+ import { createQualifiedName, isQualifiedName, parseQualifiedName } from "../utils/qualified-names.js";
7
6
  import { getClasses, getCollections, getConstructorFieldDecorators, getConstructorIndex, getConstructorTenantScopedDeclarations, getInheritanceCache, getLegacyFieldDecorators, getSourceFileFromStack, getStiSiblingsLoaded, verboseLog } from "./shared-state.js";
8
- import { findClass, getCanonicalClassName, hasClassCaseInsensitive, qualifyExtendsName } from "./name-resolver.js";
7
+ import { cloneManifestSchemaColumns, findWorkspaceRootSync, getLocalTestManifestCache, getManifestCache, getNodeBuiltins, getStaticManifestCache, getTestManifestCache } from "../manifest/store.js";
8
+ import { discoverManifestSync, discoverSTISiblingsSync, getPackageName, lookupInManifest, readProjectManifestSync } from "../manifest/manifest-loader.js";
9
+ import { findClass, findClassesByName, getCanonicalClassName, hasClassCaseInsensitive, qualifyExtendsName } from "./name-resolver.js";
9
10
  import { buildInheritanceChain } from "./inheritance-resolver.js";
10
- import { discoverManifestSync, discoverSTISiblingsSync, getPackageName, lookupInManifest } from "../manifest/manifest-loader.js";
11
11
  import { compileValidators } from "./validator.js";
12
12
  import { tableNameFromClass } from "../utils.js";
13
13
  import { SmrtObject } from "../object.js";
@@ -44,6 +44,25 @@ function pluralizeCollection(className) {
44
44
  return `${lower}s`;
45
45
  }
46
46
  /**
47
+ * The collection an STI subtype shares with its STI base, for a class that
48
+ * registers with no manifest `collection` (#3125). The manifest generator
49
+ * gives every STI subtype its base's collection (`findSTIBase`: the oldest
50
+ * ancestor declaring `tableStrategy: 'sti'`); a subtype decorated before its
51
+ * manifest loads — a consumer's bundled model chunk — must resolve the same
52
+ * one, or permission slugs and route segments derived from it name a
53
+ * collection nothing catalogues. Ancestors resolve by constructor identity, so
54
+ * a same-named class in another package cannot stand in for the base.
55
+ */
56
+ function inheritedStiCollection(ctor) {
57
+ let base;
58
+ for (let parent = Object.getPrototypeOf(ctor); parent && parent !== Function.prototype; parent = Object.getPrototypeOf(parent)) {
59
+ const key = getConstructorIndex().get(parent);
60
+ const registered = key ? getClasses().get(key) : void 0;
61
+ if (registered?.config?.tableStrategy === "sti") base = registered;
62
+ }
63
+ return base?.collection;
64
+ }
65
+ /**
47
66
  * Shared bundled-context detector. Source files in these output
48
67
  * directories come from a bundler (Vite library mode, webpack, Next.js,
49
68
  * Nuxt, svelte-kit) that can duplicate module code across chunks.
@@ -52,12 +71,100 @@ function isBundledOutputPath(sourceFile) {
52
71
  if (!sourceFile) return false;
53
72
  return sourceFile.includes(".svelte-kit/output/") || sourceFile.includes("/dist/") || sourceFile.includes("/build/") || sourceFile.includes(".next/") || sourceFile.includes(".nuxt/");
54
73
  }
74
+ function normalizeSourcePath(path) {
75
+ return path.replace(/^file:\/\//, "").replace(/\\/g, "/").replace(/^\.\//, "");
76
+ }
77
+ function isAbsoluteSourcePath(path) {
78
+ return path.startsWith("/") || /^[a-z]:\//i.test(path);
79
+ }
80
+ /**
81
+ * Directories a relative manifest `filePath` can be relative to: the scanned
82
+ * project (the process's working directory) and its workspace root.
83
+ */
84
+ function relativeSourceRoots() {
85
+ if (typeof process === "undefined" || typeof process.cwd !== "function") return [];
86
+ const cwd = normalizeSourcePath(process.cwd());
87
+ const workspaceRoot = findWorkspaceRootSync(process.cwd());
88
+ return workspaceRoot && normalizeSourcePath(workspaceRoot) !== cwd ? [cwd, normalizeSourcePath(workspaceRoot)] : [cwd];
89
+ }
90
+ /**
91
+ * Whether two source paths name the same file. A stack frame reports an
92
+ * absolute path; a manifest's `filePath` is absolute or relative to the
93
+ * project or workspace root it was scanned from (anytown scans
94
+ * `packages/cloud-network/src/models/*.ts` into the dashboard's manifest,
95
+ * #3110), so a relative path is resolved against those roots, never matched by
96
+ * suffix alone.
97
+ */
98
+ function isSameSourcePath(left, right) {
99
+ if (!left || !right) return false;
100
+ const a = normalizeSourcePath(left);
101
+ const b = normalizeSourcePath(right);
102
+ if (a === b) return true;
103
+ const aAbsolute = isAbsoluteSourcePath(a);
104
+ if (aAbsolute === isAbsoluteSourcePath(b)) return false;
105
+ const [absolute, relative] = aAbsolute ? [a, b] : [b, a];
106
+ const path = getNodeBuiltins()?.path;
107
+ if (!path) return false;
108
+ return relativeSourceRoots().some((root) => normalizeSourcePath(path.resolve(root, relative)) === absolute);
109
+ }
110
+ /**
111
+ * The package whose loaded manifest describes the class declared in
112
+ * `sourceFile` — the class's identity when its nearest `package.json` is not
113
+ * the package that scanned it (#3110). An app manifest commonly scans workspace
114
+ * packages' sources under the app's own package name (anytown's
115
+ * `packages/cloud-network` models are `@anytown/dashboard:*`), so the
116
+ * stack-derived package of a source-consumed class (Vite dev server, Vitest,
117
+ * tsx) never declares it.
118
+ *
119
+ * When several packages' entries describe the file, the stack-derived package
120
+ * wins if it is one of them; otherwise a single candidate, or the one owned by
121
+ * the manifest it appears in. Anything else is ambiguous and yields
122
+ * `undefined`.
123
+ */
124
+ function findManifestPackageForSource(name, sourceFile, stackPackageName) {
125
+ if (!sourceFile) return void 0;
126
+ const manifests = /* @__PURE__ */ new Set();
127
+ for (const manifest of [
128
+ getLocalTestManifestCache(),
129
+ getTestManifestCache(),
130
+ getStaticManifestCache(),
131
+ ...getManifestCache().values()
132
+ ]) if (manifest?.objects) manifests.add(manifest);
133
+ const resolved = resolveSourceManifestPackage(name, sourceFile, stackPackageName, manifests);
134
+ if (resolved) return resolved;
135
+ const projectManifest = readProjectManifestSync();
136
+ return projectManifest?.objects && !manifests.has(projectManifest) ? resolveSourceManifestPackage(name, sourceFile, stackPackageName, /* @__PURE__ */ new Set([projectManifest])) : void 0;
137
+ }
138
+ function resolveSourceManifestPackage(name, sourceFile, stackPackageName, manifests) {
139
+ const candidates = /* @__PURE__ */ new Map();
140
+ const ownedByTheirManifest = /* @__PURE__ */ new Set();
141
+ for (const manifest of manifests) for (const [key, entry] of Object.entries(manifest.objects)) {
142
+ const qualified = isQualifiedName(key) ? parseQualifiedName(key) : void 0;
143
+ if ((entry.className ?? qualified?.className ?? key) !== name) continue;
144
+ if (!isSameSourcePath(sourceFile, entry.filePath)) continue;
145
+ const entryPackage = entry.packageName ?? qualified?.packageName ?? manifest.packageName;
146
+ if (!entryPackage) continue;
147
+ if (!candidates.has(entryPackage)) candidates.set(entryPackage, {
148
+ ...entry,
149
+ packageName: entryPackage
150
+ });
151
+ if (entryPackage === manifest.packageName) ownedByTheirManifest.add(entryPackage);
152
+ }
153
+ const pick = (packageName) => ({
154
+ packageName,
155
+ entry: candidates.get(packageName)
156
+ });
157
+ if (stackPackageName && candidates.has(stackPackageName)) return pick(stackPackageName);
158
+ if (candidates.size === 1) return pick([...candidates.keys()][0]);
159
+ if (ownedByTheirManifest.size === 1) return pick([...ownedByTheirManifest][0]);
160
+ }
55
161
  /**
56
162
  * Build `CollisionInputs` for the decorator-origin path (`register()`).
57
163
  * Manifest-origin inputs are built separately inside `registerFromManifest`.
58
164
  */
59
165
  function buildDecoratorCollisionInputs(args) {
60
- const { ctor, name, newPackageName, newSourceFile, newInBundledContext, existing, existingKey, matchKind } = args;
166
+ const { ctor, name, newPackageName, newSourceFile, newInBundledContext, newDeclaredTableName, existing, existingKey, matchKind } = args;
167
+ const existingTableName = existing.schema?.tableName || existing.config?.tableName;
61
168
  let newExtendsExisting = false;
62
169
  let existingExtendsNew = false;
63
170
  try {
@@ -65,7 +172,7 @@ function buildDecoratorCollisionInputs(args) {
65
172
  existingExtendsNew = existing.constructor.prototype instanceof ctor;
66
173
  } catch {}
67
174
  const bothSourceFilesKnown = !!(newSourceFile && existing.sourceFilePath);
68
- const sameSourceFile = bothSourceFilesKnown && newSourceFile === existing.sourceFilePath;
175
+ const sameSourceFile = bothSourceFilesKnown && isSameSourcePath(newSourceFile, existing.sourceFilePath);
69
176
  const existingIsPackageQualified = !!existing.packageName?.startsWith("@");
70
177
  const samePackage = !!newPackageName && !!existing.packageName && newPackageName === existing.packageName;
71
178
  const hasNewQualifiedKey = !!newPackageName;
@@ -88,7 +195,8 @@ function buildDecoratorCollisionInputs(args) {
88
195
  existingHasAnyPackage: !!existing.packageName,
89
196
  hasManifestContent: false,
90
197
  registrationKeyDiffersFromExistingKey: false,
91
- existingHasNoPackage: !existing.packageName
198
+ existingHasNoPackage: !existing.packageName,
199
+ declaresDifferentTable: !!newDeclaredTableName && !!existingTableName && newDeclaredTableName !== existingTableName
92
200
  };
93
201
  }
94
202
  /**
@@ -116,8 +224,10 @@ function applyRegisterCollisionPolicy(args) {
116
224
  const matchSuffix = args.matchKind === "case-insensitive" ? ` (case-insensitive match with "${args.existingKey}")` : "";
117
225
  throw new Error(`SMRT Class Name Collision: "${args.name}"${matchSuffix}\n\nA class with this name is already registered, but with a different constructor.\nThis usually happens when:\n 1. Multiple test files define classes with the same name\n 2. Different packages export classes with the same name\n\nThe collision will cause the wrong field definitions to be used,\nleading to properties not being initialized correctly.\n\nTo fix:\n - Use unique class names (e.g., ${args.name}_UniqueId)\n - Or use @smrt({ name: 'unique_name' }) to override the registration name`);
118
226
  }
119
- case "merge-manifest":
120
- case "coexist-qualified": throw new Error(`decideCollisionPolicy returned '${decision.policy}' for decorator origin (scenario: ${decision.scenario}). This is a bug.`);
227
+ case "coexist-qualified":
228
+ verboseLog(`[registry] ${decision.scenario}: '${args.name}' coexists with '${args.existingKey}' (${decision.reason})`);
229
+ return false;
230
+ case "merge-manifest": throw new Error(`decideCollisionPolicy returned '${decision.policy}' for decorator origin (scenario: ${decision.scenario}). This is a bug.`);
121
231
  }
122
232
  }
123
233
  /**
@@ -155,7 +265,8 @@ function buildManifestCollisionInputs(args) {
155
265
  existingHasAnyPackage: !!existing.packageName,
156
266
  hasManifestContent: !!(objectDef.fields || objectDef.methods || objectDef.schema || objectDef.decoratorConfig),
157
267
  registrationKeyDiffersFromExistingKey: registrationKey !== existingKey,
158
- existingHasNoPackage: !existing.packageName
268
+ existingHasNoPackage: !existing.packageName,
269
+ declaresDifferentTable: false
159
270
  };
160
271
  }
161
272
  /**
@@ -194,9 +305,22 @@ function applyManifestCollisionPolicy(args) {
194
305
  }
195
306
  }
196
307
  function resolveTableName(ctor, name, config) {
197
- const manifestEntry = config._manifest ? lookupRegistrationManifest(config._manifest, name, config.packageName, config._manifestKey) : discoverManifestSync(name);
308
+ const manifestEntry = config._manifest ? lookupRegistrationManifest(config._manifest, name, config.packageName, config._manifestKey) : discoverManifestSync(config.packageName ? createQualifiedName(config.packageName, name) : name);
198
309
  return manifestEntry?.schema?.tableName || manifestEntry?.decoratorConfig?.tableName || config.tableName || tableNameFromClass(ctor);
199
310
  }
311
+ /**
312
+ * Whether `packageName` itself declares the class `qualifiedKey` names: it is
313
+ * registered under that key (a manifest stub or an earlier registration), or
314
+ * a manifest owned by that package describes it. A stack-derived package is
315
+ * only trusted as a class's identity on this evidence — a consumer bundle
316
+ * that inlined a dependency reports the consumer's package, whose manifest
317
+ * describes the dependency's classes under the dependency's name (#3098).
318
+ */
319
+ function packageDeclaresClass(packageName, qualifiedKey) {
320
+ if (getClasses().has(qualifiedKey)) return true;
321
+ const entry = discoverManifestSync(qualifiedKey);
322
+ return !!entry && (entry.packageName === void 0 || entry.packageName === packageName);
323
+ }
200
324
  function lookupRegistrationManifest(manifest, name, _packageName, manifestKey) {
201
325
  if (manifestKey) return manifest.objects[manifestKey];
202
326
  return lookupInManifest(manifest, name);
@@ -320,7 +444,8 @@ function registerUntracked(ctor, config = {}) {
320
444
  existing.qualifiedName = nextPackageName ? createQualifiedName(nextPackageName, name) : void 0;
321
445
  const nextTableName = resolveTableName(ctor, name, {
322
446
  ...existing.config,
323
- ...config
447
+ ...config,
448
+ ...nextPackageName ? { packageName: nextPackageName } : {}
324
449
  });
325
450
  existing.config = {
326
451
  ...existing.config,
@@ -339,6 +464,7 @@ function registerUntracked(ctor, config = {}) {
339
464
  version: ""
340
465
  };
341
466
  existing.schema.tableName = nextTableName;
467
+ if (existing.constructor._isManifestStub === true && newSourceFile) existing.sourceFilePath = newSourceFile;
342
468
  existing.constructor = ctor;
343
469
  setSmrtTableName(ctor, nextTableName);
344
470
  declareSensitiveTable(existing.config, nextTableName, existing.schema?.tableName);
@@ -359,7 +485,6 @@ function registerUntracked(ctor, config = {}) {
359
485
  return nextKey;
360
486
  }
361
487
  const newSourceFile = getSourceFileFromStack();
362
- const newPackageName = explicitPackageName || getPackageName(ctor, true) || void 0;
363
488
  const newInBundledContext = isBundledOutputPath(newSourceFile);
364
489
  const constructorKey = getConstructorIndex().get(ctor);
365
490
  const constructorEntry = constructorKey ? getClasses().get(constructorKey) : void 0;
@@ -385,15 +510,23 @@ function registerUntracked(ctor, config = {}) {
385
510
  upsertExistingEntry(constructorKey, constructorEntry);
386
511
  return;
387
512
  }
388
- if (getClasses().has(name)) {
389
- const existing = getClasses().get(name);
390
- if (!existing) throw new Error(`Registry inconsistency: ${name} exists in classes Map but get() returned undefined`);
513
+ const stackPackageName = explicitPackageName ? void 0 : getPackageName(ctor, true) || void 0;
514
+ const stackPackageDeclaresClass = !!stackPackageName && packageDeclaresClass(stackPackageName, createQualifiedName(stackPackageName, name));
515
+ const sourceManifestPackage = explicitPackageName || stackPackageDeclaresClass ? void 0 : findManifestPackageForSource(name, newSourceFile, stackPackageName);
516
+ const newPackageName = explicitPackageName || sourceManifestPackage?.packageName || stackPackageName;
517
+ const ownQualifiedKey = newPackageName ? createQualifiedName(newPackageName, name) : void 0;
518
+ const ownPackageDeclaresClass = !!ownQualifiedKey && (!!explicitPackageName || stackPackageDeclaresClass || !!sourceManifestPackage);
519
+ const belongsToAnotherPackage = (existing) => ownPackageDeclaresClass && !!existing.packageName && existing.packageName !== newPackageName;
520
+ const exactExisting = getClasses().get(name);
521
+ if (exactExisting && !belongsToAnotherPackage(exactExisting)) {
522
+ const existing = exactExisting;
391
523
  if (applyRegisterCollisionPolicy({
392
524
  ctor,
393
525
  name,
394
526
  newPackageName,
395
527
  newSourceFile,
396
528
  newInBundledContext,
529
+ newDeclaredTableName: config.tableName,
397
530
  existing,
398
531
  existingKey: name,
399
532
  matchKind: "exact-key",
@@ -406,23 +539,39 @@ function registerUntracked(ctor, config = {}) {
406
539
  const nameMatches = existing.name?.toLowerCase() === lowerName;
407
540
  if (!(keyMatches || nameMatches) || existingKey === name) continue;
408
541
  if (explicitPackageName && existing.packageName && explicitPackageName !== existing.packageName && isQualifiedName(existingKey)) continue;
542
+ if (belongsToAnotherPackage(existing)) continue;
409
543
  if (applyRegisterCollisionPolicy({
410
544
  ctor,
411
545
  name,
412
546
  newPackageName,
413
547
  newSourceFile,
414
548
  newInBundledContext,
549
+ newDeclaredTableName: config.tableName,
415
550
  existing,
416
551
  existingKey,
417
552
  matchKind: "case-insensitive",
418
553
  upsertExistingEntry
419
554
  })) return;
420
555
  }
421
- const packageNameFromStack = explicitPackageName || getPackageName(ctor, true) || void 0;
556
+ const packageNameFromStack = newPackageName;
422
557
  const sourceFilePath = getSourceFileFromStack();
558
+ const unknownPackageManifestEntry = () => {
559
+ const entry = discoverManifestSync(name);
560
+ if (!entry?.packageName || newInBundledContext || isSameSourcePath(newSourceFile, entry.filePath)) return entry;
561
+ };
562
+ const simpleNameManifestFallback = () => {
563
+ if (findClassesByName(name).some((candidate) => !!candidate.packageName && candidate.packageName !== newPackageName)) return;
564
+ const entry = discoverManifestSync(name);
565
+ if (!(!!entry?.packageName && entry.packageName !== newPackageName)) return entry;
566
+ if (isSameSourcePath(newSourceFile, entry?.filePath)) return entry;
567
+ if (ownPackageDeclaresClass) return void 0;
568
+ const entryTable = entry?.schema?.tableName || entry?.decoratorConfig?.tableName;
569
+ const declaresOtherTable = !!config.tableName && !!entryTable && config.tableName !== entryTable;
570
+ return !newInBundledContext || declaresOtherTable ? void 0 : entry;
571
+ };
423
572
  let manifestEntry;
424
573
  if (config._manifest) manifestEntry = lookupRegistrationManifest(config._manifest, name, explicitPackageName, config._manifestKey);
425
- if (!manifestEntry) manifestEntry = discoverManifestSync(name);
574
+ if (!manifestEntry) manifestEntry = ownQualifiedKey ? discoverManifestSync(ownQualifiedKey) ?? sourceManifestPackage?.entry ?? simpleNameManifestFallback() : unknownPackageManifestEntry();
426
575
  const runtimeTenantScopedDeclaration = getConstructorTenantScopedDeclarations().get(ctor);
427
576
  if (manifestEntry && config.tenantScoped === void 0 && normalizeTenantScopedConfig(manifestEntry.decoratorConfig?.tenantScoped) === void 0 && runtimeTenantScopedDeclaration) throw new ConfigurationError(`Manifest for '${name}' omits or disables tenantScoped but its runtime constructor is decorated with @TenantScoped(). Regenerate the manifest so tenancy schema and runtime enforcement agree.`, "CONFIG_TENANT_MANIFEST_CONFLICT");
428
577
  const fields = /* @__PURE__ */ new Map();
@@ -552,7 +701,7 @@ function registerUntracked(ctor, config = {}) {
552
701
  if (childStrategy !== void 0 && parentStrategy !== childStrategy) throw ConfigurationError.incompatibleStrategy(name, childStrategy, parentName, parentStrategy);
553
702
  }
554
703
  }
555
- const tableName = resolveTableName(ctor, name, config);
704
+ const tableName = manifestEntry?.schema?.tableName || manifestEntry?.decoratorConfig?.tableName || config.tableName || tableNameFromClass(ctor);
556
705
  setSmrtTableName(ctor, tableName);
557
706
  let schema;
558
707
  if (manifestEntry?.schema) {
@@ -622,7 +771,7 @@ function registerUntracked(ctor, config = {}) {
622
771
  validators,
623
772
  validationRules,
624
773
  tools: manifestEntry?.tools,
625
- collection: manifestEntry?.collection ?? pluralizeCollection(name),
774
+ collection: manifestEntry?.collection ?? inheritedStiCollection(ctor) ?? pluralizeCollection(name),
626
775
  collectionConstructor: promotedCollectionConstructor,
627
776
  packageName,
628
777
  sourceFilePath,
@@ -889,6 +1038,7 @@ function mergeManifestIntoExistingRegistration(existing, objectDef, packageName)
889
1038
  } else if (!existing.packageName && objectDef.packageName) existing.packageName = objectDef.packageName;
890
1039
  if (objectDef.extends) existing.extends = packageName ? qualifyExtendsName(objectDef.extends, packageName) : objectDef.extends;
891
1040
  if (!existing.sourceFilePath && objectDef.filePath) existing.sourceFilePath = objectDef.filePath;
1041
+ if (objectDef.collection) existing.collection = objectDef.collection;
892
1042
  existing.visibility = objectDef.visibility || manifestConfig.visibility || existing.visibility;
893
1043
  invalidateInheritanceEntries(existing);
894
1044
  }
@@ -1027,6 +1177,6 @@ function registerFromManifestUntracked(name, objectDef, manifestPackageName) {
1027
1177
  }
1028
1178
  }
1029
1179
  //#endregion
1030
- export { ensureTenantScopedField, invalidateInheritanceEntries, register, registerCollection, registerFromManifest };
1180
+ export { ensureTenantScopedField, invalidateInheritanceEntries, isBundledOutputPath, isSameSourcePath, register, registerCollection, registerFromManifest };
1031
1181
 
1032
1182
  //# sourceMappingURL=class-registration.js.map