@happyvertical/smrt-core 0.52.0 → 0.53.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.
- package/AGENTS.md +1 -0
- package/README.md +8 -2
- package/agents/model-moves.md +114 -0
- package/agents/object-runtime.md +3 -1
- package/agents/run-once.md +16 -7
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +4 -2
- package/dist/cascade.js.map +1 -1
- package/dist/generators/preflight-route.d.ts.map +1 -1
- package/dist/generators/preflight-route.js +1 -1
- package/dist/generators/preflight-route.js.map +1 -1
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +3 -0
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -1
- package/dist/migrations/qualified-name-aliases.d.ts +134 -0
- package/dist/migrations/qualified-name-aliases.d.ts.map +1 -0
- package/dist/migrations/qualified-name-aliases.js +294 -0
- package/dist/migrations/qualified-name-aliases.js.map +1 -0
- package/dist/migrations.js +2 -1
- package/dist/polymorphic-association.d.ts +18 -0
- package/dist/polymorphic-association.d.ts.map +1 -1
- package/dist/polymorphic-association.js +27 -2
- package/dist/polymorphic-association.js.map +1 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +27 -4
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/name-resolver.d.ts +8 -2
- package/dist/registry/name-resolver.d.ts.map +1 -1
- package/dist/registry/name-resolver.js +17 -8
- package/dist/registry/name-resolver.js.map +1 -1
- package/dist/registry/qualified-name-aliases.d.ts +109 -0
- package/dist/registry/qualified-name-aliases.d.ts.map +1 -0
- package/dist/registry/qualified-name-aliases.js +286 -0
- package/dist/registry/qualified-name-aliases.js.map +1 -0
- package/dist/registry/relationship-graph.d.ts.map +1 -1
- package/dist/registry/relationship-graph.js +1 -1
- package/dist/registry/relationship-graph.js.map +1 -1
- package/dist/registry/types.d.ts +26 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +59 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +115 -6
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts +11 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +36 -1
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/schema/conflict-target.js +8 -1
- package/dist/schema/conflict-target.js.map +1 -1
- package/dist/scripts/browser-plain-json-regression.mjs +4 -1
- package/dist/smrt-knowledge.json +15 -9
- package/dist/utils/qualified-names.d.ts +11 -0
- package/dist/utils/qualified-names.d.ts.map +1 -1
- package/dist/utils/qualified-names.js +21 -1
- package/dist/utils/qualified-names.js.map +1 -1
- package/package.json +11 -11
|
@@ -19,6 +19,7 @@ export { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectI
|
|
|
19
19
|
export { buildMinorUnitsStatements, type MinorUnitsColumnReport, type MinorUnitsColumnState, type MinorUnitsOptions, MinorUnitsPreflightError, type MinorUnitsPreflightResult, type MinorUnitsProblemRow, type MinorUnitsRescaleOptions, type MinorUnitsRescaleResult, type MoneyColumnTarget, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, } from './minor-units.js';
|
|
20
20
|
export { collectNullEqualIndexTargets, migrateNullEqualIndexes, type NullEqualIndexOptions, type NullEqualIndexPreflight, type NullEqualIndexReport, type NullEqualIndexTarget, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes, } from './null-equal-indexes.js';
|
|
21
21
|
export { getPendingSchemaStatements, type MigrateSmrtSchemasOptions, type MigrateSmrtSchemasResult, migrateSmrtSchemas, type PendingSchemaStatementsResult, } from './orchestrate.js';
|
|
22
|
+
export { backfillLegacyQualifiedNames, collectLegacyQualifiedNameTargets, countLegacyQualifiedNameReferences, LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX, type LegacyQualifiedNameBackfillOptions, type LegacyQualifiedNameBackfillResult, type LegacyQualifiedNameOptions, type LegacyQualifiedNameReference, type LegacyQualifiedNameReport, type LegacyQualifiedNameTarget, legacyQualifiedNamesBackfillName, } from './qualified-name-aliases.js';
|
|
22
23
|
export { type BuildSqliteRebuildStatementsInput, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, type PlanSqliteTableRebuildsOptions, planSqliteTableRebuilds, rewriteSqliteCreateTable, SQLITE_REBUILD_TABLE_PREFIX, type SqliteTableRebuildPlan, sqliteRebuildPlaceholderSql, } from './sqlite-rebuild.js';
|
|
23
24
|
export { buildConcurrentIndexPlan, extractCreatedIndexName, MigrationTracker, parsePostgresTimeoutMs, planPostgresStatements, } from './tracker.js';
|
|
24
25
|
export type { ApplyMigrationsOptions, ColumnDefinitionWithName, ColumnInfo, DatabaseEngine, DatabaseInterface, DataMigration, DataMigrationResult, DataMigrationStatement, DriftReport, ForeignKeyInfo, IndexDefinition, IndexInfo, MigrationDefinition, MigrationMode, MigrationResult, MigrationStatus, MigrationsConfig, MigrationTrackerOptions, ParsedMigrationFile, ParseMigrationOptions, RollbackOptions, SchemaChange, SchemaDiff, SchemaMigrationRecord, StiDiscriminatorMigration, TableSchemaInfo, } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,KAAK,cAAc,EACnB,6BAA6B,EAC7B,eAAe,EACf,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,yBAAyB,EACzB,cAAc,EACd,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,0BAA0B,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,6BAA6B,GACnC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,4BAA4B,EAC5B,iCAAiC,EACjC,kCAAkC,EAClC,sCAAsC,EACtC,KAAK,kCAAkC,EACvC,KAAK,iCAAiC,EACtC,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,gCAAgC,GACjC,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,KAAK,iCAAiC,EACtC,4BAA4B,EAC5B,0BAA0B,EAC1B,KAAK,8BAA8B,EACnC,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,sBAAsB,EACtB,wBAAwB,EACxB,UAAU,EACV,cAAc,EACd,iBAAiB,EAEjB,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,eAAe,EACf,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,eAAe,EAEf,eAAe,EACf,gBAAgB,EAEhB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
package/dist/migrations/index.js
CHANGED
|
@@ -10,4 +10,5 @@ import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnit
|
|
|
10
10
|
import { collectNullEqualIndexTargets, migrateNullEqualIndexes, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes } from "./null-equal-indexes.js";
|
|
11
11
|
import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./tracker.js";
|
|
12
12
|
import { getPendingSchemaStatements, migrateSmrtSchemas } from "./orchestrate.js";
|
|
13
|
-
|
|
13
|
+
import { LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX, backfillLegacyQualifiedNames, collectLegacyQualifiedNameTargets, countLegacyQualifiedNameReferences, legacyQualifiedNamesBackfillName } from "./qualified-name-aliases.js";
|
|
14
|
+
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, REQUIRED_COLUMN_NOT_ADDED, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, backfillLegacyQualifiedNames, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectLegacyQualifiedNameTargets, collectNullEqualIndexTargets, computeChecksum, countLegacyQualifiedNameReferences, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, legacyQualifiedNamesBackfillName, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored references to deprecated qualified names (#3338).
|
|
3
|
+
*
|
|
4
|
+
* `@smrt({ previousQualifiedNames })` keeps an old qualified name resolving
|
|
5
|
+
* after a class moves package; it never rewrites stored data. This module is
|
|
6
|
+
* the operator side of that bridge:
|
|
7
|
+
*
|
|
8
|
+
* - {@link countLegacyQualifiedNameReferences} — read-only counts of rows that
|
|
9
|
+
* still store an old name, which `smrt doctor --db` reports so a consumer
|
|
10
|
+
* can see when an alias is safe to remove;
|
|
11
|
+
* - {@link backfillLegacyQualifiedNames} — an opt-in, idempotent rewrite of
|
|
12
|
+
* those rows to the current name. It never runs automatically.
|
|
13
|
+
*
|
|
14
|
+
* Stored references covered:
|
|
15
|
+
*
|
|
16
|
+
* - `meta_type` of every registered `SmrtPolymorphicAssociation` subclass
|
|
17
|
+
* table (any class can be a polymorphic target, so every alias is counted);
|
|
18
|
+
* - `_meta_type` of an STI table whose family includes an aliased class.
|
|
19
|
+
*
|
|
20
|
+
* Removing an alias is a breaking change for any row this still counts.
|
|
21
|
+
*/
|
|
22
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
23
|
+
/** Prefix of the `_smrt_backfills` marker {@link backfillLegacyQualifiedNames} records. */
|
|
24
|
+
export declare const LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX = "@happyvertical/smrt-core:qualified-name-aliases:v1";
|
|
25
|
+
/** One table column that can store a class's qualified name. */
|
|
26
|
+
export interface LegacyQualifiedNameTarget {
|
|
27
|
+
/** Registered class that owns the table (qualified when available). */
|
|
28
|
+
className: string;
|
|
29
|
+
table: string;
|
|
30
|
+
/** `meta_type` (polymorphic association) or `_meta_type` (STI discriminator). */
|
|
31
|
+
column: string;
|
|
32
|
+
kind: 'polymorphic' | 'sti';
|
|
33
|
+
/** Deprecated names this column may still hold. */
|
|
34
|
+
aliases: string[];
|
|
35
|
+
/** Tenant column of the table, when tenant-owned. */
|
|
36
|
+
tenantColumn?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Upsert identity columns that include `column`. A rewrite that would
|
|
39
|
+
* duplicate a current-name row on these columns is skipped, never forced.
|
|
40
|
+
*/
|
|
41
|
+
conflictColumns?: string[];
|
|
42
|
+
}
|
|
43
|
+
/** Rows of one target that still store one deprecated name. */
|
|
44
|
+
export interface LegacyQualifiedNameReference {
|
|
45
|
+
table: string;
|
|
46
|
+
column: string;
|
|
47
|
+
kind: 'polymorphic' | 'sti';
|
|
48
|
+
alias: string;
|
|
49
|
+
current: string;
|
|
50
|
+
count: number;
|
|
51
|
+
}
|
|
52
|
+
export interface LegacyQualifiedNameReport {
|
|
53
|
+
/** Declared alias → current qualified name, sorted by alias. */
|
|
54
|
+
aliases: Array<{
|
|
55
|
+
alias: string;
|
|
56
|
+
current: string;
|
|
57
|
+
}>;
|
|
58
|
+
/** Non-zero counts only, sorted by table, column, alias. */
|
|
59
|
+
references: LegacyQualifiedNameReference[];
|
|
60
|
+
/** Sum of `references[].count`. */
|
|
61
|
+
total: number;
|
|
62
|
+
/** Target tables absent from this database (nothing to count). */
|
|
63
|
+
missingTables: string[];
|
|
64
|
+
/** Target tables excluded from a tenant-scoped run (no tenant column). */
|
|
65
|
+
untenantedTables: string[];
|
|
66
|
+
}
|
|
67
|
+
export interface LegacyQualifiedNameOptions {
|
|
68
|
+
/**
|
|
69
|
+
* Restrict counting/rewriting to one tenant's rows. Tables without a tenant
|
|
70
|
+
* column are then skipped (reported in `untenantedTables`), never touched.
|
|
71
|
+
*/
|
|
72
|
+
tenantId?: string;
|
|
73
|
+
/** Engine hint when the adapter URL does not identify it. */
|
|
74
|
+
engineHint?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface LegacyQualifiedNameBackfillOptions extends LegacyQualifiedNameOptions {
|
|
77
|
+
/** Report what would change without writing (no marker is recorded). */
|
|
78
|
+
dryRun?: boolean;
|
|
79
|
+
/** Re-run even when this alias set's marker is already recorded. */
|
|
80
|
+
force?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface LegacyQualifiedNameBackfillResult {
|
|
83
|
+
/** `false` when the marker was already recorded (and `force` was unset) or on a dry run. */
|
|
84
|
+
ran: boolean;
|
|
85
|
+
dryRun: boolean;
|
|
86
|
+
/** The `_smrt_backfills` marker for this alias set and scope. */
|
|
87
|
+
backfillName: string;
|
|
88
|
+
/** Counts before the rewrite. */
|
|
89
|
+
before: LegacyQualifiedNameReport;
|
|
90
|
+
/** Rows rewritten per table/column/alias. */
|
|
91
|
+
rewritten: LegacyQualifiedNameReference[];
|
|
92
|
+
/**
|
|
93
|
+
* Rows left under their old name because the rewrite would duplicate a row
|
|
94
|
+
* already stored under the current name (resolve those by hand, then re-run).
|
|
95
|
+
*/
|
|
96
|
+
skippedDuplicates: LegacyQualifiedNameReference[];
|
|
97
|
+
/** Whether the marker was recorded (only when nothing remains in scope). */
|
|
98
|
+
recorded: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Every table column that can store a deprecated qualified name, derived from
|
|
102
|
+
* the booted registry. Empty when no class declares `previousQualifiedNames`.
|
|
103
|
+
*/
|
|
104
|
+
export declare function collectLegacyQualifiedNameTargets(): LegacyQualifiedNameTarget[];
|
|
105
|
+
/**
|
|
106
|
+
* Count rows that still store a deprecated qualified name. Read-only.
|
|
107
|
+
*
|
|
108
|
+
* Absent tables are reported in `missingTables`, not as errors: the registry
|
|
109
|
+
* can carry classes this database never migrated.
|
|
110
|
+
*/
|
|
111
|
+
export declare function countLegacyQualifiedNameReferences(db: DatabaseInterface, options?: LegacyQualifiedNameOptions): Promise<LegacyQualifiedNameReport>;
|
|
112
|
+
/**
|
|
113
|
+
* Stable `_smrt_backfills` marker for the declared alias set and scope. A new
|
|
114
|
+
* alias (another model move) produces a new marker, so the backfill runs
|
|
115
|
+
* again for it.
|
|
116
|
+
*/
|
|
117
|
+
export declare function legacyQualifiedNamesBackfillName(pairs?: Array<{
|
|
118
|
+
alias: string;
|
|
119
|
+
current: string;
|
|
120
|
+
}>, tenantId?: string): string;
|
|
121
|
+
/**
|
|
122
|
+
* Rewrite stored deprecated qualified names to their current names.
|
|
123
|
+
*
|
|
124
|
+
* Opt-in and idempotent: it never runs on its own, rewrites only rows whose
|
|
125
|
+
* value is exactly a declared old name, and records a `_smrt_backfills`
|
|
126
|
+
* marker (per alias set and tenant scope) once nothing in scope remains, so a
|
|
127
|
+
* second call is a no-op unless `force` is set. Rows are rewritten inside one
|
|
128
|
+
* transaction where the adapter provides one; the tenant column, ids and all
|
|
129
|
+
* other values are never touched, and a tenant-scoped run only touches that
|
|
130
|
+
* tenant's rows in tenant-owned tables. A row whose rewrite would duplicate a
|
|
131
|
+
* row already stored under the current name is skipped and reported.
|
|
132
|
+
*/
|
|
133
|
+
export declare function backfillLegacyQualifiedNames(db: DatabaseInterface, options?: LegacyQualifiedNameBackfillOptions): Promise<LegacyQualifiedNameBackfillResult>;
|
|
134
|
+
//# sourceMappingURL=qualified-name-aliases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qualified-name-aliases.d.ts","sourceRoot":"","sources":["../../src/migrations/qualified-name-aliases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAa5D,2FAA2F;AAC3F,eAAO,MAAM,sCAAsC,uDACG,CAAC;AAEvD,gEAAgE;AAChE,MAAM,WAAW,yBAAyB;IACxC,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,GAAG,KAAK,CAAC;IAC5B,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,+DAA+D;AAC/D,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,GAAG,KAAK,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,gEAAgE;IAChE,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,4DAA4D;IAC5D,UAAU,EAAE,4BAA4B,EAAE,CAAC;IAC3C,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kCACf,SAAQ,0BAA0B;IAClC,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iCAAiC;IAChD,4FAA4F;IAC5F,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,MAAM,EAAE,yBAAyB,CAAC;IAClC,6CAA6C;IAC7C,SAAS,EAAE,4BAA4B,EAAE,CAAC;IAC1C;;;OAGG;IACH,iBAAiB,EAAE,4BAA4B,EAAE,CAAC;IAClD,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAmDD;;;GAGG;AACH,wBAAgB,iCAAiC,IAAI,yBAAyB,EAAE,CA6D/E;AA8CD;;;;;GAKG;AACH,wBAAsB,kCAAkC,CACtD,EAAE,EAAE,iBAAiB,EACrB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,yBAAyB,CAAC,CA4CpC;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,GAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAgB,EAC/D,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAaR;AA+CD;;;;;;;;;;;GAWG;AACH,wBAAsB,4BAA4B,CAChD,EAAE,EAAE,iBAAiB,EACrB,OAAO,GAAE,kCAAuC,GAC/C,OAAO,CAAC,iCAAiC,CAAC,CAiF5C"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { IMPLICIT_TENANT_COLUMN } from "../schema/conflict-target.js";
|
|
2
|
+
import { toSnakeCase } from "../utils/naming.js";
|
|
3
|
+
import { detectEngine } from "../schema/ddl/index.js";
|
|
4
|
+
import { tableExists } from "../system/compatibility.js";
|
|
5
|
+
import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
|
|
6
|
+
import { isSmrtCollectionExtendsName } from "../registry/collection-resolution.js";
|
|
7
|
+
import { ObjectRegistry } from "../registry.js";
|
|
8
|
+
import { BackfillTracker } from "./backfill-tracker.js";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
//#region src/migrations/qualified-name-aliases.ts
|
|
11
|
+
/**
|
|
12
|
+
* Stored references to deprecated qualified names (#3338).
|
|
13
|
+
*
|
|
14
|
+
* `@smrt({ previousQualifiedNames })` keeps an old qualified name resolving
|
|
15
|
+
* after a class moves package; it never rewrites stored data. This module is
|
|
16
|
+
* the operator side of that bridge:
|
|
17
|
+
*
|
|
18
|
+
* - {@link countLegacyQualifiedNameReferences} — read-only counts of rows that
|
|
19
|
+
* still store an old name, which `smrt doctor --db` reports so a consumer
|
|
20
|
+
* can see when an alias is safe to remove;
|
|
21
|
+
* - {@link backfillLegacyQualifiedNames} — an opt-in, idempotent rewrite of
|
|
22
|
+
* those rows to the current name. It never runs automatically.
|
|
23
|
+
*
|
|
24
|
+
* Stored references covered:
|
|
25
|
+
*
|
|
26
|
+
* - `meta_type` of every registered `SmrtPolymorphicAssociation` subclass
|
|
27
|
+
* table (any class can be a polymorphic target, so every alias is counted);
|
|
28
|
+
* - `_meta_type` of an STI table whose family includes an aliased class.
|
|
29
|
+
*
|
|
30
|
+
* Removing an alias is a breaking change for any row this still counts.
|
|
31
|
+
*/
|
|
32
|
+
/** Prefix of the `_smrt_backfills` marker {@link backfillLegacyQualifiedNames} records. */
|
|
33
|
+
var LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX = "@happyvertical/smrt-core:qualified-name-aliases:v1";
|
|
34
|
+
function compareText(left, right) {
|
|
35
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
function aliasPairs() {
|
|
38
|
+
return [...ObjectRegistry.getQualifiedNameAliases()].map(([alias, current]) => ({
|
|
39
|
+
alias,
|
|
40
|
+
current
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function quoteIdentifier(name) {
|
|
44
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) throw new Error(`Refusing to quote unexpected identifier "${name}"`);
|
|
45
|
+
return `"${name}"`;
|
|
46
|
+
}
|
|
47
|
+
function isPolymorphicAssociationFields(fields) {
|
|
48
|
+
return fields.has("metaType") && fields.has("metaId") && fields.has("role");
|
|
49
|
+
}
|
|
50
|
+
function safeConflictColumns(className) {
|
|
51
|
+
try {
|
|
52
|
+
return ObjectRegistry.getConflictColumns(className).map((column) => toSnakeCase(column));
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function safeTenantColumn(className) {
|
|
58
|
+
let declared;
|
|
59
|
+
try {
|
|
60
|
+
declared = ObjectRegistry.getTenantColumn(className);
|
|
61
|
+
} catch {
|
|
62
|
+
declared = void 0;
|
|
63
|
+
}
|
|
64
|
+
if (declared) return declared;
|
|
65
|
+
return ObjectRegistry.getFields(className).has("tenantId") ? IMPLICIT_TENANT_COLUMN : void 0;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Every table column that can store a deprecated qualified name, derived from
|
|
69
|
+
* the booted registry. Empty when no class declares `previousQualifiedNames`.
|
|
70
|
+
*/
|
|
71
|
+
function collectLegacyQualifiedNameTargets() {
|
|
72
|
+
const pairs = aliasPairs();
|
|
73
|
+
if (pairs.length === 0) return [];
|
|
74
|
+
const allAliases = pairs.map((pair) => pair.alias);
|
|
75
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
76
|
+
const add = (target) => {
|
|
77
|
+
const key = `${target.table}\u0000${target.column}`;
|
|
78
|
+
const existing = byKey.get(key);
|
|
79
|
+
if (!existing) {
|
|
80
|
+
byKey.set(key, target);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
existing.aliases = [.../* @__PURE__ */ new Set([...existing.aliases, ...target.aliases])].sort(compareText);
|
|
84
|
+
};
|
|
85
|
+
for (const className of ObjectRegistry.getQualifiedClassNames()) {
|
|
86
|
+
const registered = ObjectRegistry.getClass(className);
|
|
87
|
+
if (!registered) continue;
|
|
88
|
+
if (isSmrtCollectionExtendsName(registered.extends)) continue;
|
|
89
|
+
if (isFrameworkBaseClass(registered.name, registered.packageName)) continue;
|
|
90
|
+
if (!isPolymorphicAssociationFields(registered.fields)) continue;
|
|
91
|
+
const table = ObjectRegistry.getTableName(className);
|
|
92
|
+
if (!table) continue;
|
|
93
|
+
const conflictColumns = safeConflictColumns(className);
|
|
94
|
+
const tenantColumn = safeTenantColumn(className);
|
|
95
|
+
add({
|
|
96
|
+
className,
|
|
97
|
+
table,
|
|
98
|
+
column: "meta_type",
|
|
99
|
+
kind: "polymorphic",
|
|
100
|
+
aliases: [...allAliases].sort(compareText),
|
|
101
|
+
...tenantColumn ? { tenantColumn } : {},
|
|
102
|
+
...conflictColumns.includes("meta_type") ? { conflictColumns } : {}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const { alias, current } of pairs) {
|
|
106
|
+
if (ObjectRegistry.getTableStrategy(current) !== "sti") continue;
|
|
107
|
+
const table = ObjectRegistry.getTableName(current);
|
|
108
|
+
if (!table) continue;
|
|
109
|
+
const conflictColumns = safeConflictColumns(current);
|
|
110
|
+
const tenantColumn = safeTenantColumn(current);
|
|
111
|
+
add({
|
|
112
|
+
className: current,
|
|
113
|
+
table,
|
|
114
|
+
column: "_meta_type",
|
|
115
|
+
kind: "sti",
|
|
116
|
+
aliases: [alias],
|
|
117
|
+
...tenantColumn ? { tenantColumn } : {},
|
|
118
|
+
...conflictColumns.includes("_meta_type") ? { conflictColumns } : {}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return [...byKey.values()].sort((left, right) => compareText(left.table, right.table) || compareText(left.column, right.column));
|
|
122
|
+
}
|
|
123
|
+
function rowsOf(result) {
|
|
124
|
+
if (Array.isArray(result)) return result;
|
|
125
|
+
if (result && typeof result === "object" && "rows" in result) {
|
|
126
|
+
const rows = result.rows;
|
|
127
|
+
if (Array.isArray(rows)) return rows;
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
function resolveEngine(db, engineHint) {
|
|
132
|
+
const withConfig = db;
|
|
133
|
+
return detectEngine(db.url || withConfig.config?.url || "", engineHint || withConfig.type || withConfig.config?.type);
|
|
134
|
+
}
|
|
135
|
+
async function countTarget(db, target, tenantId) {
|
|
136
|
+
const counts = /* @__PURE__ */ new Map();
|
|
137
|
+
if (target.aliases.length === 0) return counts;
|
|
138
|
+
const column = quoteIdentifier(target.column);
|
|
139
|
+
const params = [...target.aliases];
|
|
140
|
+
let sql = `SELECT ${column} AS name, COUNT(*) AS n FROM ${quoteIdentifier(target.table)} WHERE ${column} IN (${target.aliases.map(() => "?").join(", ")})`;
|
|
141
|
+
if (tenantId !== void 0 && target.tenantColumn) {
|
|
142
|
+
sql += ` AND ${quoteIdentifier(target.tenantColumn)} = ?`;
|
|
143
|
+
params.push(tenantId);
|
|
144
|
+
}
|
|
145
|
+
sql += ` GROUP BY ${column}`;
|
|
146
|
+
for (const row of rowsOf(await db.query(sql, ...params))) {
|
|
147
|
+
const count = Number(row.n ?? 0);
|
|
148
|
+
if (count > 0) counts.set(String(row.name), count);
|
|
149
|
+
}
|
|
150
|
+
return counts;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Count rows that still store a deprecated qualified name. Read-only.
|
|
154
|
+
*
|
|
155
|
+
* Absent tables are reported in `missingTables`, not as errors: the registry
|
|
156
|
+
* can carry classes this database never migrated.
|
|
157
|
+
*/
|
|
158
|
+
async function countLegacyQualifiedNameReferences(db, options = {}) {
|
|
159
|
+
const pairs = aliasPairs();
|
|
160
|
+
const currentByAlias = new Map(pairs.map((pair) => [pair.alias, pair]));
|
|
161
|
+
const report = {
|
|
162
|
+
aliases: pairs,
|
|
163
|
+
references: [],
|
|
164
|
+
total: 0,
|
|
165
|
+
missingTables: [],
|
|
166
|
+
untenantedTables: []
|
|
167
|
+
};
|
|
168
|
+
const engine = resolveEngine(db, options.engineHint);
|
|
169
|
+
for (const target of collectLegacyQualifiedNameTargets()) {
|
|
170
|
+
if (options.tenantId !== void 0 && !target.tenantColumn) {
|
|
171
|
+
if (!report.untenantedTables.includes(target.table)) report.untenantedTables.push(target.table);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (!await tableExists(db, target.table, engine)) {
|
|
175
|
+
if (!report.missingTables.includes(target.table)) report.missingTables.push(target.table);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const counts = await countTarget(db, target, options.tenantId);
|
|
179
|
+
for (const alias of target.aliases) {
|
|
180
|
+
const count = counts.get(alias) ?? 0;
|
|
181
|
+
if (count === 0) continue;
|
|
182
|
+
report.references.push({
|
|
183
|
+
table: target.table,
|
|
184
|
+
column: target.column,
|
|
185
|
+
kind: target.kind,
|
|
186
|
+
alias,
|
|
187
|
+
current: currentByAlias.get(alias)?.current ?? alias,
|
|
188
|
+
count
|
|
189
|
+
});
|
|
190
|
+
report.total += count;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
report.missingTables.sort(compareText);
|
|
194
|
+
report.untenantedTables.sort(compareText);
|
|
195
|
+
return report;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Stable `_smrt_backfills` marker for the declared alias set and scope. A new
|
|
199
|
+
* alias (another model move) produces a new marker, so the backfill runs
|
|
200
|
+
* again for it.
|
|
201
|
+
*/
|
|
202
|
+
function legacyQualifiedNamesBackfillName(pairs = aliasPairs(), tenantId) {
|
|
203
|
+
const digest = createHash("sha256").update([...pairs].sort((left, right) => compareText(left.alias, right.alias)).map(({ alias, current }) => `${alias}=>${current}`).join("\n")).digest("hex").slice(0, 16);
|
|
204
|
+
return tenantId === void 0 ? `${LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX}:${digest}` : `${LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX}:${digest}:tenant:${tenantId}`;
|
|
205
|
+
}
|
|
206
|
+
function nullSafeEquals(engine, left, right) {
|
|
207
|
+
return engine === "sqlite" ? `${left} IS ${right}` : `${left} IS NOT DISTINCT FROM ${right}`;
|
|
208
|
+
}
|
|
209
|
+
function buildRewriteStatement(engine, target, tenantId, alias, current) {
|
|
210
|
+
const table = quoteIdentifier(target.table);
|
|
211
|
+
const column = quoteIdentifier(target.column);
|
|
212
|
+
const params = [current, alias];
|
|
213
|
+
let sql = `UPDATE ${table} SET ${column} = ? WHERE ${column} = ?`;
|
|
214
|
+
if (tenantId !== void 0 && target.tenantColumn) {
|
|
215
|
+
sql += ` AND ${quoteIdentifier(target.tenantColumn)} = ?`;
|
|
216
|
+
params.push(tenantId);
|
|
217
|
+
}
|
|
218
|
+
const identity = (target.conflictColumns ?? []).filter((name) => name !== target.column);
|
|
219
|
+
if (target.conflictColumns) {
|
|
220
|
+
const correlated = identity.map((name) => nullSafeEquals(engine, `__smrt_current.${quoteIdentifier(name)}`, `${table}.${quoteIdentifier(name)}`)).join(" AND ");
|
|
221
|
+
sql += ` AND NOT EXISTS (SELECT 1 FROM ${table} AS __smrt_current WHERE __smrt_current.${column} = ?${correlated ? ` AND ${correlated}` : ""})`;
|
|
222
|
+
params.push(current);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
sql,
|
|
226
|
+
params
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Rewrite stored deprecated qualified names to their current names.
|
|
231
|
+
*
|
|
232
|
+
* Opt-in and idempotent: it never runs on its own, rewrites only rows whose
|
|
233
|
+
* value is exactly a declared old name, and records a `_smrt_backfills`
|
|
234
|
+
* marker (per alias set and tenant scope) once nothing in scope remains, so a
|
|
235
|
+
* second call is a no-op unless `force` is set. Rows are rewritten inside one
|
|
236
|
+
* transaction where the adapter provides one; the tenant column, ids and all
|
|
237
|
+
* other values are never touched, and a tenant-scoped run only touches that
|
|
238
|
+
* tenant's rows in tenant-owned tables. A row whose rewrite would duplicate a
|
|
239
|
+
* row already stored under the current name is skipped and reported.
|
|
240
|
+
*/
|
|
241
|
+
async function backfillLegacyQualifiedNames(db, options = {}) {
|
|
242
|
+
const pairs = aliasPairs();
|
|
243
|
+
const backfillName = legacyQualifiedNamesBackfillName(pairs, options.tenantId);
|
|
244
|
+
const dryRun = options.dryRun === true;
|
|
245
|
+
const before = await countLegacyQualifiedNameReferences(db, options);
|
|
246
|
+
const result = {
|
|
247
|
+
ran: false,
|
|
248
|
+
dryRun,
|
|
249
|
+
backfillName,
|
|
250
|
+
before,
|
|
251
|
+
rewritten: [],
|
|
252
|
+
skippedDuplicates: [],
|
|
253
|
+
recorded: false
|
|
254
|
+
};
|
|
255
|
+
if (pairs.length === 0 || dryRun) return result;
|
|
256
|
+
const tracker = new BackfillTracker({ db });
|
|
257
|
+
if (!options.force && await tracker.isApplied(backfillName)) return result;
|
|
258
|
+
const engine = resolveEngine(db, options.engineHint);
|
|
259
|
+
const targets = new Map(collectLegacyQualifiedNameTargets().map((target) => [`${target.table}\u0000${target.column}`, target]));
|
|
260
|
+
const rewrite = async (handle) => {
|
|
261
|
+
for (const reference of before.references) {
|
|
262
|
+
const target = targets.get(`${reference.table}\u0000${reference.column}`);
|
|
263
|
+
if (!target) continue;
|
|
264
|
+
const { sql, params } = buildRewriteStatement(engine, target, options.tenantId, reference.alias, reference.current);
|
|
265
|
+
await handle.query(sql, ...params);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
if (typeof db.transaction === "function") await db.transaction(async (tx) => rewrite(tx));
|
|
269
|
+
else await rewrite(db);
|
|
270
|
+
const after = await countLegacyQualifiedNameReferences(db, options);
|
|
271
|
+
const remaining = new Map(after.references.map((reference) => [`${reference.table}\u0000${reference.column}\u0000${reference.alias}`, reference]));
|
|
272
|
+
for (const reference of before.references) {
|
|
273
|
+
const left = remaining.get(`${reference.table}\u0000${reference.column}\u0000${reference.alias}`);
|
|
274
|
+
const rewrittenCount = reference.count - (left?.count ?? 0);
|
|
275
|
+
if (rewrittenCount > 0) result.rewritten.push({
|
|
276
|
+
...reference,
|
|
277
|
+
count: rewrittenCount
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
result.skippedDuplicates = after.references;
|
|
281
|
+
result.ran = true;
|
|
282
|
+
if (after.total === 0) {
|
|
283
|
+
await tracker.recordApplied(backfillName, {
|
|
284
|
+
description: "Stored deprecated qualified names rewritten to current names: " + pairs.map(({ alias, current }) => `${alias} -> ${current}`).join(", "),
|
|
285
|
+
packageName: "@happyvertical/smrt-core"
|
|
286
|
+
});
|
|
287
|
+
result.recorded = true;
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
292
|
+
export { LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX, backfillLegacyQualifiedNames, collectLegacyQualifiedNameTargets, countLegacyQualifiedNameReferences, legacyQualifiedNamesBackfillName };
|
|
293
|
+
|
|
294
|
+
//# sourceMappingURL=qualified-name-aliases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qualified-name-aliases.js","names":[],"sources":["../../src/migrations/qualified-name-aliases.ts"],"sourcesContent":["/**\n * Stored references to deprecated qualified names (#3338).\n *\n * `@smrt({ previousQualifiedNames })` keeps an old qualified name resolving\n * after a class moves package; it never rewrites stored data. This module is\n * the operator side of that bridge:\n *\n * - {@link countLegacyQualifiedNameReferences} — read-only counts of rows that\n * still store an old name, which `smrt doctor --db` reports so a consumer\n * can see when an alias is safe to remove;\n * - {@link backfillLegacyQualifiedNames} — an opt-in, idempotent rewrite of\n * those rows to the current name. It never runs automatically.\n *\n * Stored references covered:\n *\n * - `meta_type` of every registered `SmrtPolymorphicAssociation` subclass\n * table (any class can be a polymorphic target, so every alias is counted);\n * - `_meta_type` of an STI table whose family includes an aliased class.\n *\n * Removing an alias is a breaking change for any row this still counts.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { ObjectRegistry } from '../registry';\nimport { isSmrtCollectionExtendsName } from '../registry/collection-resolution.js';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport {\n IMPLICIT_TENANT_COLUMN,\n IMPLICIT_TENANT_FIELD,\n} from '../schema/conflict-target.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport { tableExists } from '../system/compatibility.js';\nimport { toSnakeCase } from '../utils/naming.js';\nimport { BackfillTracker } from './backfill-tracker.js';\n\n/** Prefix of the `_smrt_backfills` marker {@link backfillLegacyQualifiedNames} records. */\nexport const LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX =\n '@happyvertical/smrt-core:qualified-name-aliases:v1';\n\n/** One table column that can store a class's qualified name. */\nexport interface LegacyQualifiedNameTarget {\n /** Registered class that owns the table (qualified when available). */\n className: string;\n table: string;\n /** `meta_type` (polymorphic association) or `_meta_type` (STI discriminator). */\n column: string;\n kind: 'polymorphic' | 'sti';\n /** Deprecated names this column may still hold. */\n aliases: string[];\n /** Tenant column of the table, when tenant-owned. */\n tenantColumn?: string;\n /**\n * Upsert identity columns that include `column`. A rewrite that would\n * duplicate a current-name row on these columns is skipped, never forced.\n */\n conflictColumns?: string[];\n}\n\n/** Rows of one target that still store one deprecated name. */\nexport interface LegacyQualifiedNameReference {\n table: string;\n column: string;\n kind: 'polymorphic' | 'sti';\n alias: string;\n current: string;\n count: number;\n}\n\nexport interface LegacyQualifiedNameReport {\n /** Declared alias → current qualified name, sorted by alias. */\n aliases: Array<{ alias: string; current: string }>;\n /** Non-zero counts only, sorted by table, column, alias. */\n references: LegacyQualifiedNameReference[];\n /** Sum of `references[].count`. */\n total: number;\n /** Target tables absent from this database (nothing to count). */\n missingTables: string[];\n /** Target tables excluded from a tenant-scoped run (no tenant column). */\n untenantedTables: string[];\n}\n\nexport interface LegacyQualifiedNameOptions {\n /**\n * Restrict counting/rewriting to one tenant's rows. Tables without a tenant\n * column are then skipped (reported in `untenantedTables`), never touched.\n */\n tenantId?: string;\n /** Engine hint when the adapter URL does not identify it. */\n engineHint?: string;\n}\n\nexport interface LegacyQualifiedNameBackfillOptions\n extends LegacyQualifiedNameOptions {\n /** Report what would change without writing (no marker is recorded). */\n dryRun?: boolean;\n /** Re-run even when this alias set's marker is already recorded. */\n force?: boolean;\n}\n\nexport interface LegacyQualifiedNameBackfillResult {\n /** `false` when the marker was already recorded (and `force` was unset) or on a dry run. */\n ran: boolean;\n dryRun: boolean;\n /** The `_smrt_backfills` marker for this alias set and scope. */\n backfillName: string;\n /** Counts before the rewrite. */\n before: LegacyQualifiedNameReport;\n /** Rows rewritten per table/column/alias. */\n rewritten: LegacyQualifiedNameReference[];\n /**\n * Rows left under their old name because the rewrite would duplicate a row\n * already stored under the current name (resolve those by hand, then re-run).\n */\n skippedDuplicates: LegacyQualifiedNameReference[];\n /** Whether the marker was recorded (only when nothing remains in scope). */\n recorded: boolean;\n}\n\ntype AliasPair = { alias: string; current: string };\n\nfunction compareText(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction aliasPairs(): AliasPair[] {\n return [...ObjectRegistry.getQualifiedNameAliases()].map(\n ([alias, current]) => ({ alias, current }),\n );\n}\n\nfunction quoteIdentifier(name: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) {\n throw new Error(`Refusing to quote unexpected identifier \"${name}\"`);\n }\n return `\"${name}\"`;\n}\n\nfunction isPolymorphicAssociationFields(fields: Map<string, unknown>): boolean {\n // Same rule as delete planning (cascade.ts): all three columns\n // `SmrtPolymorphicAssociation` contributes.\n return fields.has('metaType') && fields.has('metaId') && fields.has('role');\n}\n\nfunction safeConflictColumns(className: string): string[] {\n try {\n return ObjectRegistry.getConflictColumns(className).map((column) =>\n toSnakeCase(column),\n );\n } catch {\n return [];\n }\n}\n\nfunction safeTenantColumn(className: string): string | undefined {\n let declared: string | undefined;\n try {\n declared = ObjectRegistry.getTenantColumn(className);\n } catch {\n declared = undefined;\n }\n if (declared) return declared;\n // A `tenantId` field makes rows tenant-owned without any declaration.\n return ObjectRegistry.getFields(className).has(IMPLICIT_TENANT_FIELD)\n ? IMPLICIT_TENANT_COLUMN\n : undefined;\n}\n\n/**\n * Every table column that can store a deprecated qualified name, derived from\n * the booted registry. Empty when no class declares `previousQualifiedNames`.\n */\nexport function collectLegacyQualifiedNameTargets(): LegacyQualifiedNameTarget[] {\n const pairs = aliasPairs();\n if (pairs.length === 0) return [];\n const allAliases = pairs.map((pair) => pair.alias);\n const byKey = new Map<string, LegacyQualifiedNameTarget>();\n\n const add = (target: LegacyQualifiedNameTarget) => {\n const key = `${target.table}\\u0000${target.column}`;\n const existing = byKey.get(key);\n if (!existing) {\n byKey.set(key, target);\n return;\n }\n existing.aliases = [\n ...new Set([...existing.aliases, ...target.aliases]),\n ].sort(compareText);\n };\n\n for (const className of ObjectRegistry.getQualifiedClassNames()) {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) continue;\n if (isSmrtCollectionExtendsName(registered.extends)) continue;\n if (isFrameworkBaseClass(registered.name, registered.packageName)) continue;\n if (!isPolymorphicAssociationFields(registered.fields)) continue;\n const table = ObjectRegistry.getTableName(className);\n if (!table) continue;\n const conflictColumns = safeConflictColumns(className);\n const tenantColumn = safeTenantColumn(className);\n add({\n className,\n table,\n column: 'meta_type',\n kind: 'polymorphic',\n aliases: [...allAliases].sort(compareText),\n ...(tenantColumn ? { tenantColumn } : {}),\n ...(conflictColumns.includes('meta_type') ? { conflictColumns } : {}),\n });\n }\n\n for (const { alias, current } of pairs) {\n if (ObjectRegistry.getTableStrategy(current) !== 'sti') continue;\n const table = ObjectRegistry.getTableName(current);\n if (!table) continue;\n const conflictColumns = safeConflictColumns(current);\n const tenantColumn = safeTenantColumn(current);\n add({\n className: current,\n table,\n column: '_meta_type',\n kind: 'sti',\n aliases: [alias],\n ...(tenantColumn ? { tenantColumn } : {}),\n ...(conflictColumns.includes('_meta_type') ? { conflictColumns } : {}),\n });\n }\n\n return [...byKey.values()].sort(\n (left, right) =>\n compareText(left.table, right.table) ||\n compareText(left.column, right.column),\n );\n}\n\nfunction rowsOf(result: unknown): Record<string, unknown>[] {\n if (Array.isArray(result)) return result as Record<string, unknown>[];\n if (result && typeof result === 'object' && 'rows' in result) {\n const rows = (result as { rows?: unknown }).rows;\n if (Array.isArray(rows)) return rows as Record<string, unknown>[];\n }\n return [];\n}\n\nfunction resolveEngine(db: DatabaseInterface, engineHint?: string): string {\n const withConfig = db as DatabaseInterface & {\n config?: { url?: string; type?: string };\n type?: string;\n };\n return detectEngine(\n db.url || withConfig.config?.url || '',\n engineHint || withConfig.type || withConfig.config?.type,\n );\n}\n\nasync function countTarget(\n db: DatabaseInterface,\n target: LegacyQualifiedNameTarget,\n tenantId: string | undefined,\n): Promise<Map<string, number>> {\n const counts = new Map<string, number>();\n if (target.aliases.length === 0) return counts;\n const column = quoteIdentifier(target.column);\n const params: unknown[] = [...target.aliases];\n let sql =\n `SELECT ${column} AS name, COUNT(*) AS n FROM ${quoteIdentifier(target.table)} ` +\n `WHERE ${column} IN (${target.aliases.map(() => '?').join(', ')})`;\n if (tenantId !== undefined && target.tenantColumn) {\n sql += ` AND ${quoteIdentifier(target.tenantColumn)} = ?`;\n params.push(tenantId);\n }\n sql += ` GROUP BY ${column}`;\n for (const row of rowsOf(await db.query(sql, ...params))) {\n const count = Number(row.n ?? 0);\n if (count > 0) counts.set(String(row.name), count);\n }\n return counts;\n}\n\n/**\n * Count rows that still store a deprecated qualified name. Read-only.\n *\n * Absent tables are reported in `missingTables`, not as errors: the registry\n * can carry classes this database never migrated.\n */\nexport async function countLegacyQualifiedNameReferences(\n db: DatabaseInterface,\n options: LegacyQualifiedNameOptions = {},\n): Promise<LegacyQualifiedNameReport> {\n const pairs = aliasPairs();\n const currentByAlias = new Map(pairs.map((pair) => [pair.alias, pair]));\n const report: LegacyQualifiedNameReport = {\n aliases: pairs,\n references: [],\n total: 0,\n missingTables: [],\n untenantedTables: [],\n };\n const engine = resolveEngine(db, options.engineHint);\n\n for (const target of collectLegacyQualifiedNameTargets()) {\n if (options.tenantId !== undefined && !target.tenantColumn) {\n if (!report.untenantedTables.includes(target.table)) {\n report.untenantedTables.push(target.table);\n }\n continue;\n }\n if (!(await tableExists(db, target.table, engine))) {\n if (!report.missingTables.includes(target.table)) {\n report.missingTables.push(target.table);\n }\n continue;\n }\n const counts = await countTarget(db, target, options.tenantId);\n for (const alias of target.aliases) {\n const count = counts.get(alias) ?? 0;\n if (count === 0) continue;\n report.references.push({\n table: target.table,\n column: target.column,\n kind: target.kind,\n alias,\n current: currentByAlias.get(alias)?.current ?? alias,\n count,\n });\n report.total += count;\n }\n }\n\n report.missingTables.sort(compareText);\n report.untenantedTables.sort(compareText);\n return report;\n}\n\n/**\n * Stable `_smrt_backfills` marker for the declared alias set and scope. A new\n * alias (another model move) produces a new marker, so the backfill runs\n * again for it.\n */\nexport function legacyQualifiedNamesBackfillName(\n pairs: Array<{ alias: string; current: string }> = aliasPairs(),\n tenantId?: string,\n): string {\n const digest = createHash('sha256')\n .update(\n [...pairs]\n .sort((left, right) => compareText(left.alias, right.alias))\n .map(({ alias, current }) => `${alias}=>${current}`)\n .join('\\n'),\n )\n .digest('hex')\n .slice(0, 16);\n return tenantId === undefined\n ? `${LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX}:${digest}`\n : `${LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX}:${digest}:tenant:${tenantId}`;\n}\n\nfunction nullSafeEquals(engine: string, left: string, right: string): string {\n return engine === 'sqlite'\n ? `${left} IS ${right}`\n : `${left} IS NOT DISTINCT FROM ${right}`;\n}\n\nfunction buildRewriteStatement(\n engine: string,\n target: LegacyQualifiedNameTarget,\n tenantId: string | undefined,\n alias: string,\n current: string,\n): { sql: string; params: unknown[] } {\n const table = quoteIdentifier(target.table);\n const column = quoteIdentifier(target.column);\n const params: unknown[] = [current, alias];\n let sql = `UPDATE ${table} SET ${column} = ? WHERE ${column} = ?`;\n if (tenantId !== undefined && target.tenantColumn) {\n sql += ` AND ${quoteIdentifier(target.tenantColumn)} = ?`;\n params.push(tenantId);\n }\n const identity = (target.conflictColumns ?? []).filter(\n (name) => name !== target.column,\n );\n if (target.conflictColumns) {\n // Never rewrite a row onto the identity of one already stored under the\n // current name: that is a duplicate association, which an operator must\n // resolve, not a unique-violation the rewrite should provoke.\n const correlated = identity\n .map((name) =>\n nullSafeEquals(\n engine,\n `__smrt_current.${quoteIdentifier(name)}`,\n `${table}.${quoteIdentifier(name)}`,\n ),\n )\n .join(' AND ');\n sql +=\n ` AND NOT EXISTS (SELECT 1 FROM ${table} AS __smrt_current ` +\n `WHERE __smrt_current.${column} = ?${correlated ? ` AND ${correlated}` : ''})`;\n params.push(current);\n }\n return { sql, params };\n}\n\n/**\n * Rewrite stored deprecated qualified names to their current names.\n *\n * Opt-in and idempotent: it never runs on its own, rewrites only rows whose\n * value is exactly a declared old name, and records a `_smrt_backfills`\n * marker (per alias set and tenant scope) once nothing in scope remains, so a\n * second call is a no-op unless `force` is set. Rows are rewritten inside one\n * transaction where the adapter provides one; the tenant column, ids and all\n * other values are never touched, and a tenant-scoped run only touches that\n * tenant's rows in tenant-owned tables. A row whose rewrite would duplicate a\n * row already stored under the current name is skipped and reported.\n */\nexport async function backfillLegacyQualifiedNames(\n db: DatabaseInterface,\n options: LegacyQualifiedNameBackfillOptions = {},\n): Promise<LegacyQualifiedNameBackfillResult> {\n const pairs = aliasPairs();\n const backfillName = legacyQualifiedNamesBackfillName(\n pairs,\n options.tenantId,\n );\n const dryRun = options.dryRun === true;\n const before = await countLegacyQualifiedNameReferences(db, options);\n const result: LegacyQualifiedNameBackfillResult = {\n ran: false,\n dryRun,\n backfillName,\n before,\n rewritten: [],\n skippedDuplicates: [],\n recorded: false,\n };\n if (pairs.length === 0 || dryRun) return result;\n\n const tracker = new BackfillTracker({ db });\n if (!options.force && (await tracker.isApplied(backfillName))) {\n return result;\n }\n\n const engine = resolveEngine(db, options.engineHint);\n const targets = new Map(\n collectLegacyQualifiedNameTargets().map((target) => [\n `${target.table}\\u0000${target.column}`,\n target,\n ]),\n );\n\n const rewrite = async (handle: DatabaseInterface) => {\n for (const reference of before.references) {\n const target = targets.get(`${reference.table}\\u0000${reference.column}`);\n if (!target) continue;\n const { sql, params } = buildRewriteStatement(\n engine,\n target,\n options.tenantId,\n reference.alias,\n reference.current,\n );\n await handle.query(sql, ...params);\n }\n };\n if (typeof db.transaction === 'function') {\n await db.transaction(async (tx) => rewrite(tx as DatabaseInterface));\n } else {\n await rewrite(db);\n }\n\n const after = await countLegacyQualifiedNameReferences(db, options);\n const remaining = new Map(\n after.references.map((reference) => [\n `${reference.table}\\u0000${reference.column}\\u0000${reference.alias}`,\n reference,\n ]),\n );\n for (const reference of before.references) {\n const left = remaining.get(\n `${reference.table}\\u0000${reference.column}\\u0000${reference.alias}`,\n );\n const rewrittenCount = reference.count - (left?.count ?? 0);\n if (rewrittenCount > 0) {\n result.rewritten.push({ ...reference, count: rewrittenCount });\n }\n }\n result.skippedDuplicates = after.references;\n result.ran = true;\n\n if (after.total === 0) {\n await tracker.recordApplied(backfillName, {\n description:\n 'Stored deprecated qualified names rewritten to current names: ' +\n pairs.map(({ alias, current }) => `${alias} -> ${current}`).join(', '),\n packageName: '@happyvertical/smrt-core',\n });\n result.recorded = true;\n }\n return result;\n}\n"],"mappings":"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"}
|
package/dist/migrations.js
CHANGED
|
@@ -10,5 +10,6 @@ import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnit
|
|
|
10
10
|
import { collectNullEqualIndexTargets, migrateNullEqualIndexes, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes } from "./migrations/null-equal-indexes.js";
|
|
11
11
|
import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./migrations/tracker.js";
|
|
12
12
|
import { getPendingSchemaStatements, migrateSmrtSchemas } from "./migrations/orchestrate.js";
|
|
13
|
+
import { LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX, backfillLegacyQualifiedNames, collectLegacyQualifiedNameTargets, countLegacyQualifiedNameReferences, legacyQualifiedNamesBackfillName } from "./migrations/qualified-name-aliases.js";
|
|
13
14
|
import "./migrations/index.js";
|
|
14
|
-
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, REQUIRED_COLUMN_NOT_ADDED, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectNullEqualIndexTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
15
|
+
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, LEGACY_QUALIFIED_NAMES_BACKFILL_PREFIX, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, REQUIRED_COLUMN_NOT_ADDED, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, backfillLegacyQualifiedNames, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectLegacyQualifiedNameTargets, collectNullEqualIndexTargets, computeChecksum, countLegacyQualifiedNameReferences, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, legacyQualifiedNamesBackfillName, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
@@ -80,6 +80,19 @@ export declare class SmrtPolymorphicAssociation extends SmrtObject {
|
|
|
80
80
|
/** Sort order for ordering targets within a role. */
|
|
81
81
|
sortOrder: number;
|
|
82
82
|
constructor(options?: SmrtPolymorphicAssociationOptions);
|
|
83
|
+
/**
|
|
84
|
+
* Store `metaType` under its target's CURRENT qualified name before every
|
|
85
|
+
* save (#3338). A deprecated name declared in the target's
|
|
86
|
+
* `previousQualifiedNames` is rewritten first, so new rows — and any legacy
|
|
87
|
+
* row that is loaded and saved again — never perpetuate it. A target known
|
|
88
|
+
* only from an installed manifest is lazily loaded first; names that
|
|
89
|
+
* resolve to no class at all are stored as given.
|
|
90
|
+
*
|
|
91
|
+
* This runs in the first save-preparation hook rather than a `save()`
|
|
92
|
+
* override: a public override would be scanned into every subclass's
|
|
93
|
+
* manifest as a `save` method.
|
|
94
|
+
*/
|
|
95
|
+
protected validateBeforeSave(): Promise<void>;
|
|
83
96
|
/**
|
|
84
97
|
* Resolve and load the target object this association points at.
|
|
85
98
|
*
|
|
@@ -100,6 +113,11 @@ export declare class SmrtPolymorphicAssociation extends SmrtObject {
|
|
|
100
113
|
* exists. The target shares this association's database connection via
|
|
101
114
|
* `this.options`.
|
|
102
115
|
*
|
|
116
|
+
* A `metaType` written under a class's former qualified name (declared in
|
|
117
|
+
* its `previousQualifiedNames`, e.g. after the class moved package)
|
|
118
|
+
* resolves to the class, eagerly or through the lazy manifest alias index,
|
|
119
|
+
* with a one-time deprecation warning (#3338).
|
|
120
|
+
*
|
|
103
121
|
* Prefer qualified `metaType` values (`@pkg:Class`): a bare simple name
|
|
104
122
|
* resolves to the first registered match, and an ambiguous unqualified name
|
|
105
123
|
* that has to be lazily loaded surfaces a `ConfigurationError` rather than
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"polymorphic-association.d.ts","sourceRoot":"","sources":["../src/polymorphic-association.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG9D;;;GAGG;AACH,MAAM,WAAW,iCAAkC,SAAQ,iBAAiB;IAC1E,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,0BAA2B,SAAQ,UAAU;IACxD;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,CAAC,6BAA6B,EAAG,IAAI,CAAU;IAE9D,kFAAkF;IAElF,QAAQ,SAAM;IAEd;;;;OAIG;IAEH,MAAM,SAAM;IAEZ,+EAA+E;IAE/E,IAAI,SAAa;IAEjB,qDAAqD;IAErD,SAAS,EAAE,MAAM,CAAK;gBAEV,OAAO,GAAE,iCAAsC;IAQ3D
|
|
1
|
+
{"version":3,"file":"polymorphic-association.d.ts","sourceRoot":"","sources":["../src/polymorphic-association.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG9D;;;GAGG;AACH,MAAM,WAAW,iCAAkC,SAAQ,iBAAiB;IAC1E,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,0BAA2B,SAAQ,UAAU;IACxD;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,CAAC,6BAA6B,EAAG,IAAI,CAAU;IAE9D,kFAAkF;IAElF,QAAQ,SAAM;IAEd;;;;OAIG;IAEH,MAAM,SAAM;IAEZ,+EAA+E;IAE/E,IAAI,SAAa;IAEjB,qDAAqD;IAErD,SAAS,EAAE,MAAM,CAAK;gBAEV,OAAO,GAAE,iCAAsC;IAQ3D;;;;;;;;;;;OAWG;cACsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,OAAO,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CAsCtE"}
|
|
@@ -78,6 +78,25 @@ var SmrtPolymorphicAssociation = class extends SmrtObject {
|
|
|
78
78
|
if (options.sortOrder !== void 0) this.sortOrder = options.sortOrder;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
+
* Store `metaType` under its target's CURRENT qualified name before every
|
|
82
|
+
* save (#3338). A deprecated name declared in the target's
|
|
83
|
+
* `previousQualifiedNames` is rewritten first, so new rows — and any legacy
|
|
84
|
+
* row that is loaded and saved again — never perpetuate it. A target known
|
|
85
|
+
* only from an installed manifest is lazily loaded first; names that
|
|
86
|
+
* resolve to no class at all are stored as given.
|
|
87
|
+
*
|
|
88
|
+
* This runs in the first save-preparation hook rather than a `save()`
|
|
89
|
+
* override: a public override would be scanned into every subclass's
|
|
90
|
+
* manifest as a `save` method.
|
|
91
|
+
*/
|
|
92
|
+
async validateBeforeSave() {
|
|
93
|
+
if (this.metaType) {
|
|
94
|
+
const current = await ObjectRegistry.resolveQualifiedNameAsync(this.metaType, { source: "SmrtPolymorphicAssociation.save" });
|
|
95
|
+
if (current && current !== this.metaType) this.metaType = current;
|
|
96
|
+
}
|
|
97
|
+
await super.validateBeforeSave();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
81
100
|
* Resolve and load the target object this association points at.
|
|
82
101
|
*
|
|
83
102
|
* Resolves `metaType` to its registered class via
|
|
@@ -97,6 +116,11 @@ var SmrtPolymorphicAssociation = class extends SmrtObject {
|
|
|
97
116
|
* exists. The target shares this association's database connection via
|
|
98
117
|
* `this.options`.
|
|
99
118
|
*
|
|
119
|
+
* A `metaType` written under a class's former qualified name (declared in
|
|
120
|
+
* its `previousQualifiedNames`, e.g. after the class moved package)
|
|
121
|
+
* resolves to the class, eagerly or through the lazy manifest alias index,
|
|
122
|
+
* with a one-time deprecation warning (#3338).
|
|
123
|
+
*
|
|
100
124
|
* Prefer qualified `metaType` values (`@pkg:Class`): a bare simple name
|
|
101
125
|
* resolves to the first registered match, and an ambiguous unqualified name
|
|
102
126
|
* that has to be lazily loaded surfaces a `ConfigurationError` rather than
|
|
@@ -106,10 +130,11 @@ var SmrtPolymorphicAssociation = class extends SmrtObject {
|
|
|
106
130
|
*/
|
|
107
131
|
async hydrate() {
|
|
108
132
|
if (!this.metaType || !this.metaId) return null;
|
|
109
|
-
|
|
133
|
+
const lookup = { source: "SmrtPolymorphicAssociation.hydrate" };
|
|
134
|
+
let registered = ObjectRegistry.getClassByQualifiedName(this.metaType, lookup) ?? ObjectRegistry.getClass(this.metaType);
|
|
110
135
|
if (!registered) {
|
|
111
136
|
if (!await ObjectRegistry.tryLoadFromExternalPackage(this.metaType)) return null;
|
|
112
|
-
registered = ObjectRegistry.getClassByQualifiedName(this.metaType) ?? ObjectRegistry.getClass(this.metaType);
|
|
137
|
+
registered = ObjectRegistry.getClassByQualifiedName(this.metaType, lookup) ?? ObjectRegistry.getClass(this.metaType);
|
|
113
138
|
}
|
|
114
139
|
if (!registered) return null;
|
|
115
140
|
return await (await ObjectRegistry.getCollection(registered.qualifiedName ?? registered.name, this.options)).get({ id: this.metaId });
|