@happyvertical/smrt-core 0.51.38 → 0.52.0

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 (79) hide show
  1. package/AGENTS.md +8 -0
  2. package/agents/schema-paths.md +90 -0
  3. package/dist/cascade.d.ts.map +1 -1
  4. package/dist/cascade.js +2 -1
  5. package/dist/cascade.js.map +1 -1
  6. package/dist/collection.d.ts +10 -1
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +14 -4
  9. package/dist/collection.js.map +1 -1
  10. package/dist/errors.d.ts +18 -0
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +21 -0
  13. package/dist/errors.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/manifest/static-manifest.d.ts.map +1 -1
  18. package/dist/manifest/static-manifest.js +9 -1
  19. package/dist/manifest/static-manifest.js.map +1 -1
  20. package/dist/manifest/store.js +1 -1
  21. package/dist/manifest/store.js.map +1 -1
  22. package/dist/manifest.json +9 -1
  23. package/dist/migrations/differ.d.ts +58 -0
  24. package/dist/migrations/differ.d.ts.map +1 -1
  25. package/dist/migrations/differ.js +187 -32
  26. package/dist/migrations/differ.js.map +1 -1
  27. package/dist/migrations/null-equal-indexes.js +1 -1
  28. package/dist/migrations/orchestrate.d.ts +7 -0
  29. package/dist/migrations/orchestrate.d.ts.map +1 -1
  30. package/dist/migrations/orchestrate.js +4 -2
  31. package/dist/migrations/orchestrate.js.map +1 -1
  32. package/dist/migrations/tracker.d.ts +29 -1
  33. package/dist/migrations/tracker.d.ts.map +1 -1
  34. package/dist/migrations/tracker.js +92 -7
  35. package/dist/migrations/tracker.js.map +1 -1
  36. package/dist/object.d.ts +128 -0
  37. package/dist/object.d.ts.map +1 -1
  38. package/dist/object.js +333 -44
  39. package/dist/object.js.map +1 -1
  40. package/dist/registry/schema-builder.d.ts.map +1 -1
  41. package/dist/registry/schema-builder.js +7 -5
  42. package/dist/registry/schema-builder.js.map +1 -1
  43. package/dist/registry.d.ts +68 -4
  44. package/dist/registry.d.ts.map +1 -1
  45. package/dist/registry.js +119 -8
  46. package/dist/registry.js.map +1 -1
  47. package/dist/scanner/index.d.ts +1 -1
  48. package/dist/scanner/index.d.ts.map +1 -1
  49. package/dist/scanner/manifest-generator.d.ts +3 -2
  50. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  51. package/dist/scanner/manifest-generator.js +12 -8
  52. package/dist/scanner/manifest-generator.js.map +1 -1
  53. package/dist/scanner/types.d.ts +21 -0
  54. package/dist/scanner/types.d.ts.map +1 -1
  55. package/dist/scanner/types.js.map +1 -1
  56. package/dist/schema/conflict-target.d.ts +68 -11
  57. package/dist/schema/conflict-target.d.ts.map +1 -1
  58. package/dist/schema/conflict-target.js +68 -12
  59. package/dist/schema/conflict-target.js.map +1 -1
  60. package/dist/schema/foreign-key-ddl.d.ts +17 -1
  61. package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
  62. package/dist/schema/foreign-key-ddl.js +29 -3
  63. package/dist/schema/foreign-key-ddl.js.map +1 -1
  64. package/dist/schema/foreign-key-policy.d.ts +8 -0
  65. package/dist/schema/foreign-key-policy.d.ts.map +1 -1
  66. package/dist/schema/foreign-key-policy.js +2 -1
  67. package/dist/schema/foreign-key-policy.js.map +1 -1
  68. package/dist/schema/generator.d.ts +7 -0
  69. package/dist/schema/generator.d.ts.map +1 -1
  70. package/dist/schema/generator.js +18 -10
  71. package/dist/schema/generator.js.map +1 -1
  72. package/dist/schema/text-cast-probe.d.ts +50 -1
  73. package/dist/schema/text-cast-probe.d.ts.map +1 -1
  74. package/dist/schema/text-cast-probe.js +46 -11
  75. package/dist/schema/text-cast-probe.js.map +1 -1
  76. package/dist/schema/types.d.ts +6 -0
  77. package/dist/schema/types.d.ts.map +1 -1
  78. package/dist/smrt-knowledge.json +12 -7
  79. package/package.json +10 -10
@@ -363,6 +363,25 @@ export interface AgentAdminRouteManifest {
363
363
  /** Optional: export name for server load function */
364
364
  load?: string;
365
365
  }
366
+ /**
367
+ * A "create" option an agent contributes, declared via `static createEntries`
368
+ * on the agent class. See `AgentCreateEntry` in `@happyvertical/smrt-agents/ui`.
369
+ */
370
+ export interface AgentCreateEntryManifest {
371
+ id: string;
372
+ type: string;
373
+ format?: string;
374
+ label: string;
375
+ description?: string;
376
+ icon?: string;
377
+ route: string;
378
+ order?: number;
379
+ availability?: {
380
+ permission?: string;
381
+ accessLevels?: string[];
382
+ requires?: string[];
383
+ };
384
+ }
366
385
  /**
367
386
  * Auto-generated agent manifest section
368
387
  * Produced by the manifest build pipeline for any class with `agent` decorator config
@@ -375,6 +394,8 @@ export interface AgentManifest {
375
394
  description?: string;
376
395
  uiSlots: Record<string, AgentUISlotManifest>;
377
396
  adminRoutes?: AgentAdminRouteManifest[];
397
+ /** Create options this agent contributes (`static createEntries`) */
398
+ createEntries?: AgentCreateEntryManifest[];
378
399
  /** Default signal subscriptions declared by this agent */
379
400
  signalSubscriptions?: string[];
380
401
  permissions: AgentPermission[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scanner/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scanner/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../src/scanner/types.ts"],"sourcesContent":["/**\n * Type definitions for AST scanning and manifest generation\n */\n\nimport type { SmartObjectConfig } from '../registry.js';\n\n/**\n * Qualified class name in format \"@package/name:ClassName\"\n * Example: \"@happyvertical/smrt-core:Product\"\n */\nexport type QualifiedClassName = `${string}:${string}`;\n\n/**\n * Static per-field UI hints authored via `@field({ ui })` (#2046).\n *\n * A pure presentation seed for the field-policy rail (epic #2045): it rides the\n * manifest under the field's `_meta.ui` (never promoted to a top-level\n * `FieldDefinition` key), reaches runtime at `field._meta.ui` via\n * `ObjectRegistry.getAllFields()`, and is emitted to the browser in generated\n * web-collection definitions. It has NO schema, persistence, or security\n * effect — `sensitive`/`readPermission` remain the security rail.\n */\nexport interface FieldUIHints {\n /**\n * Seed the field into the \"basic\" visibility tier (shown before the\n * advanced disclosure). Unmarked fields default per the cold-start rule:\n * an object with no basic markers renders everything basic; once any field\n * is marked, unmarked fields default to advanced.\n */\n basic?: boolean;\n /** Grouping key for form section/grouping (consumer-defined label). */\n group?: string;\n /** Relative sort order within the form/group (ascending). */\n order?: number;\n /**\n * Seed the field's policy as locked: org/user policy tiers may not\n * override it unless an org admin explicitly unlocks it.\n */\n locked?: boolean;\n}\n\n/**\n * Structured metadata attached to a field definition under `_meta`.\n *\n * Captures the field-helper options the scanner and schema/manifest\n * generators read off a field (`sqlType`, `nullable`, `__tenancy`, …). The\n * index signature keeps the bag open for forward-compatible keys without\n * forcing every consumer through `any`.\n */\nexport interface FieldMeta {\n /** Explicit SQL type override (e.g. 'UUID') applied during schema generation. */\n sqlType?: string;\n /** Storage type for cross-package reference ids ('text' forces TEXT). */\n idType?: 'uuid' | 'text';\n /** When true, the column is nullable regardless of `required`. */\n nullable?: boolean;\n /** Mirror of the field's required flag captured at registration. */\n required?: boolean;\n /** Default value carried in metadata for runtime-registry generation. */\n default?: unknown;\n /** Per-row SQL backfill for adding/tightening a required column (#3008). */\n backfill?: string;\n /** Marks the field as the primary key column. */\n primaryKey?: boolean;\n /** Marks the column as unique. */\n unique?: boolean;\n /** Column description carried into generated schema. */\n description?: string;\n /** Static per-field UI hints authored via `@field({ ui })` (#2046). */\n ui?: FieldUIHints;\n /** Opt-in column/JSON-path indexing flag. */\n indexed?: boolean;\n /** Foreign-key delete action carried in metadata. */\n onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';\n /** Configures physical DDL while retaining FK relationship metadata. */\n constraint?:\n | boolean\n | { engines: Array<'postgres' | 'sqlite' | 'duckdb' | 'json'> };\n /** Excludes the field from persistence when true. */\n transient?: boolean;\n /** Numeric/length validation bounds and pattern. */\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n /**\n * Validation regex. A string source, a `RegExp`, or — when a manifest was\n * JSON-serialized and a `RegExp` collapsed to `{}` — an opaque object. Read\n * sites must narrow before accessing `.source`.\n */\n pattern?: unknown;\n /** Tenancy metadata injected for tenant-scoped models. */\n __tenancy?: {\n isTenantIdField?: boolean;\n /** Populated by the tenancy beforeSave interceptor when enabled. */\n autoPopulate?: boolean;\n [key: string]: unknown;\n };\n /** Report-aggregate metadata injected by the report normalizer. */\n __report?: {\n kind?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n/**\n * Controls how a class is exposed in manifests and across packages\n */\nexport type SmrtVisibility =\n | 'public' // Included in published manifest, available to all consumers\n | 'internal' // Package-only, excluded from published manifest\n | 'test'; // Test-only, never in any published manifest\n\nexport interface FieldDefinition {\n type:\n | 'text'\n | 'decimal'\n | 'boolean'\n | 'integer'\n | 'datetime'\n | 'json'\n | 'foreignKey'\n | 'crossPackageRef' // Cross-package reference — UUID id column by default, no DDL FK\n | 'oneToMany'\n | 'manyToMany'\n | 'meta'; // STI meta fields (_meta_type, _meta_data)\n required?: boolean;\n default?: unknown;\n min?: number;\n max?: number;\n maxLength?: number;\n minLength?: number;\n related?: string; // For foreignKey, crossPackageRef, oneToMany, manyToMany\n description?: string;\n _meta?: FieldMeta;\n transient?: boolean; // Field not persisted to database\n /**\n * Sensitive value (API secrets, credentials, tax IDs). Still persisted, but\n * excluded from `toPublicJSON()` (generated REST/MCP/SvelteKit responses) and\n * rejected as a `where` filter key.\n */\n sensitive?: boolean;\n /**\n * Read-only over generated write surfaces. Stripped from create/update\n * request bodies so callers cannot mass-assign it.\n */\n readonly?: boolean;\n /**\n * Permission slug required to include this field in public/read responses.\n * Omitted unless the serializer is given a resolved permission set that\n * includes this slug. `sensitive` still wins and hides the field for all\n * callers.\n */\n readPermission?: string;\n /**\n * Controls whether the field is included in JSON exports.\n * - `true`: Always exported (unless site explicitly excludes it)\n * - `false`: Never exported (cannot be overridden by site config)\n * - `undefined`: Uses site's fieldExportDefault setting\n */\n exported?: boolean;\n}\n\nexport interface MethodParameterDefinition {\n name: string;\n type: string;\n optional: boolean;\n default?: unknown;\n /**\n * The declared annotation -- or a member of an inline object literal inside\n * it -- was syntax the scanner could not express as a string (intersection,\n * tuple, conditional, mapped, `typeof`, indexed access).\n *\n * `type` still reads `'any'` for compatibility, so this flag is the ONLY\n * thing separating \"the author wrote `any`\" from \"the scanner gave up\".\n * Anything that must fail closed on an uncertain type -- notably the API\n * wire-ability gate in `generators/custom-action.ts` -- reads this (#2686).\n */\n typeUnresolved?: boolean;\n /**\n * Resolved member types of an INLINE object-literal annotation, flattened\n * across nested literals, arrays, unions, and type arguments.\n *\n * The manifest records an inline literal as the single string `'object'`,\n * which hides exactly the members that decide whether the bag can cross a\n * wire (a callback, a model instance). NAMED bags -- an interface, type\n * alias, `Partial<>`/`Pick<>` -- are deliberately absent here and accepted\n * heuristically instead; expanding them needs cross-file type resolution the\n * AST scanner does not perform (#2686).\n */\n memberTypes?: string[];\n /**\n * For a top-level UNION, each branch with the inline members IT declared.\n *\n * `memberTypes` above flattens across branches, which lets one branch veto\n * another: `{ callback: () => void } | string` is wire-able through its\n * string branch, but the flattened `Function` rejected the whole parameter.\n * A consumer that understands this field MUST prefer it over `memberTypes`\n * for unions. Absent on a non-union parameter and on manifests generated\n * before #2686.\n */\n unionBranches?: ParameterTypeBranch[];\n}\n\n/** One branch of a top-level union parameter type (#2686). */\nexport interface ParameterTypeBranch {\n /** The branch's own type name, e.g. `string` or `object`. */\n type: string;\n /** Inline object members declared by THIS branch only. */\n memberTypes?: string[];\n}\n\nexport interface MethodDefinition {\n name: string;\n async: boolean;\n parameters: MethodParameterDefinition[];\n returnType: string;\n description?: string;\n isStatic: boolean;\n isPublic: boolean;\n /**\n * Config object of an `@method()` decorator declared on this method.\n *\n * `{}` for a bare `@method()`; absent when the method carries no `@method()`\n * at all. Typed loosely on purpose: this is SCANNED config that reached the\n * manifest as JSON, so consumers narrow it defensively through\n * `readMethodDecoratorConfig` in `generators/custom-action.ts` rather than\n * trusting a compile-time shape. The authoring type is `MethodOptions` in\n * `decorators/index.ts` (#2686).\n */\n decoratorConfig?: Record<string, unknown>;\n}\n\n/**\n * Pre-generated schema column definition for manifest\n */\nexport interface ManifestColumnDefinition {\n type: string; // SQL type: TEXT, INTEGER, DECIMAL, BOOLEAN, DATETIME, JSON\n primaryKey?: boolean;\n /**\n * Non-DDL marker for SMRT-owned identifier/reference columns.\n */\n referenceKind?: 'id' | 'foreignKey' | 'crossPackageRef' | 'tenantId';\n notNull?: boolean;\n unique?: boolean;\n default?: unknown;\n /** Same-package FK only. Cross-package references never populate this. */\n foreignKey?: import('../schema/types.js').ColumnDefinition['foreignKey'];\n}\n\n/**\n * Pre-generated schema index definition for manifest\n */\nexport interface ManifestIndexDefinition {\n name: string;\n columns: string[];\n unique?: boolean;\n /** SMRT-owned nullable upsert identity; PostgreSQL 15+ uses NULLS NOT DISTINCT. */\n nullsNotDistinct?: boolean;\n where?: string;\n /**\n * Expression-based index target — when set, the DDL strategy renders the\n * index over a JSON path inside `column` (dialect-specific syntax).\n */\n jsonPath?: {\n column: string;\n path: string;\n };\n}\n\n/**\n * Pre-generated schema for efficient external package consumption\n * Generated at build time by ManifestGenerator, stored in manifest.json\n */\nexport interface ManifestSchema {\n tableName: string;\n /**\n * Engine-neutral CREATE TABLE preview. Not executable as-is: no indexes,\n * abstract SQL types. Consumers render `columns` + `indexes` through the\n * engine DDL strategy (see `schema/manifest-schema.ts`, #2358).\n */\n ddl: string;\n columns: Record<string, ManifestColumnDefinition>;\n indexes: ManifestIndexDefinition[];\n version: string; // Hash of schema definition for migration tracking\n}\n\n/**\n * Validation rule type for pre-computed validators\n * These are serializable rules extracted from field definitions at build time,\n * eliminating the need to compile validator closures at runtime.\n */\nexport type ValidationRuleType =\n | 'required'\n | 'min'\n | 'max'\n | 'minLength'\n | 'maxLength'\n | 'pattern';\n\n/**\n * Pre-computed validation rule for a field\n * Generated at build time by ManifestGenerator, stored in manifest.json\n *\n * These rules are evaluated at runtime without creating closure functions,\n * significantly reducing CLI startup time for projects with many SMRT objects.\n */\nexport interface ValidationRule {\n /** Field name to validate */\n field: string;\n /** Type of validation rule */\n rule: ValidationRuleType;\n /** Value for the rule (number for min/max/minLength/maxLength, string for pattern) */\n value?: number | string;\n /** Field type for context (text, integer, decimal, etc.) */\n fieldType?: string;\n}\n\n// ── Agent manifest types ────────────────────────────────────────────────\n\n/**\n * Permission declared by an agent\n * Generated from uiSlots (manage:*) and CLI/MCP methods (execute:*)\n */\nexport interface AgentPermission {\n id: string;\n label: string;\n category: 'slot' | 'method';\n defaultGranted?: boolean;\n}\n\n/**\n * Feature declared by an agent\n */\nexport interface AgentFeature {\n id: string;\n label: string;\n description?: string;\n type: 'slot' | 'method';\n}\n\n/**\n * Menu item derived from agent uiSlots\n */\nexport interface AgentMenuItem {\n id: string;\n label: string;\n icon?: string;\n order: number;\n path: string;\n requiredPermission?: string;\n}\n\n/**\n * Component export declaration derived from package.json exports\n */\nexport interface AgentComponentDeclaration {\n exportPath: string;\n type: string;\n}\n\n/**\n * UI slot definition captured from static uiSlots on Agent subclasses\n */\nexport interface AgentUISlotManifest {\n id: string;\n label: string;\n description?: string;\n icon?: string;\n order?: number;\n disabled?: boolean;\n /** Durable owner for settings written through this slot. */\n scope?: 'agent' | 'persona';\n /** Optional fallback form contract for host applications. */\n settingsSchema?: {\n version: number;\n fields: Array<{\n id: string;\n label: string;\n type: 'string' | 'number' | 'boolean' | 'select' | 'textarea' | 'json';\n description?: string;\n required?: boolean;\n default?: unknown;\n placeholder?: string;\n options?: Array<{ value: string; label: string }>;\n min?: number;\n max?: number;\n }>;\n };\n}\n\n/**\n * Admin route declared by an agent via static adminRoutes\n * Consumed by vitePluginAgentRoutes to register admin routes with SvelteKit\n */\nexport interface AgentAdminRouteManifest {\n /** Route path relative to agent root (e.g., 'sources/[sourceId]') */\n path: string;\n /** Component export name from the agent's admin entry point */\n component: string;\n /** Optional: export name for server load function */\n load?: string;\n}\n\n/**\n * Auto-generated agent manifest section\n * Produced by the manifest build pipeline for any class with `agent` decorator config\n */\nexport interface AgentManifest {\n name: string;\n slug: string;\n icon?: string;\n tier: 'free' | 'standard' | 'premium';\n description?: string;\n uiSlots: Record<string, AgentUISlotManifest>;\n adminRoutes?: AgentAdminRouteManifest[];\n /** Default signal subscriptions declared by this agent */\n signalSubscriptions?: string[];\n permissions: AgentPermission[];\n features: AgentFeature[];\n menuItems: AgentMenuItem[];\n components: AgentComponentDeclaration[];\n}\n\n// ── Core manifest types ─────────────────────────────────────────────────\n\nexport interface SmartObjectDefinition {\n /**\n * Qualified name in format \"@package/name:ClassName\"\n * This is the PRIMARY KEY for manifest lookups.\n * Example: \"@happyvertical/smrt-core:Product\"\n *\n * Optional during transition - will be required in future versions.\n */\n qualifiedName?: QualifiedClassName;\n\n /**\n * Simple class name (PascalCase)\n * Example: \"Product\"\n */\n className: string;\n\n /**\n * Package name where this class is defined.\n * Example: \"@happyvertical/smrt-core\"\n *\n * Optional during transition - will be required in future versions.\n */\n packageName?: string;\n\n /**\n * Visibility control for manifest inclusion\n * - 'public': Included in published manifest (default)\n * - 'internal': Package-only, excluded from published manifest\n * - 'test': Test-only, never in any published manifest\n *\n * Defaults to 'public' if not specified.\n */\n visibility?: SmrtVisibility;\n\n /**\n * @deprecated Use qualifiedName instead. Kept for transition period.\n * Lowercase class name, formerly used as manifest key.\n */\n name: string;\n\n collection: string; // Pluralized name for endpoints\n filePath: string;\n packageVersion?: string; // Package version for external manifest loading\n importPath?: string; // Import path for dynamic loading (e.g., \"@pkg/objects\")\n modulePath?: string; // Relative module path within package\n exportName?: string; // Named export to use (defaults to className)\n collectionExportName?: string; // Collection class export name\n fields: Record<string, FieldDefinition>;\n methods: Record<string, MethodDefinition>;\n decoratorConfig: SmartObjectConfig;\n extends?: string; // Base class name (simple name, not qualified)\n extendsQualified?: string; // Base class qualified name\n extendsTypeArg?: string; // Generic type argument (e.g., \"Meeting\" from \"SmrtCollection<Meeting>\")\n tools?: Array<{\n type: 'function';\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n };\n }>;\n /**\n * Pre-generated schema for efficient external package consumption\n * Generated at build time by ManifestGenerator (smrt scan)\n *\n * When present, consumers can use this pre-computed schema directly\n * without calling generateSchema() at runtime, eliminating latency.\n */\n schema?: ManifestSchema;\n\n /**\n * Pre-computed validation rules for efficient runtime validation\n * Generated at build time by ManifestGenerator (smrt scan)\n *\n * When present, the registry uses these rules instead of compiling\n * validator closures at runtime, significantly reducing startup time.\n *\n * @see ValidationRule\n */\n validationRules?: ValidationRule[];\n\n /**\n * Static properties captured from the class definition\n * Currently used for `static uiSlots` on Agent subclasses\n */\n staticProperties?: Record<string, unknown>;\n\n /**\n * Auto-generated agent manifest\n * Only present for classes with `agent` in their decorator config\n * Generated at build time by ManifestGenerator fifth pass\n */\n agent?: AgentManifest;\n}\n\n/**\n * Fixed `timestamp` for every manifest that can reach build output.\n *\n * Manifests are inlined into bundles (`registerPackageManifest(JSON.parse(...))`),\n * so a wall-clock value changes the emitted bytes on every build. That changes\n * Vite's content hash, which changes `dist/`, which invalidates every\n * downstream package through `dependsOn: ['^build']` — one rebuilt package\n * churned 202 of 245 task hashes, and `typecheck` never reused a cache entry\n * across runs (#2223).\n *\n * Nothing reads the field: `getManifestTimestampsHash` invalidates on\n * filesystem mtimes, and `normalizeManifestForHash` deletes it before\n * comparing. A fixed value therefore costs no information.\n *\n * Genuine runtime caches that expire on elapsed time — the discovery cache in\n * `manifest/discover-smrt-packages.ts` — keep using the clock; they never\n * reach build output.\n */\nexport const MANIFEST_TIMESTAMP = 0;\n\n/**\n * Fixed `generatedAt` for knowledge artifacts emitted into `dist/`.\n *\n * Same reasoning as {@link MANIFEST_TIMESTAMP}: build output is compared\n * byte-for-byte, so it cannot carry the clock. The Unix epoch is used rather\n * than `0` because the field is an ISO-8601 string, and a parseable value\n * keeps any consumer that formats it working.\n *\n * The working copy under `.smrt/` keeps a real timestamp — it is useful to a\n * human there, and `preserveKnowledgeGeneratedAt` already stops it churning\n * across incremental rebuilds.\n */\nexport const DETERMINISTIC_GENERATED_AT = '1970-01-01T00:00:00.000Z';\n\nexport interface SmartObjectManifest {\n version: string;\n /** Always {@link MANIFEST_TIMESTAMP} for build-output manifests. */\n timestamp: number;\n packageName?: string; // Root package name (should be set for all new manifests)\n packageVersion?: string; // Root package version\n /**\n * Objects keyed by qualified name (e.g., \"@happyvertical/smrt-core:Product\")\n * Key is string for backward compatibility, but should be QualifiedClassName format.\n */\n objects: Record<string, SmartObjectDefinition>;\n /** Test artifacts must not become dependency providers during discovery. */\n artifactPurpose?: 'runtime' | 'test';\n moduleType?: string; // Module type identifier (e.g., \"smrt\") for package discovery\n smrtDependencies?: string[]; // Discovered SMRT packages from dependency tree\n}\n\nexport interface ScanResult {\n filePath: string;\n objects: SmartObjectDefinition[];\n errors: Array<{\n message: string;\n line?: number;\n column?: number;\n }>;\n}\n\nexport interface ScanOptions {\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n followImports?: boolean;\n baseClasses?: string[]; // Classes to consider as SMRT base classes\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4hBA,IAAa,qBAAqB;;;;;;;;;;;;;AAclC,IAAa,6BAA6B"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/scanner/types.ts"],"sourcesContent":["/**\n * Type definitions for AST scanning and manifest generation\n */\n\nimport type { SmartObjectConfig } from '../registry.js';\n\n/**\n * Qualified class name in format \"@package/name:ClassName\"\n * Example: \"@happyvertical/smrt-core:Product\"\n */\nexport type QualifiedClassName = `${string}:${string}`;\n\n/**\n * Static per-field UI hints authored via `@field({ ui })` (#2046).\n *\n * A pure presentation seed for the field-policy rail (epic #2045): it rides the\n * manifest under the field's `_meta.ui` (never promoted to a top-level\n * `FieldDefinition` key), reaches runtime at `field._meta.ui` via\n * `ObjectRegistry.getAllFields()`, and is emitted to the browser in generated\n * web-collection definitions. It has NO schema, persistence, or security\n * effect — `sensitive`/`readPermission` remain the security rail.\n */\nexport interface FieldUIHints {\n /**\n * Seed the field into the \"basic\" visibility tier (shown before the\n * advanced disclosure). Unmarked fields default per the cold-start rule:\n * an object with no basic markers renders everything basic; once any field\n * is marked, unmarked fields default to advanced.\n */\n basic?: boolean;\n /** Grouping key for form section/grouping (consumer-defined label). */\n group?: string;\n /** Relative sort order within the form/group (ascending). */\n order?: number;\n /**\n * Seed the field's policy as locked: org/user policy tiers may not\n * override it unless an org admin explicitly unlocks it.\n */\n locked?: boolean;\n}\n\n/**\n * Structured metadata attached to a field definition under `_meta`.\n *\n * Captures the field-helper options the scanner and schema/manifest\n * generators read off a field (`sqlType`, `nullable`, `__tenancy`, …). The\n * index signature keeps the bag open for forward-compatible keys without\n * forcing every consumer through `any`.\n */\nexport interface FieldMeta {\n /** Explicit SQL type override (e.g. 'UUID') applied during schema generation. */\n sqlType?: string;\n /** Storage type for cross-package reference ids ('text' forces TEXT). */\n idType?: 'uuid' | 'text';\n /** When true, the column is nullable regardless of `required`. */\n nullable?: boolean;\n /** Mirror of the field's required flag captured at registration. */\n required?: boolean;\n /** Default value carried in metadata for runtime-registry generation. */\n default?: unknown;\n /** Per-row SQL backfill for adding/tightening a required column (#3008). */\n backfill?: string;\n /** Marks the field as the primary key column. */\n primaryKey?: boolean;\n /** Marks the column as unique. */\n unique?: boolean;\n /** Column description carried into generated schema. */\n description?: string;\n /** Static per-field UI hints authored via `@field({ ui })` (#2046). */\n ui?: FieldUIHints;\n /** Opt-in column/JSON-path indexing flag. */\n indexed?: boolean;\n /** Foreign-key delete action carried in metadata. */\n onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';\n /** Configures physical DDL while retaining FK relationship metadata. */\n constraint?:\n | boolean\n | { engines: Array<'postgres' | 'sqlite' | 'duckdb' | 'json'> };\n /** Excludes the field from persistence when true. */\n transient?: boolean;\n /** Numeric/length validation bounds and pattern. */\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n /**\n * Validation regex. A string source, a `RegExp`, or — when a manifest was\n * JSON-serialized and a `RegExp` collapsed to `{}` — an opaque object. Read\n * sites must narrow before accessing `.source`.\n */\n pattern?: unknown;\n /** Tenancy metadata injected for tenant-scoped models. */\n __tenancy?: {\n isTenantIdField?: boolean;\n /** Populated by the tenancy beforeSave interceptor when enabled. */\n autoPopulate?: boolean;\n [key: string]: unknown;\n };\n /** Report-aggregate metadata injected by the report normalizer. */\n __report?: {\n kind?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n/**\n * Controls how a class is exposed in manifests and across packages\n */\nexport type SmrtVisibility =\n | 'public' // Included in published manifest, available to all consumers\n | 'internal' // Package-only, excluded from published manifest\n | 'test'; // Test-only, never in any published manifest\n\nexport interface FieldDefinition {\n type:\n | 'text'\n | 'decimal'\n | 'boolean'\n | 'integer'\n | 'datetime'\n | 'json'\n | 'foreignKey'\n | 'crossPackageRef' // Cross-package reference — UUID id column by default, no DDL FK\n | 'oneToMany'\n | 'manyToMany'\n | 'meta'; // STI meta fields (_meta_type, _meta_data)\n required?: boolean;\n default?: unknown;\n min?: number;\n max?: number;\n maxLength?: number;\n minLength?: number;\n related?: string; // For foreignKey, crossPackageRef, oneToMany, manyToMany\n description?: string;\n _meta?: FieldMeta;\n transient?: boolean; // Field not persisted to database\n /**\n * Sensitive value (API secrets, credentials, tax IDs). Still persisted, but\n * excluded from `toPublicJSON()` (generated REST/MCP/SvelteKit responses) and\n * rejected as a `where` filter key.\n */\n sensitive?: boolean;\n /**\n * Read-only over generated write surfaces. Stripped from create/update\n * request bodies so callers cannot mass-assign it.\n */\n readonly?: boolean;\n /**\n * Permission slug required to include this field in public/read responses.\n * Omitted unless the serializer is given a resolved permission set that\n * includes this slug. `sensitive` still wins and hides the field for all\n * callers.\n */\n readPermission?: string;\n /**\n * Controls whether the field is included in JSON exports.\n * - `true`: Always exported (unless site explicitly excludes it)\n * - `false`: Never exported (cannot be overridden by site config)\n * - `undefined`: Uses site's fieldExportDefault setting\n */\n exported?: boolean;\n}\n\nexport interface MethodParameterDefinition {\n name: string;\n type: string;\n optional: boolean;\n default?: unknown;\n /**\n * The declared annotation -- or a member of an inline object literal inside\n * it -- was syntax the scanner could not express as a string (intersection,\n * tuple, conditional, mapped, `typeof`, indexed access).\n *\n * `type` still reads `'any'` for compatibility, so this flag is the ONLY\n * thing separating \"the author wrote `any`\" from \"the scanner gave up\".\n * Anything that must fail closed on an uncertain type -- notably the API\n * wire-ability gate in `generators/custom-action.ts` -- reads this (#2686).\n */\n typeUnresolved?: boolean;\n /**\n * Resolved member types of an INLINE object-literal annotation, flattened\n * across nested literals, arrays, unions, and type arguments.\n *\n * The manifest records an inline literal as the single string `'object'`,\n * which hides exactly the members that decide whether the bag can cross a\n * wire (a callback, a model instance). NAMED bags -- an interface, type\n * alias, `Partial<>`/`Pick<>` -- are deliberately absent here and accepted\n * heuristically instead; expanding them needs cross-file type resolution the\n * AST scanner does not perform (#2686).\n */\n memberTypes?: string[];\n /**\n * For a top-level UNION, each branch with the inline members IT declared.\n *\n * `memberTypes` above flattens across branches, which lets one branch veto\n * another: `{ callback: () => void } | string` is wire-able through its\n * string branch, but the flattened `Function` rejected the whole parameter.\n * A consumer that understands this field MUST prefer it over `memberTypes`\n * for unions. Absent on a non-union parameter and on manifests generated\n * before #2686.\n */\n unionBranches?: ParameterTypeBranch[];\n}\n\n/** One branch of a top-level union parameter type (#2686). */\nexport interface ParameterTypeBranch {\n /** The branch's own type name, e.g. `string` or `object`. */\n type: string;\n /** Inline object members declared by THIS branch only. */\n memberTypes?: string[];\n}\n\nexport interface MethodDefinition {\n name: string;\n async: boolean;\n parameters: MethodParameterDefinition[];\n returnType: string;\n description?: string;\n isStatic: boolean;\n isPublic: boolean;\n /**\n * Config object of an `@method()` decorator declared on this method.\n *\n * `{}` for a bare `@method()`; absent when the method carries no `@method()`\n * at all. Typed loosely on purpose: this is SCANNED config that reached the\n * manifest as JSON, so consumers narrow it defensively through\n * `readMethodDecoratorConfig` in `generators/custom-action.ts` rather than\n * trusting a compile-time shape. The authoring type is `MethodOptions` in\n * `decorators/index.ts` (#2686).\n */\n decoratorConfig?: Record<string, unknown>;\n}\n\n/**\n * Pre-generated schema column definition for manifest\n */\nexport interface ManifestColumnDefinition {\n type: string; // SQL type: TEXT, INTEGER, DECIMAL, BOOLEAN, DATETIME, JSON\n primaryKey?: boolean;\n /**\n * Non-DDL marker for SMRT-owned identifier/reference columns.\n */\n referenceKind?: 'id' | 'foreignKey' | 'crossPackageRef' | 'tenantId';\n notNull?: boolean;\n unique?: boolean;\n default?: unknown;\n /** Same-package FK only. Cross-package references never populate this. */\n foreignKey?: import('../schema/types.js').ColumnDefinition['foreignKey'];\n}\n\n/**\n * Pre-generated schema index definition for manifest\n */\nexport interface ManifestIndexDefinition {\n name: string;\n columns: string[];\n unique?: boolean;\n /** SMRT-owned nullable upsert identity; PostgreSQL 15+ uses NULLS NOT DISTINCT. */\n nullsNotDistinct?: boolean;\n where?: string;\n /**\n * Expression-based index target — when set, the DDL strategy renders the\n * index over a JSON path inside `column` (dialect-specific syntax).\n */\n jsonPath?: {\n column: string;\n path: string;\n };\n}\n\n/**\n * Pre-generated schema for efficient external package consumption\n * Generated at build time by ManifestGenerator, stored in manifest.json\n */\nexport interface ManifestSchema {\n tableName: string;\n /**\n * Engine-neutral CREATE TABLE preview. Not executable as-is: no indexes,\n * abstract SQL types. Consumers render `columns` + `indexes` through the\n * engine DDL strategy (see `schema/manifest-schema.ts`, #2358).\n */\n ddl: string;\n columns: Record<string, ManifestColumnDefinition>;\n indexes: ManifestIndexDefinition[];\n version: string; // Hash of schema definition for migration tracking\n}\n\n/**\n * Validation rule type for pre-computed validators\n * These are serializable rules extracted from field definitions at build time,\n * eliminating the need to compile validator closures at runtime.\n */\nexport type ValidationRuleType =\n | 'required'\n | 'min'\n | 'max'\n | 'minLength'\n | 'maxLength'\n | 'pattern';\n\n/**\n * Pre-computed validation rule for a field\n * Generated at build time by ManifestGenerator, stored in manifest.json\n *\n * These rules are evaluated at runtime without creating closure functions,\n * significantly reducing CLI startup time for projects with many SMRT objects.\n */\nexport interface ValidationRule {\n /** Field name to validate */\n field: string;\n /** Type of validation rule */\n rule: ValidationRuleType;\n /** Value for the rule (number for min/max/minLength/maxLength, string for pattern) */\n value?: number | string;\n /** Field type for context (text, integer, decimal, etc.) */\n fieldType?: string;\n}\n\n// ── Agent manifest types ────────────────────────────────────────────────\n\n/**\n * Permission declared by an agent\n * Generated from uiSlots (manage:*) and CLI/MCP methods (execute:*)\n */\nexport interface AgentPermission {\n id: string;\n label: string;\n category: 'slot' | 'method';\n defaultGranted?: boolean;\n}\n\n/**\n * Feature declared by an agent\n */\nexport interface AgentFeature {\n id: string;\n label: string;\n description?: string;\n type: 'slot' | 'method';\n}\n\n/**\n * Menu item derived from agent uiSlots\n */\nexport interface AgentMenuItem {\n id: string;\n label: string;\n icon?: string;\n order: number;\n path: string;\n requiredPermission?: string;\n}\n\n/**\n * Component export declaration derived from package.json exports\n */\nexport interface AgentComponentDeclaration {\n exportPath: string;\n type: string;\n}\n\n/**\n * UI slot definition captured from static uiSlots on Agent subclasses\n */\nexport interface AgentUISlotManifest {\n id: string;\n label: string;\n description?: string;\n icon?: string;\n order?: number;\n disabled?: boolean;\n /** Durable owner for settings written through this slot. */\n scope?: 'agent' | 'persona';\n /** Optional fallback form contract for host applications. */\n settingsSchema?: {\n version: number;\n fields: Array<{\n id: string;\n label: string;\n type: 'string' | 'number' | 'boolean' | 'select' | 'textarea' | 'json';\n description?: string;\n required?: boolean;\n default?: unknown;\n placeholder?: string;\n options?: Array<{ value: string; label: string }>;\n min?: number;\n max?: number;\n }>;\n };\n}\n\n/**\n * Admin route declared by an agent via static adminRoutes\n * Consumed by vitePluginAgentRoutes to register admin routes with SvelteKit\n */\nexport interface AgentAdminRouteManifest {\n /** Route path relative to agent root (e.g., 'sources/[sourceId]') */\n path: string;\n /** Component export name from the agent's admin entry point */\n component: string;\n /** Optional: export name for server load function */\n load?: string;\n}\n\n/**\n * A \"create\" option an agent contributes, declared via `static createEntries`\n * on the agent class. See `AgentCreateEntry` in `@happyvertical/smrt-agents/ui`.\n */\nexport interface AgentCreateEntryManifest {\n id: string;\n type: string;\n format?: string;\n label: string;\n description?: string;\n icon?: string;\n route: string;\n order?: number;\n availability?: {\n permission?: string;\n accessLevels?: string[];\n requires?: string[];\n };\n}\n\n/**\n * Auto-generated agent manifest section\n * Produced by the manifest build pipeline for any class with `agent` decorator config\n */\nexport interface AgentManifest {\n name: string;\n slug: string;\n icon?: string;\n tier: 'free' | 'standard' | 'premium';\n description?: string;\n uiSlots: Record<string, AgentUISlotManifest>;\n adminRoutes?: AgentAdminRouteManifest[];\n /** Create options this agent contributes (`static createEntries`) */\n createEntries?: AgentCreateEntryManifest[];\n /** Default signal subscriptions declared by this agent */\n signalSubscriptions?: string[];\n permissions: AgentPermission[];\n features: AgentFeature[];\n menuItems: AgentMenuItem[];\n components: AgentComponentDeclaration[];\n}\n\n// ── Core manifest types ─────────────────────────────────────────────────\n\nexport interface SmartObjectDefinition {\n /**\n * Qualified name in format \"@package/name:ClassName\"\n * This is the PRIMARY KEY for manifest lookups.\n * Example: \"@happyvertical/smrt-core:Product\"\n *\n * Optional during transition - will be required in future versions.\n */\n qualifiedName?: QualifiedClassName;\n\n /**\n * Simple class name (PascalCase)\n * Example: \"Product\"\n */\n className: string;\n\n /**\n * Package name where this class is defined.\n * Example: \"@happyvertical/smrt-core\"\n *\n * Optional during transition - will be required in future versions.\n */\n packageName?: string;\n\n /**\n * Visibility control for manifest inclusion\n * - 'public': Included in published manifest (default)\n * - 'internal': Package-only, excluded from published manifest\n * - 'test': Test-only, never in any published manifest\n *\n * Defaults to 'public' if not specified.\n */\n visibility?: SmrtVisibility;\n\n /**\n * @deprecated Use qualifiedName instead. Kept for transition period.\n * Lowercase class name, formerly used as manifest key.\n */\n name: string;\n\n collection: string; // Pluralized name for endpoints\n filePath: string;\n packageVersion?: string; // Package version for external manifest loading\n importPath?: string; // Import path for dynamic loading (e.g., \"@pkg/objects\")\n modulePath?: string; // Relative module path within package\n exportName?: string; // Named export to use (defaults to className)\n collectionExportName?: string; // Collection class export name\n fields: Record<string, FieldDefinition>;\n methods: Record<string, MethodDefinition>;\n decoratorConfig: SmartObjectConfig;\n extends?: string; // Base class name (simple name, not qualified)\n extendsQualified?: string; // Base class qualified name\n extendsTypeArg?: string; // Generic type argument (e.g., \"Meeting\" from \"SmrtCollection<Meeting>\")\n tools?: Array<{\n type: 'function';\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n };\n }>;\n /**\n * Pre-generated schema for efficient external package consumption\n * Generated at build time by ManifestGenerator (smrt scan)\n *\n * When present, consumers can use this pre-computed schema directly\n * without calling generateSchema() at runtime, eliminating latency.\n */\n schema?: ManifestSchema;\n\n /**\n * Pre-computed validation rules for efficient runtime validation\n * Generated at build time by ManifestGenerator (smrt scan)\n *\n * When present, the registry uses these rules instead of compiling\n * validator closures at runtime, significantly reducing startup time.\n *\n * @see ValidationRule\n */\n validationRules?: ValidationRule[];\n\n /**\n * Static properties captured from the class definition\n * Currently used for `static uiSlots` on Agent subclasses\n */\n staticProperties?: Record<string, unknown>;\n\n /**\n * Auto-generated agent manifest\n * Only present for classes with `agent` in their decorator config\n * Generated at build time by ManifestGenerator fifth pass\n */\n agent?: AgentManifest;\n}\n\n/**\n * Fixed `timestamp` for every manifest that can reach build output.\n *\n * Manifests are inlined into bundles (`registerPackageManifest(JSON.parse(...))`),\n * so a wall-clock value changes the emitted bytes on every build. That changes\n * Vite's content hash, which changes `dist/`, which invalidates every\n * downstream package through `dependsOn: ['^build']` — one rebuilt package\n * churned 202 of 245 task hashes, and `typecheck` never reused a cache entry\n * across runs (#2223).\n *\n * Nothing reads the field: `getManifestTimestampsHash` invalidates on\n * filesystem mtimes, and `normalizeManifestForHash` deletes it before\n * comparing. A fixed value therefore costs no information.\n *\n * Genuine runtime caches that expire on elapsed time — the discovery cache in\n * `manifest/discover-smrt-packages.ts` — keep using the clock; they never\n * reach build output.\n */\nexport const MANIFEST_TIMESTAMP = 0;\n\n/**\n * Fixed `generatedAt` for knowledge artifacts emitted into `dist/`.\n *\n * Same reasoning as {@link MANIFEST_TIMESTAMP}: build output is compared\n * byte-for-byte, so it cannot carry the clock. The Unix epoch is used rather\n * than `0` because the field is an ISO-8601 string, and a parseable value\n * keeps any consumer that formats it working.\n *\n * The working copy under `.smrt/` keeps a real timestamp — it is useful to a\n * human there, and `preserveKnowledgeGeneratedAt` already stops it churning\n * across incremental rebuilds.\n */\nexport const DETERMINISTIC_GENERATED_AT = '1970-01-01T00:00:00.000Z';\n\nexport interface SmartObjectManifest {\n version: string;\n /** Always {@link MANIFEST_TIMESTAMP} for build-output manifests. */\n timestamp: number;\n packageName?: string; // Root package name (should be set for all new manifests)\n packageVersion?: string; // Root package version\n /**\n * Objects keyed by qualified name (e.g., \"@happyvertical/smrt-core:Product\")\n * Key is string for backward compatibility, but should be QualifiedClassName format.\n */\n objects: Record<string, SmartObjectDefinition>;\n /** Test artifacts must not become dependency providers during discovery. */\n artifactPurpose?: 'runtime' | 'test';\n moduleType?: string; // Module type identifier (e.g., \"smrt\") for package discovery\n smrtDependencies?: string[]; // Discovered SMRT packages from dependency tree\n}\n\nexport interface ScanResult {\n filePath: string;\n objects: SmartObjectDefinition[];\n errors: Array<{\n message: string;\n line?: number;\n column?: number;\n }>;\n}\n\nexport interface ScanOptions {\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n followImports?: boolean;\n baseClasses?: string[]; // Classes to consider as SMRT base classes\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkjBA,IAAa,qBAAqB;;;;;;;;;;;;;AAclC,IAAa,6BAA6B"}
@@ -28,13 +28,23 @@
28
28
  * themselves exactly as every row did before, and per-tenant rows dedup per
29
29
  * tenant.
30
30
  *
31
- * Index NAMES stay stable across this change — the tenant-led default key
32
- * keeps `<table>_slug_context_idx` (CTI) / `<table>_slug_context_meta_type_idx`
33
- * (STI) — so `SchemaComparer`'s same-name shape-drift repair swaps the columns
34
- * in place (`DROP INDEX` + `CREATE UNIQUE INDEX`) instead of leaving the old
35
- * global unique index behind to keep blocking cross-tenant duplicates. The new
36
- * key is a superset of the old one, so creating it cannot fail on existing
37
- * data.
31
+ * Index names:
32
+ *
33
+ * - A DECLARED tenant-scoped class (#2360) keeps `<table>_slug_context_idx`
34
+ * (CTI) / `<table>_slug_context_meta_type_idx` (STI) for its tenant-led key.
35
+ * That swap shipped long ago; live databases carry the tenant-led columns
36
+ * under that name, and `db:migrate-null-equal-indexes` targets it.
37
+ * - A class that is tenant-owned only by INFERENCE (an undeclared `tenantId`
38
+ * field, {@link resolveOwnershipTenantColumn}) gets its tenant-led key under
39
+ * a NEW name, `<table>_tenant_id_slug_idx`, created beside the legacy global
40
+ * `<table>_slug_context_idx` (expand, then contract). Old code upserts
41
+ * `ON CONFLICT (slug, context)` and new code `ON CONFLICT (tenant_id, slug,
42
+ * context)`; PostgreSQL binds each only to a unique index over exactly those
43
+ * columns, so during a rolling deploy both indexes must exist. The differ
44
+ * keeps the legacy index ({@link isLegacyNaturalKeyIndex}) until the operator
45
+ * runs `smrt db:migrate --drop-legacy-natural-key` once no old code is left.
46
+ * The new key is a superset of the old one, so creating it cannot fail on
47
+ * existing data.
38
48
  */
39
49
  /** Default natural key of a class-per-table (CTI) object. */
40
50
  export declare const DEFAULT_CTI_CONFLICT_COLUMNS: readonly string[];
@@ -72,16 +82,33 @@ export declare function isDefaultNaturalKey(conflictColumns: readonly string[],
72
82
  * - default CTI key `(slug, context)` → `<table>_slug_context_idx`
73
83
  * - default STI key `(slug, context, _meta_type)` →
74
84
  * `<table>_slug_context_meta_type_idx`
75
- * - either default led by the tenant column → the SAME name as without it
76
- * (#2360: the differ swaps the columns in place by name)
85
+ * - either default led by the DECLARED tenant column → the SAME name as
86
+ * without it (#2360: the differ swapped the columns in place by name)
87
+ * - either default led by an INFERRED ownership column (pass no
88
+ * `tenantColumn`) → the custom-key rule, `<table>_tenant_id_slug_idx`, so it
89
+ * is created beside the legacy global index instead of replacing it
77
90
  * - any custom key → `<table>_<first two columns>_idx` (the historical rule;
78
91
  * a longer key keeps the two-column name so existing databases are not
79
92
  * renamed)
80
93
  *
81
- * @param tenantColumn - the table's tenant column, when the schema owner is
82
- * tenant-scoped; only consulted to recognize a tenant-led default key
94
+ * @param tenantColumn - the table's DECLARED tenant column, when the schema
95
+ * owner is tenant-scoped; only consulted to recognize a tenant-led default
96
+ * key. Never pass an inferred ownership column here.
83
97
  */
84
98
  export declare function conflictIndexName(tableName: string, conflictColumns: readonly string[], tenantColumn?: string | null): string;
99
+ /**
100
+ * Whether a live unique index over `indexColumns` is the LEGACY global natural
101
+ * key that a tenant-led default key superseded: its columns are exactly a
102
+ * default natural key (CTI `(slug, context)` / STI `(slug, context,
103
+ * _meta_type)`) and `widerColumns` is that same key led by one ownership
104
+ * column.
105
+ *
106
+ * The differ keeps such an index (expand, then contract) so code still
107
+ * upserting `ON CONFLICT (slug, context)` keeps working while a release that
108
+ * upserts on the tenant-led key rolls out; it is dropped only on
109
+ * `smrt db:migrate --drop-legacy-natural-key`.
110
+ */
111
+ export declare function isLegacyNaturalKeyIndex(indexColumns: readonly string[], widerColumns: readonly string[]): boolean;
85
112
  /**
86
113
  * Whether an unqualified index over `indexColumns` serves slug lookups
87
114
  * (`loadFromSlug()` / `getId()` / `getSavedId()` filter on `slug`, `context`):
@@ -102,6 +129,36 @@ export declare function servesSlugLookup(indexColumns: readonly string[], tenant
102
129
  * @param toColumn - field-name → column-name conversion (`toSnakeCase`)
103
130
  */
104
131
  export declare function resolveTenantColumn(tenantField: string | undefined | null, hasField: (fieldName: string) => boolean, toColumn: (fieldName: string) => string): string | undefined;
132
+ /**
133
+ * The field whose mere presence marks a table's rows as tenant-owned when the
134
+ * class declares no tenancy (`tenantScoped` / `@TenantScoped()` / `@tenantId`).
135
+ * It is the tenancy default field name, so it is also the field a consumer's
136
+ * `registerTenantScopedClass(name)` filters and populates at runtime.
137
+ */
138
+ export declare const IMPLICIT_TENANT_FIELD = "tenantId";
139
+ /** Column of {@link IMPLICIT_TENANT_FIELD}. */
140
+ export declare const IMPLICIT_TENANT_COLUMN = "tenant_id";
141
+ /**
142
+ * The column that owns a table's rows for natural-key identity: the declared
143
+ * tenant column, otherwise the implicit `tenant_id` column when the schema
144
+ * owner has a `tenantId` field.
145
+ *
146
+ * Before this rule a class that carried `tenantId` but did not DECLARE tenancy
147
+ * (the consumer registered it with the tenancy interceptor at runtime instead)
148
+ * kept the global `(slug, context)` key. The interceptor filtered its lookups
149
+ * by tenant, so tenant B's lookup missed tenant A's same-slug row and B's
150
+ * `save()` upserted onto it: `DO UPDATE SET` rewrote A's `id` and `tenant_id`
151
+ * and `ON UPDATE CASCADE` re-pointed A's children at B's row. Rows that carry
152
+ * a tenant are unique per tenant whether or not reads are filtered, so the
153
+ * default natural key follows ownership, not the read policy. Only the
154
+ * DEFAULT key uses this; an explicit `@smrt({ conflictColumns })` is the
155
+ * author's contract and is never rewritten.
156
+ *
157
+ * @param declaredTenantColumn - the column from the class's tenancy
158
+ * declaration ({@link resolveTenantColumn}), when it has one
159
+ * @param hasColumn - whether the schema owner has the given column
160
+ */
161
+ export declare function resolveOwnershipTenantColumn(declaredTenantColumn: string | undefined | null, hasColumn: (columnName: string) => boolean): string | undefined;
105
162
  /** Only the framework conflict identity opts into NULL-equal uniqueness. */
106
163
  export declare function nullableConflictIdentity(conflictColumns: readonly string[], columns: Record<string, {
107
164
  notNull?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"conflict-target.d.ts","sourceRoot":"","sources":["../../src/schema/conflict-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAGzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAIzD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,KAAK,CAAC;AAElD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,qBAAqB,EAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,MAAM,EAAE,CAMV;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAC9C,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,MAAM,EAAE,CAKX;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAST;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,MAAM,CAYR;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAQT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACtC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,EACxC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GACtC,MAAM,GAAG,SAAS,CAGpB;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,OAAO,EAAE,MAAM,CACb,MAAM,EACN;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CACxD,GACA,OAAO,CAKT"}
1
+ {"version":3,"file":"conflict-target.d.ts","sourceRoot":"","sources":["../../src/schema/conflict-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAGzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAIzD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,KAAK,CAAC;AAElD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,qBAAqB,EAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,MAAM,EAAE,CAMV;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAC9C,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,MAAM,EAAE,CAKX;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAST;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,MAAM,CAYR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,OAAO,CAWT;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAQT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACtC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,EACxC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GACtC,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD,+CAA+C;AAC/C,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAElD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,oBAAoB,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAC/C,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,GACzC,MAAM,GAAG,SAAS,CAGpB;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,OAAO,EAAE,MAAM,CACb,MAAM,EACN;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CACxD,GACA,OAAO,CAKT"}
@@ -29,13 +29,23 @@
29
29
  * themselves exactly as every row did before, and per-tenant rows dedup per
30
30
  * tenant.
31
31
  *
32
- * Index NAMES stay stable across this change — the tenant-led default key
33
- * keeps `<table>_slug_context_idx` (CTI) / `<table>_slug_context_meta_type_idx`
34
- * (STI) — so `SchemaComparer`'s same-name shape-drift repair swaps the columns
35
- * in place (`DROP INDEX` + `CREATE UNIQUE INDEX`) instead of leaving the old
36
- * global unique index behind to keep blocking cross-tenant duplicates. The new
37
- * key is a superset of the old one, so creating it cannot fail on existing
38
- * data.
32
+ * Index names:
33
+ *
34
+ * - A DECLARED tenant-scoped class (#2360) keeps `<table>_slug_context_idx`
35
+ * (CTI) / `<table>_slug_context_meta_type_idx` (STI) for its tenant-led key.
36
+ * That swap shipped long ago; live databases carry the tenant-led columns
37
+ * under that name, and `db:migrate-null-equal-indexes` targets it.
38
+ * - A class that is tenant-owned only by INFERENCE (an undeclared `tenantId`
39
+ * field, {@link resolveOwnershipTenantColumn}) gets its tenant-led key under
40
+ * a NEW name, `<table>_tenant_id_slug_idx`, created beside the legacy global
41
+ * `<table>_slug_context_idx` (expand, then contract). Old code upserts
42
+ * `ON CONFLICT (slug, context)` and new code `ON CONFLICT (tenant_id, slug,
43
+ * context)`; PostgreSQL binds each only to a unique index over exactly those
44
+ * columns, so during a rolling deploy both indexes must exist. The differ
45
+ * keeps the legacy index ({@link isLegacyNaturalKeyIndex}) until the operator
46
+ * runs `smrt db:migrate --drop-legacy-natural-key` once no old code is left.
47
+ * The new key is a superset of the old one, so creating it cannot fail on
48
+ * existing data.
39
49
  */
40
50
  /** Default natural key of a class-per-table (CTI) object. */
41
51
  var DEFAULT_CTI_CONFLICT_COLUMNS = ["slug", "context"];
@@ -84,14 +94,18 @@ function isDefaultNaturalKey(conflictColumns, tenantColumn) {
84
94
  * - default CTI key `(slug, context)` → `<table>_slug_context_idx`
85
95
  * - default STI key `(slug, context, _meta_type)` →
86
96
  * `<table>_slug_context_meta_type_idx`
87
- * - either default led by the tenant column → the SAME name as without it
88
- * (#2360: the differ swaps the columns in place by name)
97
+ * - either default led by the DECLARED tenant column → the SAME name as
98
+ * without it (#2360: the differ swapped the columns in place by name)
99
+ * - either default led by an INFERRED ownership column (pass no
100
+ * `tenantColumn`) → the custom-key rule, `<table>_tenant_id_slug_idx`, so it
101
+ * is created beside the legacy global index instead of replacing it
89
102
  * - any custom key → `<table>_<first two columns>_idx` (the historical rule;
90
103
  * a longer key keeps the two-column name so existing databases are not
91
104
  * renamed)
92
105
  *
93
- * @param tenantColumn - the table's tenant column, when the schema owner is
94
- * tenant-scoped; only consulted to recognize a tenant-led default key
106
+ * @param tenantColumn - the table's DECLARED tenant column, when the schema
107
+ * owner is tenant-scoped; only consulted to recognize a tenant-led default
108
+ * key. Never pass an inferred ownership column here.
95
109
  */
96
110
  function conflictIndexName(tableName, conflictColumns, tenantColumn) {
97
111
  const key = tenantColumn && conflictColumns[0] === tenantColumn && isDefaultNaturalKey(conflictColumns, tenantColumn) ? conflictColumns.slice(1) : conflictColumns;
@@ -99,6 +113,22 @@ function conflictIndexName(tableName, conflictColumns, tenantColumn) {
99
113
  return `${tableName}_${(key.length > 2 ? key.slice(0, 2) : key).join("_")}_idx`;
100
114
  }
101
115
  /**
116
+ * Whether a live unique index over `indexColumns` is the LEGACY global natural
117
+ * key that a tenant-led default key superseded: its columns are exactly a
118
+ * default natural key (CTI `(slug, context)` / STI `(slug, context,
119
+ * _meta_type)`) and `widerColumns` is that same key led by one ownership
120
+ * column.
121
+ *
122
+ * The differ keeps such an index (expand, then contract) so code still
123
+ * upserting `ON CONFLICT (slug, context)` keeps working while a release that
124
+ * upserts on the tenant-led key rolls out; it is dropped only on
125
+ * `smrt db:migrate --drop-legacy-natural-key`.
126
+ */
127
+ function isLegacyNaturalKeyIndex(indexColumns, widerColumns) {
128
+ if (!sameColumns(indexColumns, DEFAULT_CTI_CONFLICT_COLUMNS) && !sameColumns(indexColumns, DEFAULT_STI_CONFLICT_COLUMNS)) return false;
129
+ return widerColumns.length === indexColumns.length + 1 && sameColumns(widerColumns.slice(1), indexColumns);
130
+ }
131
+ /**
102
132
  * Whether an unqualified index over `indexColumns` serves slug lookups
103
133
  * (`loadFromSlug()` / `getId()` / `getSavedId()` filter on `slug`, `context`):
104
134
  * it leads with `slug`, or it is the tenant-led default key — a tenant-scoped
@@ -124,6 +154,32 @@ function resolveTenantColumn(tenantField, hasField, toColumn) {
124
154
  if (!tenantField || !hasField(tenantField)) return void 0;
125
155
  return toColumn(tenantField);
126
156
  }
157
+ /** Column of {@link IMPLICIT_TENANT_FIELD}. */
158
+ var IMPLICIT_TENANT_COLUMN = "tenant_id";
159
+ /**
160
+ * The column that owns a table's rows for natural-key identity: the declared
161
+ * tenant column, otherwise the implicit `tenant_id` column when the schema
162
+ * owner has a `tenantId` field.
163
+ *
164
+ * Before this rule a class that carried `tenantId` but did not DECLARE tenancy
165
+ * (the consumer registered it with the tenancy interceptor at runtime instead)
166
+ * kept the global `(slug, context)` key. The interceptor filtered its lookups
167
+ * by tenant, so tenant B's lookup missed tenant A's same-slug row and B's
168
+ * `save()` upserted onto it: `DO UPDATE SET` rewrote A's `id` and `tenant_id`
169
+ * and `ON UPDATE CASCADE` re-pointed A's children at B's row. Rows that carry
170
+ * a tenant are unique per tenant whether or not reads are filtered, so the
171
+ * default natural key follows ownership, not the read policy. Only the
172
+ * DEFAULT key uses this; an explicit `@smrt({ conflictColumns })` is the
173
+ * author's contract and is never rewritten.
174
+ *
175
+ * @param declaredTenantColumn - the column from the class's tenancy
176
+ * declaration ({@link resolveTenantColumn}), when it has one
177
+ * @param hasColumn - whether the schema owner has the given column
178
+ */
179
+ function resolveOwnershipTenantColumn(declaredTenantColumn, hasColumn) {
180
+ if (declaredTenantColumn) return declaredTenantColumn;
181
+ return hasColumn("tenant_id") ? IMPLICIT_TENANT_COLUMN : void 0;
182
+ }
127
183
  /** Only the framework conflict identity opts into NULL-equal uniqueness. */
128
184
  function nullableConflictIdentity(conflictColumns, columns) {
129
185
  return conflictColumns.some((name) => {
@@ -132,6 +188,6 @@ function nullableConflictIdentity(conflictColumns, columns) {
132
188
  });
133
189
  }
134
190
  //#endregion
135
- export { DEFAULT_CTI_CONFLICT_COLUMNS, DEFAULT_STI_CONFLICT_COLUMNS, conflictIndexName, defaultConflictColumns, isDefaultNaturalKey, nullableConflictIdentity, resolveConflictColumns, resolveTenantColumn, servesSlugLookup };
191
+ export { DEFAULT_CTI_CONFLICT_COLUMNS, DEFAULT_STI_CONFLICT_COLUMNS, IMPLICIT_TENANT_COLUMN, conflictIndexName, defaultConflictColumns, isDefaultNaturalKey, isLegacyNaturalKeyIndex, nullableConflictIdentity, resolveConflictColumns, resolveOwnershipTenantColumn, resolveTenantColumn, servesSlugLookup };
136
192
 
137
193
  //# sourceMappingURL=conflict-target.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"conflict-target.js","names":[],"sources":["../../src/schema/conflict-target.ts"],"sourcesContent":["/**\n * Conflict target (upsert natural key) resolution shared by every schema path\n * and by the runtime (#2360, epic #2382 finding B1/A4).\n *\n * `SmrtObject.save()` upserts a NEW object on\n * `ObjectRegistry.getConflictColumns()`; the schema generator emits ONE unique\n * index over the same columns so `INSERT … ON CONFLICT (…)` binds on every\n * engine. Before #2360 the tenancy interceptor populated `tenant_id` on save\n * but neither the conflict target nor the unique index carried it, so a\n * second tenant creating the same natural key (`slug`, `context`) matched the\n * first tenant's row and `DO UPDATE SET` rewrote its `id` and `tenant_id`\n * (probed: tenant A's row was silently adopted by tenant B).\n *\n * The rule now: **a tenant-scoped class's default conflict target is\n * `[tenantColumn, ...naturalKey]`** on the build path (manifest\n * `decoratorConfig.conflictColumns`, materialized by\n * `ManifestGenerator.normalizeConflictColumns()`) and on the runtime path\n * (`ObjectRegistry.getConflictColumns()`), so schema and upsert agree by\n * construction. Explicit `@smrt({ conflictColumns })` always wins and is\n * never rewritten — a class that declares its own natural key owns it.\n *\n * NULL-tenant rows (`mode: 'optional'` outside a tenant context) keep the\n * SDK's null-aware upsert semantics: `@happyvertical/sql` matches conflict\n * columns with `IS NOT DISTINCT FROM`, so `(NULL, 'widget', '')` updates the\n * existing `(NULL, 'widget', '')` row through the model layer. PostgreSQL 15+\n * framework indexes additionally enforce this with NULLS NOT DISTINCT (#2834);\n * older engines and unmigrated indexes retain the fallback. Global rows dedup among\n * themselves exactly as every row did before, and per-tenant rows dedup per\n * tenant.\n *\n * Index NAMES stay stable across this change — the tenant-led default key\n * keeps `<table>_slug_context_idx` (CTI) / `<table>_slug_context_meta_type_idx`\n * (STI) — so `SchemaComparer`'s same-name shape-drift repair swaps the columns\n * in place (`DROP INDEX` + `CREATE UNIQUE INDEX`) instead of leaving the old\n * global unique index behind to keep blocking cross-tenant duplicates. The new\n * key is a superset of the old one, so creating it cannot fail on existing\n * data.\n */\n\n/** Default natural key of a class-per-table (CTI) object. */\nexport const DEFAULT_CTI_CONFLICT_COLUMNS: readonly string[] = [\n 'slug',\n 'context',\n];\n\n/**\n * Default natural key of a single-table-inheritance (STI) hierarchy: the\n * discriminator participates in identity so two subtypes may share a slug.\n */\nexport const DEFAULT_STI_CONFLICT_COLUMNS: readonly string[] = [\n 'slug',\n 'context',\n '_meta_type',\n];\n\nexport type ConflictTableStrategy = 'cti' | 'sti';\n\n/**\n * The default conflict target for a table with the given strategy, led by the\n * tenant column when the schema-owning class is tenant-scoped.\n */\nexport function defaultConflictColumns(\n strategy: ConflictTableStrategy,\n tenantColumn?: string | null,\n): string[] {\n const naturalKey =\n strategy === 'sti'\n ? [...DEFAULT_STI_CONFLICT_COLUMNS]\n : [...DEFAULT_CTI_CONFLICT_COLUMNS];\n return tenantColumn ? [tenantColumn, ...naturalKey] : naturalKey;\n}\n\n/**\n * Resolve the conflict target of a table.\n *\n * `explicit` (a declared `@smrt({ conflictColumns })`) always wins; otherwise\n * the strategy default, tenant-led when `tenantColumn` is given.\n */\nexport function resolveConflictColumns(options: {\n explicit?: readonly string[] | null;\n strategy: ConflictTableStrategy;\n tenantColumn?: string | null;\n}): string[] {\n if (options.explicit && options.explicit.length > 0) {\n return [...options.explicit];\n }\n return defaultConflictColumns(options.strategy, options.tenantColumn);\n}\n\nfunction sameColumns(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((column, i) => column === b[i]);\n}\n\n/**\n * Whether `conflictColumns` is exactly a default natural key (CTI or STI),\n * optionally led by `tenantColumn`. Used to keep the default index names\n * stable when the tenant column is prepended.\n */\nexport function isDefaultNaturalKey(\n conflictColumns: readonly string[],\n tenantColumn?: string | null,\n): boolean {\n const key =\n tenantColumn && conflictColumns[0] === tenantColumn\n ? conflictColumns.slice(1)\n : conflictColumns;\n return (\n sameColumns(key, DEFAULT_CTI_CONFLICT_COLUMNS) ||\n sameColumns(key, DEFAULT_STI_CONFLICT_COLUMNS)\n );\n}\n\n/**\n * The stable name of the unique conflict index for `conflictColumns`.\n *\n * - default CTI key `(slug, context)` → `<table>_slug_context_idx`\n * - default STI key `(slug, context, _meta_type)` →\n * `<table>_slug_context_meta_type_idx`\n * - either default led by the tenant column → the SAME name as without it\n * (#2360: the differ swaps the columns in place by name)\n * - any custom key → `<table>_<first two columns>_idx` (the historical rule;\n * a longer key keeps the two-column name so existing databases are not\n * renamed)\n *\n * @param tenantColumn - the table's tenant column, when the schema owner is\n * tenant-scoped; only consulted to recognize a tenant-led default key\n */\nexport function conflictIndexName(\n tableName: string,\n conflictColumns: readonly string[],\n tenantColumn?: string | null,\n): string {\n const key =\n tenantColumn &&\n conflictColumns[0] === tenantColumn &&\n isDefaultNaturalKey(conflictColumns, tenantColumn)\n ? conflictColumns.slice(1)\n : conflictColumns;\n if (sameColumns(key, DEFAULT_STI_CONFLICT_COLUMNS)) {\n return `${tableName}_slug_context_meta_type_idx`;\n }\n const nameColumns = key.length > 2 ? key.slice(0, 2) : key;\n return `${tableName}_${nameColumns.join('_')}_idx`;\n}\n\n/**\n * Whether an unqualified index over `indexColumns` serves slug lookups\n * (`loadFromSlug()` / `getId()` / `getSavedId()` filter on `slug`, `context`):\n * it leads with `slug`, or it is the tenant-led default key — a tenant-scoped\n * lookup carries the tenant predicate (#2365) and is served by the prefix.\n */\nexport function servesSlugLookup(\n indexColumns: readonly string[],\n tenantColumn?: string | null,\n): boolean {\n if (indexColumns[0] === 'slug') return true;\n return Boolean(\n tenantColumn &&\n indexColumns[0] === tenantColumn &&\n indexColumns[1] === 'slug' &&\n indexColumns[2] === 'context',\n );\n}\n\n/**\n * The tenant column of a table, from the schema-owning class's normalized\n * tenant-scoped configuration. Returns `undefined` when the class is not\n * tenant-scoped or the configured field is not among its fields (the field is\n * always injected for tenant-scoped classes; the guard keeps the runtime and\n * build paths honest about the column actually existing).\n *\n * @param tenantField - the configured tenant field name (`tenantScoped.field`,\n * default `tenantId`), or `undefined` when the class is not tenant-scoped\n * @param hasField - whether the schema owner declares that field\n * @param toColumn - field-name → column-name conversion (`toSnakeCase`)\n */\nexport function resolveTenantColumn(\n tenantField: string | undefined | null,\n hasField: (fieldName: string) => boolean,\n toColumn: (fieldName: string) => string,\n): string | undefined {\n if (!tenantField || !hasField(tenantField)) return undefined;\n return toColumn(tenantField);\n}\n\n/** Only the framework conflict identity opts into NULL-equal uniqueness. */\nexport function nullableConflictIdentity(\n conflictColumns: readonly string[],\n columns: Record<\n string,\n { notNull?: boolean; primaryKey?: boolean } | undefined\n >,\n): boolean {\n return conflictColumns.some((name) => {\n const column = columns[name];\n return column !== undefined && !column.notNull && !column.primaryKey;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,+BAAkD,CAC7D,QACA,SACF;;;;;AAMA,IAAa,+BAAkD;CAC7D;CACA;CACA;AACF;;;;;AAQA,SAAgB,uBACd,UACA,cACU;CACV,MAAM,aACJ,aAAa,QACT,CAAC,GAAG,4BAA4B,IAChC,CAAC,GAAG,4BAA4B;CACtC,OAAO,eAAe,CAAC,cAAc,GAAG,UAAU,IAAI;AACxD;;;;;;;AAQA,SAAgB,uBAAuB,SAI1B;CACX,IAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAChD,OAAO,CAAC,GAAG,QAAQ,QAAQ;CAE7B,OAAO,uBAAuB,QAAQ,UAAU,QAAQ,YAAY;AACtE;AAEA,SAAS,YAAY,GAAsB,GAA+B;CACxE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,QAAQ,MAAM,WAAW,EAAE,EAAE;AACxE;;;;;;AAOA,SAAgB,oBACd,iBACA,cACS;CACT,MAAM,MACJ,gBAAgB,gBAAgB,OAAO,eACnC,gBAAgB,MAAM,CAAC,IACvB;CACN,OACE,YAAY,KAAK,4BAA4B,KAC7C,YAAY,KAAK,4BAA4B;AAEjD;;;;;;;;;;;;;;;;AAiBA,SAAgB,kBACd,WACA,iBACA,cACQ;CACR,MAAM,MACJ,gBACA,gBAAgB,OAAO,gBACvB,oBAAoB,iBAAiB,YAAY,IAC7C,gBAAgB,MAAM,CAAC,IACvB;CACN,IAAI,YAAY,KAAK,4BAA4B,GAC/C,OAAO,GAAG,UAAU;CAGtB,OAAO,GAAG,UAAU,IADA,IAAI,SAAS,IAAI,IAAI,MAAM,GAAG,CAAC,IAAI,IAAA,CACpB,KAAK,GAAG,EAAE;AAC/C;;;;;;;AAQA,SAAgB,iBACd,cACA,cACS;CACT,IAAI,aAAa,OAAO,QAAQ,OAAO;CACvC,OAAO,QACL,gBACE,aAAa,OAAO,gBACpB,aAAa,OAAO,UACpB,aAAa,OAAO,SACxB;AACF;;;;;;;;;;;;;AAcA,SAAgB,oBACd,aACA,UACA,UACoB;CACpB,IAAI,CAAC,eAAe,CAAC,SAAS,WAAW,GAAG,OAAO,KAAA;CACnD,OAAO,SAAS,WAAW;AAC7B;;AAGA,SAAgB,yBACd,iBACA,SAIS;CACT,OAAO,gBAAgB,MAAM,SAAS;EACpC,MAAM,SAAS,QAAQ;EACvB,OAAO,WAAW,KAAA,KAAa,CAAC,OAAO,WAAW,CAAC,OAAO;CAC5D,CAAC;AACH"}
1
+ {"version":3,"file":"conflict-target.js","names":[],"sources":["../../src/schema/conflict-target.ts"],"sourcesContent":["/**\n * Conflict target (upsert natural key) resolution shared by every schema path\n * and by the runtime (#2360, epic #2382 finding B1/A4).\n *\n * `SmrtObject.save()` upserts a NEW object on\n * `ObjectRegistry.getConflictColumns()`; the schema generator emits ONE unique\n * index over the same columns so `INSERT … ON CONFLICT (…)` binds on every\n * engine. Before #2360 the tenancy interceptor populated `tenant_id` on save\n * but neither the conflict target nor the unique index carried it, so a\n * second tenant creating the same natural key (`slug`, `context`) matched the\n * first tenant's row and `DO UPDATE SET` rewrote its `id` and `tenant_id`\n * (probed: tenant A's row was silently adopted by tenant B).\n *\n * The rule now: **a tenant-scoped class's default conflict target is\n * `[tenantColumn, ...naturalKey]`** on the build path (manifest\n * `decoratorConfig.conflictColumns`, materialized by\n * `ManifestGenerator.normalizeConflictColumns()`) and on the runtime path\n * (`ObjectRegistry.getConflictColumns()`), so schema and upsert agree by\n * construction. Explicit `@smrt({ conflictColumns })` always wins and is\n * never rewritten — a class that declares its own natural key owns it.\n *\n * NULL-tenant rows (`mode: 'optional'` outside a tenant context) keep the\n * SDK's null-aware upsert semantics: `@happyvertical/sql` matches conflict\n * columns with `IS NOT DISTINCT FROM`, so `(NULL, 'widget', '')` updates the\n * existing `(NULL, 'widget', '')` row through the model layer. PostgreSQL 15+\n * framework indexes additionally enforce this with NULLS NOT DISTINCT (#2834);\n * older engines and unmigrated indexes retain the fallback. Global rows dedup among\n * themselves exactly as every row did before, and per-tenant rows dedup per\n * tenant.\n *\n * Index names:\n *\n * - A DECLARED tenant-scoped class (#2360) keeps `<table>_slug_context_idx`\n * (CTI) / `<table>_slug_context_meta_type_idx` (STI) for its tenant-led key.\n * That swap shipped long ago; live databases carry the tenant-led columns\n * under that name, and `db:migrate-null-equal-indexes` targets it.\n * - A class that is tenant-owned only by INFERENCE (an undeclared `tenantId`\n * field, {@link resolveOwnershipTenantColumn}) gets its tenant-led key under\n * a NEW name, `<table>_tenant_id_slug_idx`, created beside the legacy global\n * `<table>_slug_context_idx` (expand, then contract). Old code upserts\n * `ON CONFLICT (slug, context)` and new code `ON CONFLICT (tenant_id, slug,\n * context)`; PostgreSQL binds each only to a unique index over exactly those\n * columns, so during a rolling deploy both indexes must exist. The differ\n * keeps the legacy index ({@link isLegacyNaturalKeyIndex}) until the operator\n * runs `smrt db:migrate --drop-legacy-natural-key` once no old code is left.\n * The new key is a superset of the old one, so creating it cannot fail on\n * existing data.\n */\n\n/** Default natural key of a class-per-table (CTI) object. */\nexport const DEFAULT_CTI_CONFLICT_COLUMNS: readonly string[] = [\n 'slug',\n 'context',\n];\n\n/**\n * Default natural key of a single-table-inheritance (STI) hierarchy: the\n * discriminator participates in identity so two subtypes may share a slug.\n */\nexport const DEFAULT_STI_CONFLICT_COLUMNS: readonly string[] = [\n 'slug',\n 'context',\n '_meta_type',\n];\n\nexport type ConflictTableStrategy = 'cti' | 'sti';\n\n/**\n * The default conflict target for a table with the given strategy, led by the\n * tenant column when the schema-owning class is tenant-scoped.\n */\nexport function defaultConflictColumns(\n strategy: ConflictTableStrategy,\n tenantColumn?: string | null,\n): string[] {\n const naturalKey =\n strategy === 'sti'\n ? [...DEFAULT_STI_CONFLICT_COLUMNS]\n : [...DEFAULT_CTI_CONFLICT_COLUMNS];\n return tenantColumn ? [tenantColumn, ...naturalKey] : naturalKey;\n}\n\n/**\n * Resolve the conflict target of a table.\n *\n * `explicit` (a declared `@smrt({ conflictColumns })`) always wins; otherwise\n * the strategy default, tenant-led when `tenantColumn` is given.\n */\nexport function resolveConflictColumns(options: {\n explicit?: readonly string[] | null;\n strategy: ConflictTableStrategy;\n tenantColumn?: string | null;\n}): string[] {\n if (options.explicit && options.explicit.length > 0) {\n return [...options.explicit];\n }\n return defaultConflictColumns(options.strategy, options.tenantColumn);\n}\n\nfunction sameColumns(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((column, i) => column === b[i]);\n}\n\n/**\n * Whether `conflictColumns` is exactly a default natural key (CTI or STI),\n * optionally led by `tenantColumn`. Used to keep the default index names\n * stable when the tenant column is prepended.\n */\nexport function isDefaultNaturalKey(\n conflictColumns: readonly string[],\n tenantColumn?: string | null,\n): boolean {\n const key =\n tenantColumn && conflictColumns[0] === tenantColumn\n ? conflictColumns.slice(1)\n : conflictColumns;\n return (\n sameColumns(key, DEFAULT_CTI_CONFLICT_COLUMNS) ||\n sameColumns(key, DEFAULT_STI_CONFLICT_COLUMNS)\n );\n}\n\n/**\n * The stable name of the unique conflict index for `conflictColumns`.\n *\n * - default CTI key `(slug, context)` → `<table>_slug_context_idx`\n * - default STI key `(slug, context, _meta_type)` →\n * `<table>_slug_context_meta_type_idx`\n * - either default led by the DECLARED tenant column → the SAME name as\n * without it (#2360: the differ swapped the columns in place by name)\n * - either default led by an INFERRED ownership column (pass no\n * `tenantColumn`) → the custom-key rule, `<table>_tenant_id_slug_idx`, so it\n * is created beside the legacy global index instead of replacing it\n * - any custom key → `<table>_<first two columns>_idx` (the historical rule;\n * a longer key keeps the two-column name so existing databases are not\n * renamed)\n *\n * @param tenantColumn - the table's DECLARED tenant column, when the schema\n * owner is tenant-scoped; only consulted to recognize a tenant-led default\n * key. Never pass an inferred ownership column here.\n */\nexport function conflictIndexName(\n tableName: string,\n conflictColumns: readonly string[],\n tenantColumn?: string | null,\n): string {\n const key =\n tenantColumn &&\n conflictColumns[0] === tenantColumn &&\n isDefaultNaturalKey(conflictColumns, tenantColumn)\n ? conflictColumns.slice(1)\n : conflictColumns;\n if (sameColumns(key, DEFAULT_STI_CONFLICT_COLUMNS)) {\n return `${tableName}_slug_context_meta_type_idx`;\n }\n const nameColumns = key.length > 2 ? key.slice(0, 2) : key;\n return `${tableName}_${nameColumns.join('_')}_idx`;\n}\n\n/**\n * Whether a live unique index over `indexColumns` is the LEGACY global natural\n * key that a tenant-led default key superseded: its columns are exactly a\n * default natural key (CTI `(slug, context)` / STI `(slug, context,\n * _meta_type)`) and `widerColumns` is that same key led by one ownership\n * column.\n *\n * The differ keeps such an index (expand, then contract) so code still\n * upserting `ON CONFLICT (slug, context)` keeps working while a release that\n * upserts on the tenant-led key rolls out; it is dropped only on\n * `smrt db:migrate --drop-legacy-natural-key`.\n */\nexport function isLegacyNaturalKeyIndex(\n indexColumns: readonly string[],\n widerColumns: readonly string[],\n): boolean {\n if (\n !sameColumns(indexColumns, DEFAULT_CTI_CONFLICT_COLUMNS) &&\n !sameColumns(indexColumns, DEFAULT_STI_CONFLICT_COLUMNS)\n ) {\n return false;\n }\n return (\n widerColumns.length === indexColumns.length + 1 &&\n sameColumns(widerColumns.slice(1), indexColumns)\n );\n}\n\n/**\n * Whether an unqualified index over `indexColumns` serves slug lookups\n * (`loadFromSlug()` / `getId()` / `getSavedId()` filter on `slug`, `context`):\n * it leads with `slug`, or it is the tenant-led default key — a tenant-scoped\n * lookup carries the tenant predicate (#2365) and is served by the prefix.\n */\nexport function servesSlugLookup(\n indexColumns: readonly string[],\n tenantColumn?: string | null,\n): boolean {\n if (indexColumns[0] === 'slug') return true;\n return Boolean(\n tenantColumn &&\n indexColumns[0] === tenantColumn &&\n indexColumns[1] === 'slug' &&\n indexColumns[2] === 'context',\n );\n}\n\n/**\n * The tenant column of a table, from the schema-owning class's normalized\n * tenant-scoped configuration. Returns `undefined` when the class is not\n * tenant-scoped or the configured field is not among its fields (the field is\n * always injected for tenant-scoped classes; the guard keeps the runtime and\n * build paths honest about the column actually existing).\n *\n * @param tenantField - the configured tenant field name (`tenantScoped.field`,\n * default `tenantId`), or `undefined` when the class is not tenant-scoped\n * @param hasField - whether the schema owner declares that field\n * @param toColumn - field-name → column-name conversion (`toSnakeCase`)\n */\nexport function resolveTenantColumn(\n tenantField: string | undefined | null,\n hasField: (fieldName: string) => boolean,\n toColumn: (fieldName: string) => string,\n): string | undefined {\n if (!tenantField || !hasField(tenantField)) return undefined;\n return toColumn(tenantField);\n}\n\n/**\n * The field whose mere presence marks a table's rows as tenant-owned when the\n * class declares no tenancy (`tenantScoped` / `@TenantScoped()` / `@tenantId`).\n * It is the tenancy default field name, so it is also the field a consumer's\n * `registerTenantScopedClass(name)` filters and populates at runtime.\n */\nexport const IMPLICIT_TENANT_FIELD = 'tenantId';\n\n/** Column of {@link IMPLICIT_TENANT_FIELD}. */\nexport const IMPLICIT_TENANT_COLUMN = 'tenant_id';\n\n/**\n * The column that owns a table's rows for natural-key identity: the declared\n * tenant column, otherwise the implicit `tenant_id` column when the schema\n * owner has a `tenantId` field.\n *\n * Before this rule a class that carried `tenantId` but did not DECLARE tenancy\n * (the consumer registered it with the tenancy interceptor at runtime instead)\n * kept the global `(slug, context)` key. The interceptor filtered its lookups\n * by tenant, so tenant B's lookup missed tenant A's same-slug row and B's\n * `save()` upserted onto it: `DO UPDATE SET` rewrote A's `id` and `tenant_id`\n * and `ON UPDATE CASCADE` re-pointed A's children at B's row. Rows that carry\n * a tenant are unique per tenant whether or not reads are filtered, so the\n * default natural key follows ownership, not the read policy. Only the\n * DEFAULT key uses this; an explicit `@smrt({ conflictColumns })` is the\n * author's contract and is never rewritten.\n *\n * @param declaredTenantColumn - the column from the class's tenancy\n * declaration ({@link resolveTenantColumn}), when it has one\n * @param hasColumn - whether the schema owner has the given column\n */\nexport function resolveOwnershipTenantColumn(\n declaredTenantColumn: string | undefined | null,\n hasColumn: (columnName: string) => boolean,\n): string | undefined {\n if (declaredTenantColumn) return declaredTenantColumn;\n return hasColumn(IMPLICIT_TENANT_COLUMN) ? IMPLICIT_TENANT_COLUMN : undefined;\n}\n\n/** Only the framework conflict identity opts into NULL-equal uniqueness. */\nexport function nullableConflictIdentity(\n conflictColumns: readonly string[],\n columns: Record<\n string,\n { notNull?: boolean; primaryKey?: boolean } | undefined\n >,\n): boolean {\n return conflictColumns.some((name) => {\n const column = columns[name];\n return column !== undefined && !column.notNull && !column.primaryKey;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,+BAAkD,CAC7D,QACA,SACF;;;;;AAMA,IAAa,+BAAkD;CAC7D;CACA;CACA;AACF;;;;;AAQA,SAAgB,uBACd,UACA,cACU;CACV,MAAM,aACJ,aAAa,QACT,CAAC,GAAG,4BAA4B,IAChC,CAAC,GAAG,4BAA4B;CACtC,OAAO,eAAe,CAAC,cAAc,GAAG,UAAU,IAAI;AACxD;;;;;;;AAQA,SAAgB,uBAAuB,SAI1B;CACX,IAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAChD,OAAO,CAAC,GAAG,QAAQ,QAAQ;CAE7B,OAAO,uBAAuB,QAAQ,UAAU,QAAQ,YAAY;AACtE;AAEA,SAAS,YAAY,GAAsB,GAA+B;CACxE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,QAAQ,MAAM,WAAW,EAAE,EAAE;AACxE;;;;;;AAOA,SAAgB,oBACd,iBACA,cACS;CACT,MAAM,MACJ,gBAAgB,gBAAgB,OAAO,eACnC,gBAAgB,MAAM,CAAC,IACvB;CACN,OACE,YAAY,KAAK,4BAA4B,KAC7C,YAAY,KAAK,4BAA4B;AAEjD;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,kBACd,WACA,iBACA,cACQ;CACR,MAAM,MACJ,gBACA,gBAAgB,OAAO,gBACvB,oBAAoB,iBAAiB,YAAY,IAC7C,gBAAgB,MAAM,CAAC,IACvB;CACN,IAAI,YAAY,KAAK,4BAA4B,GAC/C,OAAO,GAAG,UAAU;CAGtB,OAAO,GAAG,UAAU,IADA,IAAI,SAAS,IAAI,IAAI,MAAM,GAAG,CAAC,IAAI,IAAA,CACpB,KAAK,GAAG,EAAE;AAC/C;;;;;;;;;;;;;AAcA,SAAgB,wBACd,cACA,cACS;CACT,IACE,CAAC,YAAY,cAAc,4BAA4B,KACvD,CAAC,YAAY,cAAc,4BAA4B,GAEvD,OAAO;CAET,OACE,aAAa,WAAW,aAAa,SAAS,KAC9C,YAAY,aAAa,MAAM,CAAC,GAAG,YAAY;AAEnD;;;;;;;AAQA,SAAgB,iBACd,cACA,cACS;CACT,IAAI,aAAa,OAAO,QAAQ,OAAO;CACvC,OAAO,QACL,gBACE,aAAa,OAAO,gBACpB,aAAa,OAAO,UACpB,aAAa,OAAO,SACxB;AACF;;;;;;;;;;;;;AAcA,SAAgB,oBACd,aACA,UACA,UACoB;CACpB,IAAI,CAAC,eAAe,CAAC,SAAS,WAAW,GAAG,OAAO,KAAA;CACnD,OAAO,SAAS,WAAW;AAC7B;;AAWA,IAAa,yBAAyB;;;;;;;;;;;;;;;;;;;;;AAsBtC,SAAgB,6BACd,sBACA,WACoB;CACpB,IAAI,sBAAsB,OAAO;CACjC,OAAO,UAAA,WAAgC,IAAI,yBAAyB,KAAA;AACtE;;AAGA,SAAgB,yBACd,iBACA,SAIS;CACT,OAAO,gBAAgB,MAAM,SAAS;EACpC,MAAM,SAAS,QAAQ;EACvB,OAAO,WAAW,KAAA,KAAa,CAAC,OAAO,WAAW,CAAC,OAAO;CAC5D,CAAC;AACH"}
@@ -2,7 +2,7 @@ import type { DatabaseEngine } from './ddl/types.js';
2
2
  import type { ForeignKeyDefinition, SchemaDefinition } from './types.js';
3
3
  export type ForeignKeyUuidCastSide = 'child' | 'parent' | 'both' | 'none';
4
4
  export declare function foreignKeyConstraintName(tableName: string, foreignKey: ForeignKeyDefinition): string;
5
- export declare function renderForeignKeyConstraint(tableName: string, foreignKey: ForeignKeyDefinition): string;
5
+ export declare function renderForeignKeyConstraint(tableName: string, foreignKey: ForeignKeyDefinition, constraintName?: string): string;
6
6
  /**
7
7
  * Render the two-statement `ADD ... NOT VALID` + `VALIDATE CONSTRAINT`
8
8
  * sequence PostgreSQL uses to add a foreign key without holding a full-table
@@ -11,6 +11,22 @@ export declare function renderForeignKeyConstraint(tableName: string, foreignKey
11
11
  * after nulling out orphan references the differ refused to plan around.
12
12
  */
13
13
  export declare function renderForeignKeyAddStatements(tableName: string, foreignKey: ForeignKeyDefinition): string[];
14
+ /**
15
+ * Replace a framework-owned PostgreSQL foreign key whose only drift is its
16
+ * referential action, without re-scanning the child table under the
17
+ * migration's locks (#3023 review):
18
+ *
19
+ * 1. `ADD CONSTRAINT <name>_smrt_new … NOT VALID` — catalog only, no scan;
20
+ * 2. `VALIDATE CONSTRAINT <name>_smrt_new` — the scan, before any
21
+ * ACCESS EXCLUSIVE lock is taken; under `--postgres-safe` the planner runs
22
+ * it (and the swap after it) outside the batch transaction, where
23
+ * VALIDATE holds only SHARE UPDATE EXCLUSIVE and reads and writes go on;
24
+ * 3. `DROP CONSTRAINT <name>` and `RENAME CONSTRAINT <name>_smrt_new TO
25
+ * <name>` — catalog only.
26
+ *
27
+ * The child column is enforced by one of the two constraints at every step.
28
+ */
29
+ export declare function renderForeignKeyReplaceStatements(tableName: string, foreignKey: ForeignKeyDefinition, liveConstraintName: string): string[];
14
30
  /** Render a PostgreSQL DROP CONSTRAINT statement for a catalog-owned FK. */
15
31
  export declare function renderForeignKeyConstraintDrop(tableName: string, constraintName: string): string;
16
32
  /** Render a PostgreSQL VALIDATE CONSTRAINT statement. */
@@ -1 +1 @@
1
- {"version":3,"file":"foreign-key-ddl.d.ts","sourceRoot":"","sources":["../../src/schema/foreign-key-ddl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1E,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,GAC/B,MAAM,CAIR;AAED,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,GAC/B,MAAM,CAmBR;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,GAC/B,MAAM,EAAE,CAMV;AAED,4EAA4E;AAC5E,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAER;AAED,yDAAyD;AACzD,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAER;AAED,mFAAmF;AACnF,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GACd,MAAM,CAER;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAClC,GACL,MAAM,CAmBR;AAED,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,MAAM,CAuBR;AAID;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,mEAC+B,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,QAKqD,CAAC;AAsCrG,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC,GAC/C,oBAAoB,EAAE,CAmBxB;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,IAAI,CACd,oBAAoB,EACpB,QAAQ,GAAG,kBAAkB,GAAG,iBAAiB,CAClD,GACA,MAAM,CAMR;AAED,yEAAyE;AACzE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC,EAChD,MAAM,EAAE,cAAc,GACrB,oBAAoB,EAAE,CAuBxB;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,cAAc,GAAG,aAAa,CAAC,CAAC,EACjE,MAAM,EAAE,cAAc,GACrB,MAAM,EAAE,CA0BV"}
1
+ {"version":3,"file":"foreign-key-ddl.d.ts","sourceRoot":"","sources":["../../src/schema/foreign-key-ddl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1E,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,GAC/B,MAAM,CAIR;AAED,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAoBR;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,GAC/B,MAAM,EAAE,CAMV;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,kBAAkB,EAAE,MAAM,GACzB,MAAM,EAAE,CAUV;AAED,4EAA4E;AAC5E,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAER;AAED,yDAAyD;AACzD,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAER;AAED,mFAAmF;AACnF,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GACd,MAAM,CAER;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAClC,GACL,MAAM,CAmBR;AAED,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,MAAM,CAuBR;AAID;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,mEAC+B,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,QAKqD,CAAC;AAsCrG,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC,GAC/C,oBAAoB,EAAE,CAmBxB;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,IAAI,CACd,oBAAoB,EACpB,QAAQ,GAAG,kBAAkB,GAAG,iBAAiB,CAClD,GACA,MAAM,CAMR;AAED,yEAAyE;AACzE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC,EAChD,MAAM,EAAE,cAAc,GACrB,oBAAoB,EAAE,CAuBxB;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,cAAc,GAAG,aAAa,CAAC,CAAC,EACjE,MAAM,EAAE,cAAc,GACrB,MAAM,EAAE,CA0BV"}
@@ -5,7 +5,7 @@ import { shortenIdentifier } from "./index-utils.js";
5
5
  function foreignKeyConstraintName(tableName, foreignKey) {
6
6
  return shortenIdentifier(`${tableName}_${foreignKey.column}_${foreignKey.referencesTable}_${foreignKey.referencesColumn}_fkey`);
7
7
  }
8
- function renderForeignKeyConstraint(tableName, foreignKey) {
8
+ function renderForeignKeyConstraint(tableName, foreignKey, constraintName) {
9
9
  const action = (value, clause) => {
10
10
  if (value === void 0) return void 0;
11
11
  return requireForeignKeyAction(value, `${tableName}.${foreignKey.column} ON ${clause}`);
@@ -13,7 +13,7 @@ function renderForeignKeyConstraint(tableName, foreignKey) {
13
13
  const onDelete = action(foreignKey.onDelete, "DELETE");
14
14
  const onUpdate = action(foreignKey.onUpdate, "UPDATE");
15
15
  const parts = [
16
- `CONSTRAINT ${quoteIdentifier(foreignKeyConstraintName(tableName, foreignKey))}`,
16
+ `CONSTRAINT ${quoteIdentifier(constraintName ?? foreignKeyConstraintName(tableName, foreignKey))}`,
17
17
  `FOREIGN KEY (${quoteIdentifier(foreignKey.column)})`,
18
18
  `REFERENCES ${quoteIdentifier(foreignKey.referencesTable)} (${quoteIdentifier(foreignKey.referencesColumn)})`
19
19
  ];
@@ -32,6 +32,32 @@ function renderForeignKeyAddStatements(tableName, foreignKey) {
32
32
  const constraintName = foreignKeyConstraintName(tableName, foreignKey);
33
33
  return [`ALTER TABLE ${quoteIdentifier(tableName)} ADD ${renderForeignKeyConstraint(tableName, foreignKey)} NOT VALID`, `ALTER TABLE ${quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${quoteIdentifier(constraintName)}`];
34
34
  }
35
+ /**
36
+ * Replace a framework-owned PostgreSQL foreign key whose only drift is its
37
+ * referential action, without re-scanning the child table under the
38
+ * migration's locks (#3023 review):
39
+ *
40
+ * 1. `ADD CONSTRAINT <name>_smrt_new … NOT VALID` — catalog only, no scan;
41
+ * 2. `VALIDATE CONSTRAINT <name>_smrt_new` — the scan, before any
42
+ * ACCESS EXCLUSIVE lock is taken; under `--postgres-safe` the planner runs
43
+ * it (and the swap after it) outside the batch transaction, where
44
+ * VALIDATE holds only SHARE UPDATE EXCLUSIVE and reads and writes go on;
45
+ * 3. `DROP CONSTRAINT <name>` and `RENAME CONSTRAINT <name>_smrt_new TO
46
+ * <name>` — catalog only.
47
+ *
48
+ * The child column is enforced by one of the two constraints at every step.
49
+ */
50
+ function renderForeignKeyReplaceStatements(tableName, foreignKey, liveConstraintName) {
51
+ const constraintName = foreignKeyConstraintName(tableName, foreignKey);
52
+ const staged = shortenIdentifier(`${constraintName}_smrt_new`);
53
+ const table = quoteIdentifier(tableName);
54
+ return [
55
+ `ALTER TABLE ${table} ADD ${renderForeignKeyConstraint(tableName, foreignKey, staged)} NOT VALID`,
56
+ `ALTER TABLE ${table} VALIDATE CONSTRAINT ${quoteIdentifier(staged)}`,
57
+ `ALTER TABLE ${table} DROP CONSTRAINT ${quoteIdentifier(liveConstraintName)}`,
58
+ `ALTER TABLE ${table} RENAME CONSTRAINT ${quoteIdentifier(staged)} TO ${quoteIdentifier(constraintName)}`
59
+ ];
60
+ }
35
61
  /** Render a PostgreSQL DROP CONSTRAINT statement for a catalog-owned FK. */
36
62
  function renderForeignKeyConstraintDrop(tableName, constraintName) {
37
63
  return `ALTER TABLE ${quoteIdentifier(tableName)} DROP CONSTRAINT ${quoteIdentifier(constraintName)};`;
@@ -140,6 +166,6 @@ function schemaDependenciesForEngine(schema, engine) {
140
166
  return Array.from(/* @__PURE__ */ new Set([...(schema.dependencies ?? []).filter((dependency) => !disabledTargets.has(dependency) || enabledTargets.has(dependency)), ...enabledTargets]));
141
167
  }
142
168
  //#endregion
143
- export { CANONICAL_UUID_PATTERN, CANONICAL_UUID_SQLITE_GLOB_PATTERN, foreignKeyConstraintName, foreignKeyRelationshipKey, renderForeignKeyAddStatements, renderForeignKeyConstraint, renderForeignKeyConstraintComment, renderForeignKeyConstraintDrop, renderForeignKeyConstraintValidate, renderForeignKeyOrphanDetector, renderForeignKeyOrphanRepair, schemaDependenciesForEngine, schemaForeignKeys, schemaForeignKeysForEngine };
169
+ export { CANONICAL_UUID_PATTERN, CANONICAL_UUID_SQLITE_GLOB_PATTERN, foreignKeyConstraintName, foreignKeyRelationshipKey, renderForeignKeyAddStatements, renderForeignKeyConstraint, renderForeignKeyConstraintComment, renderForeignKeyConstraintDrop, renderForeignKeyConstraintValidate, renderForeignKeyOrphanDetector, renderForeignKeyOrphanRepair, renderForeignKeyReplaceStatements, schemaDependenciesForEngine, schemaForeignKeys, schemaForeignKeysForEngine };
144
170
 
145
171
  //# sourceMappingURL=foreign-key-ddl.js.map