@rvoh/dream 2.19.0 → 2.20.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 (235) hide show
  1. package/dist/cjs/src/Dream.js +83 -0
  2. package/dist/cjs/src/dream/Query.js +11 -0
  3. package/dist/cjs/src/dream/QueryDriver/Kysely.js +62 -14
  4. package/dist/cjs/src/dream/internal/filterRowToKnownColumns.js +41 -0
  5. package/dist/cjs/src/dream/internal/saveDream.js +6 -1
  6. package/dist/cjs/src/dream/internal/sqlResultToDreamInstance.js +9 -2
  7. package/dist/cjs/src/errors/schema-builder/CannotIgnoreAssociationColumn.js +38 -0
  8. package/dist/cjs/src/errors/schema-builder/CannotIgnoreEncryptedColumn.js +20 -0
  9. package/dist/cjs/src/errors/schema-builder/CannotIgnorePrimaryKey.js +18 -0
  10. package/dist/cjs/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.js +21 -0
  11. package/dist/cjs/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.js +20 -0
  12. package/dist/cjs/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.js +24 -0
  13. package/dist/cjs/src/errors/schema-builder/CannotIgnoreStiTypeColumn.js +17 -0
  14. package/dist/cjs/src/errors/schema-builder/ConflictingIgnoredColumns.js +27 -0
  15. package/dist/cjs/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.js +21 -0
  16. package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +38 -1
  17. package/dist/cjs/src/helpers/cli/ASTKyselyCodegenEnhancer.js +60 -0
  18. package/dist/cjs/src/helpers/cli/resolveIgnoredColumns.js +198 -0
  19. package/dist/esm/src/Dream.js +83 -0
  20. package/dist/esm/src/dream/Query.js +11 -0
  21. package/dist/esm/src/dream/QueryDriver/Kysely.js +62 -14
  22. package/dist/esm/src/dream/internal/filterRowToKnownColumns.js +41 -0
  23. package/dist/esm/src/dream/internal/saveDream.js +6 -1
  24. package/dist/esm/src/dream/internal/sqlResultToDreamInstance.js +9 -2
  25. package/dist/esm/src/errors/schema-builder/CannotIgnoreAssociationColumn.js +38 -0
  26. package/dist/esm/src/errors/schema-builder/CannotIgnoreEncryptedColumn.js +20 -0
  27. package/dist/esm/src/errors/schema-builder/CannotIgnorePrimaryKey.js +18 -0
  28. package/dist/esm/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.js +21 -0
  29. package/dist/esm/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.js +20 -0
  30. package/dist/esm/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.js +24 -0
  31. package/dist/esm/src/errors/schema-builder/CannotIgnoreStiTypeColumn.js +17 -0
  32. package/dist/esm/src/errors/schema-builder/ConflictingIgnoredColumns.js +27 -0
  33. package/dist/esm/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.js +21 -0
  34. package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +38 -1
  35. package/dist/esm/src/helpers/cli/ASTKyselyCodegenEnhancer.js +60 -0
  36. package/dist/esm/src/helpers/cli/resolveIgnoredColumns.js +198 -0
  37. package/dist/types/src/Dream.d.ts +81 -0
  38. package/dist/types/src/dream/Query.d.ts +11 -0
  39. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +21 -0
  40. package/dist/types/src/dream/internal/filterRowToKnownColumns.d.ts +30 -0
  41. package/dist/types/src/errors/schema-builder/CannotIgnoreAssociationColumn.d.ts +12 -0
  42. package/dist/types/src/errors/schema-builder/CannotIgnoreEncryptedColumn.d.ts +7 -0
  43. package/dist/types/src/errors/schema-builder/CannotIgnorePrimaryKey.d.ts +6 -0
  44. package/dist/types/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.d.ts +7 -0
  45. package/dist/types/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.d.ts +7 -0
  46. package/dist/types/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.d.ts +8 -0
  47. package/dist/types/src/errors/schema-builder/CannotIgnoreStiTypeColumn.d.ts +6 -0
  48. package/dist/types/src/errors/schema-builder/ConflictingIgnoredColumns.d.ts +7 -0
  49. package/dist/types/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.d.ts +7 -0
  50. package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +20 -0
  51. package/dist/types/src/helpers/cli/ASTKyselyCodegenEnhancer.d.ts +13 -0
  52. package/dist/types/src/helpers/cli/resolveIgnoredColumns.d.ts +41 -0
  53. package/docs/assets/search.js +1 -1
  54. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  55. package/docs/classes/db.KyselyQueryDriver.html +57 -39
  56. package/docs/classes/db.PostgresQueryDriver.html +58 -40
  57. package/docs/classes/db.QueryDriverBase.html +38 -38
  58. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  59. package/docs/classes/errors.ColumnOverflow.html +3 -3
  60. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  61. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  62. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  63. package/docs/classes/errors.DecryptionError.html +2 -2
  64. package/docs/classes/errors.DecryptionParseError.html +2 -2
  65. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  66. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  67. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  68. package/docs/classes/errors.InvalidClockTime.html +2 -2
  69. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  70. package/docs/classes/errors.InvalidDateTime.html +2 -2
  71. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  72. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  73. package/docs/classes/errors.NotNullViolation.html +3 -3
  74. package/docs/classes/errors.RecordNotFound.html +3 -3
  75. package/docs/classes/errors.ValidationError.html +3 -3
  76. package/docs/classes/index.CalendarDate.html +33 -33
  77. package/docs/classes/index.ClockTime.html +32 -32
  78. package/docs/classes/index.ClockTimeTz.html +35 -35
  79. package/docs/classes/index.DateTime.html +86 -86
  80. package/docs/classes/index.Decorators.html +19 -19
  81. package/docs/classes/index.Dream.html +188 -123
  82. package/docs/classes/index.DreamApp.html +10 -10
  83. package/docs/classes/index.DreamTransaction.html +2 -2
  84. package/docs/classes/index.Env.html +2 -2
  85. package/docs/classes/index.Query.html +73 -62
  86. package/docs/classes/system.CliFileWriter.html +4 -4
  87. package/docs/classes/system.DreamBin.html +2 -2
  88. package/docs/classes/system.DreamCLI.html +7 -7
  89. package/docs/classes/system.DreamImporter.html +2 -2
  90. package/docs/classes/system.DreamLogos.html +2 -2
  91. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  92. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  93. package/docs/classes/system.PathHelpers.html +3 -3
  94. package/docs/classes/utils.Encrypt.html +3 -3
  95. package/docs/classes/utils.Range.html +2 -2
  96. package/docs/functions/db.closeAllDbConnections.html +1 -1
  97. package/docs/functions/db.dreamDbConnections.html +1 -1
  98. package/docs/functions/db.untypedDb.html +1 -1
  99. package/docs/functions/db.validateColumn.html +1 -1
  100. package/docs/functions/db.validateTable.html +1 -1
  101. package/docs/functions/errors.pgErrorType.html +1 -1
  102. package/docs/functions/index.DreamSerializer.html +1 -1
  103. package/docs/functions/index.ObjectSerializer.html +1 -1
  104. package/docs/functions/index.ReplicaSafe.html +1 -1
  105. package/docs/functions/index.STI.html +1 -1
  106. package/docs/functions/index.SoftDelete.html +1 -1
  107. package/docs/functions/utils.camelize.html +1 -1
  108. package/docs/functions/utils.capitalize.html +1 -1
  109. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  110. package/docs/functions/utils.compact.html +1 -1
  111. package/docs/functions/utils.groupBy.html +1 -1
  112. package/docs/functions/utils.hyphenize.html +1 -1
  113. package/docs/functions/utils.intersection.html +1 -1
  114. package/docs/functions/utils.isEmpty.html +1 -1
  115. package/docs/functions/utils.normalizeUnicode.html +1 -1
  116. package/docs/functions/utils.pascalize.html +1 -1
  117. package/docs/functions/utils.percent.html +1 -1
  118. package/docs/functions/utils.range.html +1 -1
  119. package/docs/functions/utils.round.html +1 -1
  120. package/docs/functions/utils.sanitizeString.html +1 -1
  121. package/docs/functions/utils.snakeify.html +1 -1
  122. package/docs/functions/utils.sort.html +1 -1
  123. package/docs/functions/utils.sortBy.html +1 -1
  124. package/docs/functions/utils.sortObjectByKey.html +1 -1
  125. package/docs/functions/utils.sortObjectByValue.html +1 -1
  126. package/docs/functions/utils.uncapitalize.html +1 -1
  127. package/docs/functions/utils.uniq.html +1 -1
  128. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  129. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  130. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  131. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  132. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  133. package/docs/interfaces/types.DecoratorContext.html +2 -2
  134. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  135. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  136. package/docs/interfaces/types.DreamDbConfig.html +5 -5
  137. package/docs/interfaces/types.DurationObject.html +2 -2
  138. package/docs/interfaces/types.EncryptOptions.html +2 -2
  139. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  140. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  141. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  142. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  143. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  144. package/docs/types/openapi.OpenapiFormats.html +1 -1
  145. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  146. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  147. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  148. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  149. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  150. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  155. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  156. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  157. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  158. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  159. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  161. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  163. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  164. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  165. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  166. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  167. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  168. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  169. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  170. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  171. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  172. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  173. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  174. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  175. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  176. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  177. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  178. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  179. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  180. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  181. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  182. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  183. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  184. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  185. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  186. package/docs/types/types.CalendarDateObject.html +1 -1
  187. package/docs/types/types.Camelized.html +1 -1
  188. package/docs/types/types.ClockTimeObject.html +1 -1
  189. package/docs/types/types.DbConnectionType.html +1 -1
  190. package/docs/types/types.DbTypes.html +1 -1
  191. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  192. package/docs/types/types.DreamAttributes.html +1 -1
  193. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  194. package/docs/types/types.DreamClassColumn.html +1 -1
  195. package/docs/types/types.DreamColumn.html +1 -1
  196. package/docs/types/types.DreamColumnNames.html +1 -1
  197. package/docs/types/types.DreamLogLevel.html +1 -1
  198. package/docs/types/types.DreamLogger.html +2 -2
  199. package/docs/types/types.DreamModelSerializerType.html +1 -1
  200. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  201. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  202. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  203. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  204. package/docs/types/types.DreamSerializable.html +1 -1
  205. package/docs/types/types.DreamSerializableArray.html +1 -1
  206. package/docs/types/types.DreamSerializerKey.html +1 -1
  207. package/docs/types/types.DreamSerializers.html +1 -1
  208. package/docs/types/types.DreamVirtualColumns.html +1 -1
  209. package/docs/types/types.DurationUnit.html +1 -1
  210. package/docs/types/types.EncryptAlgorithm.html +1 -1
  211. package/docs/types/types.HasManyStatement.html +1 -1
  212. package/docs/types/types.HasOneStatement.html +1 -1
  213. package/docs/types/types.Hyphenized.html +1 -1
  214. package/docs/types/types.Pascalized.html +1 -1
  215. package/docs/types/types.PrimaryKeyType.html +1 -1
  216. package/docs/types/types.RoundingPrecision.html +1 -1
  217. package/docs/types/types.SerializerCasing.html +1 -1
  218. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  219. package/docs/types/types.Snakeified.html +1 -1
  220. package/docs/types/types.StrictInterface.html +1 -1
  221. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  222. package/docs/types/types.UpdateableProperties.html +1 -1
  223. package/docs/types/types.ValidationType.html +1 -1
  224. package/docs/types/types.ViewModel.html +2 -2
  225. package/docs/types/types.ViewModelClass.html +1 -1
  226. package/docs/types/types.WeekdayName.html +1 -1
  227. package/docs/types/types.WhereStatementForDream.html +1 -1
  228. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  229. package/docs/variables/index.DreamConst.html +1 -1
  230. package/docs/variables/index.ops.html +1 -1
  231. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  232. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  233. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  234. package/docs/variables/system.primaryKeyTypes.html +1 -1
  235. package/package.json +1 -1
@@ -1461,6 +1461,12 @@ export default class Dream {
1461
1461
  * 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinPreload('a', 'b', 'c')`, if each `a` has 10 `b` and each `b` has 10 `c`, then for one `a`, 100 records will be returned, each of which has all of the columns of `a`. `.preload('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once.
1462
1462
  * 4. the individual query becomes more complex the more associations are included
1463
1463
  * 5. associations loading associations loading associations could result in exponential amounts of data; in those cases, `.preload(...).findEach(...)` avoids instantiating massive amounts of data at once
1464
+ * 6. leftJoinPreload must enumerate every compiled column of every joined
1465
+ * model, so unlike base-model reads, `preload`/`load`, and saves, it does
1466
+ * not tolerate schema/image skew from an unplanned column drop during a
1467
+ * rolling deploy; see {@link Query.leftJoinPreload} and the
1468
+ * `ignoredColumns` getter for the two-deploy process that makes a
1469
+ * planned drop safe.
1464
1470
  *
1465
1471
  * ```ts
1466
1472
  * const user = await User.leftJoinPreload('posts', 'comments', { visibilty: 'public' }, 'replies').first()
@@ -2340,6 +2346,83 @@ export default class Dream {
2340
2346
  get table() {
2341
2347
  throw new DreamMissingRequiredOverride(this.constructor, 'table');
2342
2348
  }
2349
+ /**
2350
+ * Columns that Dream should behave as though they do not exist.
2351
+ *
2352
+ * Declaring a column ignored removes it from the generated types the next
2353
+ * time `sync` runs: it is omitted from both the db types file (the Kysely
2354
+ * `DB` interface) and the dream schema file, so it disappears from
2355
+ * `columns()` and from every place that flows from `columns()` — select
2356
+ * lists built for `preload`/`load` and `leftJoinPreload`, save hydration,
2357
+ * attribute definition, and param safety. References to the column in
2358
+ * application code become type errors, which is the point: they must be
2359
+ * removed before the column can be dropped.
2360
+ *
2361
+ * This enables safely dropping a column under rolling deploys — the same
2362
+ * problem Rails solves with `ignored_columns`. The safety requirement is
2363
+ * that no image that can run against the post-drop schema names the
2364
+ * column in any SQL it generates. To satisfy it: remove all application
2365
+ * code that uses the column, declare it here, and run `sync`; then let
2366
+ * the drop migration run only once no image lacking the declaration can
2367
+ * run against the database — whether the migration ships in a later
2368
+ * deploy of its own, or together with the declaration in a pipeline that
2369
+ * runs migrations only after the new images have rolled out. Once the
2370
+ * column is dropped, remove this declaration and resync.
2371
+ *
2372
+ * Precondition: as soon as an image built with this declaration runs,
2373
+ * the column is never placed in INSERT column lists, so before that
2374
+ * image can run, the column must be nullable or carry a database
2375
+ * default — a live `NOT NULL` column without a default fails every
2376
+ * create against the table.
2377
+ *
2378
+ * Dropping the column while an image that names it can still run leaves
2379
+ * a window during which those containers (including a rolled-back image)
2380
+ * fail with `42703 column does not exist` — `leftJoinPreload` in
2381
+ * particular has no runtime tolerance for this, since it must enumerate
2382
+ * aliased columns.
2383
+ *
2384
+ * This is a mechanism for the drop window, not for permanently hiding
2385
+ * wide columns: the ignored column is still transferred from the
2386
+ * database on every `RETURNING *` / `select *` until it is actually
2387
+ * dropped.
2388
+ *
2389
+ * The declaration is read only while `sync` generates the types files; it
2390
+ * has no runtime behavior of its own. A declared-but-not-synced model is
2391
+ * therefore not yet protected — CI should verify that `sync` produces no
2392
+ * diff. `sync` will fail loudly if a declared name is not camelCase
2393
+ * (generated column names are camelized, so any other shape could never
2394
+ * match and would be silently inert), if models sharing a table declare
2395
+ * different ignored columns, or if a model attempts to ignore a column
2396
+ * the framework itself reads and writes by name: its primary key, an STI
2397
+ * model's `type` column, any association's foreign key, polymorphic type
2398
+ * field, or `primaryKeyOverride` column, an `@Sortable` position field or
2399
+ * plain-column `@Sortable` scope, an `@Encrypted` backing column, or a
2400
+ * SoftDelete model's `deletedAt` column.
2401
+ *
2402
+ * Because an ignored column vanishes from `columns()`, runtime access via
2403
+ * type escape hatches behaves exactly like any unknown attribute: reads
2404
+ * return `undefined`, and writes assign a plain instance property that is
2405
+ * never persisted.
2406
+ *
2407
+ * ```ts
2408
+ * class User extends ApplicationModel {
2409
+ * public override get ignoredColumns() {
2410
+ * return ['legacyEmail'] as const
2411
+ * }
2412
+ * }
2413
+ * ```
2414
+ *
2415
+ * NOTE: this getter is intentionally typed as `readonly string[]` rather
2416
+ * than as a union of known column names: during the deploy that declares
2417
+ * a column ignored, the regenerated types no longer contain the column,
2418
+ * so a column-name-derived type would reject the very declaration that
2419
+ * removed it.
2420
+ *
2421
+ * @returns The list of column names this model should ignore
2422
+ */
2423
+ get ignoredColumns() {
2424
+ return [];
2425
+ }
2343
2426
  /**
2344
2427
  * @internal
2345
2428
  *
@@ -442,6 +442,17 @@ export default class Query {
442
442
  * 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinPreload('a', 'b', 'c')`, if each `a` has 10 `b` and each `b` has 10 `c`, then for one `a`, 100 records will be returned, each of which has all of the columns of `a`. `.preload('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once.
443
443
  * 4. the individual query becomes more complex the more associations are included
444
444
  * 5. associations loading associations loading associations could result in exponential amounts of data; in those cases, `.preload(...).findEach(...)` avoids instantiating massive amounts of data at once
445
+ * 6. unlike base-model reads, `preload`/`load`, and saves — which tolerate
446
+ * schema/image skew (e.g. a rolling deploy dropping a column while
447
+ * containers compiled against the previous schema are still draining) —
448
+ * leftJoinPreload must enumerate every compiled column of every joined
449
+ * model under per-alias names (per-alias `*` is not expressible in a
450
+ * single flat row), so an **unplanned** column drop breaks
451
+ * leftJoinPreload queries for the duration of the rollout window. A
452
+ * **planned** drop is safe when performed via the two-deploy process
453
+ * documented on the `ignoredColumns` getter of Dream: declaring the
454
+ * column ignored removes it from the generated schema, so leftJoinPreload
455
+ * stops naming it a full deploy before the column is actually dropped.
445
456
  *
446
457
  *
447
458
  * ```ts
@@ -734,6 +734,27 @@ export default class KyselyQueryDriver extends QueryDriverBase {
734
734
  * is provided as a second argument, it will use that transaction
735
735
  * to encapsulate the persisting of the dream, as well as any
736
736
  * subsequent model hooks that are fired.
737
+ *
738
+ * `RETURNING *` (rather than enumerating the compiled column list) keeps
739
+ * writes working under schema/image skew: during a rolling deploy, a
740
+ * container built before a drop-column migration would otherwise name the
741
+ * dropped column in `RETURNING` and fail with `42703 column does not
742
+ * exist` on every write, even writes that never touch that column. The
743
+ * `SET`/`VALUES` half still names only dirty attributes, so a write that
744
+ * actually sets a dropped column still fails loudly. The returned row is
745
+ * filtered to the compiled column list before hydration (see
746
+ * internal/saveDream.ts), so a column the image doesn't know about never
747
+ * reaches `setAttributes`.
748
+ *
749
+ * KNOWN CONSTRAINT: star-selects are only safe because nothing in this
750
+ * stack uses named prepared statements — node-postgres prepares a
751
+ * statement only when given an explicit `name`, and Kysely never names
752
+ * them, so every query is re-planned. With named prepared statements, a
753
+ * concurrent `ADD COLUMN` changes a cached plan's result shape and
754
+ * Postgres raises `cached plan must not change result type` (the reason
755
+ * Rails added `enumerate_columns_in_select_statements`). If a future
756
+ * driver or pooling layer enables named prepared statements, revisit
757
+ * every `RETURNING *` / `select *` in this driver.
737
758
  */
738
759
  static async saveDream(dream, txn = null) {
739
760
  const connectionName = dream.connectionName || 'default';
@@ -745,13 +766,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
745
766
  .updateTable(dream.table)
746
767
  .set(sqlifiedAttributes)
747
768
  .where(namespaceColumn(dream['_primaryKey'], dream.table), '=', dream.primaryKeyValue());
748
- return await executeDatabaseQuery(query.returning([...dream.columns()]), 'executeTakeFirstOrThrow');
769
+ return await executeDatabaseQuery(query.returningAll(), 'executeTakeFirstOrThrow');
749
770
  }
750
771
  else {
751
772
  const query = db
752
773
  .insertInto(dream.table)
753
774
  .values(sqlifiedAttributes)
754
- .returning([...dream.columns()]);
775
+ .returningAll();
755
776
  return await executeDatabaseQuery(query, 'executeTakeFirstOrThrow');
756
777
  }
757
778
  }
@@ -2401,15 +2422,29 @@ export default class KyselyQueryDriver extends QueryDriverBase {
2401
2422
  const preloadedPolymorphicBelongsTos = await this.preloadPolymorphicBelongsTo(association, dreamClassToHydrate, dreams);
2402
2423
  return preloadedPolymorphicBelongsTos;
2403
2424
  }
2404
- const dreamClassToHydrateColumns = [...dreamClassToHydrate.columns()];
2405
- const columnsToPluck = dreamClassToHydrateColumns.map(column => this.namespaceColumn(column.toString(), alias));
2406
- columnsToPluck.push(this.namespaceColumn(dreamClass.primaryKey, dreamClass.table));
2425
+ const dreamClassToHydrateColumns = dreamClassToHydrate.columns();
2426
+ // The base-model primary key rides along under a short alias so each
2427
+ // associated row can be matched back to the dream it belongs to. The
2428
+ // alias is lowercase without underscores so Kysely's CamelCasePlugin
2429
+ // passes it through untouched (the same strategy as `pluck0` /
2430
+ // `groupvalue`), and is extended until it cannot collide with a real
2431
+ // column of the associated table. That collision loop can only consult
2432
+ // the compiled column set, so under add-column skew a live column
2433
+ // literally named like the alias would produce two identically-named
2434
+ // result fields. The aliased base PK is deliberately selected last,
2435
+ // and node-postgres resolves duplicate field names last-wins, so the
2436
+ // base PK still wins even in that pathological case — and the
2437
+ // hydration filter strips the colliding key from the instance's
2438
+ // attributes.
2439
+ let basePrimaryKeyAlias = 'preloadbasepk';
2440
+ while (dreamClassToHydrateColumns.has(basePrimaryKeyAlias))
2441
+ basePrimaryKeyAlias += '0';
2407
2442
  const baseClass = dreamClass['stiBaseClassOrOwnClass']['getAssociationMetadata'](associationName)
2408
2443
  ? dreamClass['stiBaseClassOrOwnClass']
2409
2444
  : dreamClass;
2410
2445
  const associationDataScope = this.dreamClassQueryWithScopeBypasses(baseClass, {
2411
2446
  // In order to stay DRY, preloading leverages the association logic built into
2412
- // `joins` (by using `pluck`, which calls `joins`). However, baseClass may have
2447
+ // `joins`. However, baseClass may have
2413
2448
  // default scopes that would preclude finding that instance. We remove all
2414
2449
  // default scopes on baseClass, but not subsequent associations, so that the
2415
2450
  // single query will be able to find each row corresponding to a Dream in `dreams`,
@@ -2418,16 +2453,29 @@ export default class KyselyQueryDriver extends QueryDriverBase {
2418
2453
  }).where({
2419
2454
  [dreamClass.primaryKey]: dreams.map(obj => obj.primaryKeyValue()),
2420
2455
  });
2421
- const hydrationData = await associationDataScope['_connection'](this.connectionOverride)
2422
- .innerJoin(associationName, (onStatement || {}))
2423
- .pluck(...columnsToPluck);
2424
- const preloadedDreamsAndWhatTheyPointTo = hydrationData.map(pluckedData => {
2425
- const attributes = {};
2426
- dreamClassToHydrateColumns.forEach((columnName, index) => (attributes[protectAgainstPollutingAssignment(columnName)] = pluckedData[index]));
2427
- const hydratedDream = this.dbResultToDreamInstance(attributes, dreamClassToHydrate);
2456
+ const joinedQuery = associationDataScope['_connection'](this.connectionOverride).innerJoin(associationName, (onStatement || {}));
2457
+ // Select the association's row wholesale (`"alias".*`) rather than
2458
+ // enumerating the compiled column list. Enumerating made every preload
2459
+ // assert the compiled schema against the live database: under
2460
+ // schema/image skew (rolling deploy or rollback around a drop-column
2461
+ // migration), naming a dropped column fails the whole preload with
2462
+ // `42703 column does not exist`. The raw row is passed straight to
2463
+ // hydration, where sqlResultToDreamInstance — the single filtering
2464
+ // point — intersects it with the compiled column list (also stripping
2465
+ // the base-PK alias key), so a column the image doesn't know about
2466
+ // (the add-column direction of skew) never reaches the instance. See
2467
+ // the prepared-statements note on `saveDream` before changing this
2468
+ // back to an enumerated select.
2469
+ const kyselyQuery = new this.constructor(joinedQuery)
2470
+ .buildSelect({ bypassSelectAll: true })
2471
+ .selectAll(alias)
2472
+ .select(`${this.namespaceColumn(dreamClass.primaryKey, dreamClass.table)} as ${basePrimaryKeyAlias}`);
2473
+ const rows = await executeDatabaseQuery(kyselyQuery, 'execute');
2474
+ const preloadedDreamsAndWhatTheyPointTo = rows.map((row) => {
2475
+ const hydratedDream = this.dbResultToDreamInstance(row, dreamClassToHydrate);
2428
2476
  return {
2429
2477
  dream: hydratedDream,
2430
- pointsToPrimaryKey: pluckedData.at(-1),
2478
+ pointsToPrimaryKey: row[basePrimaryKeyAlias],
2431
2479
  };
2432
2480
  });
2433
2481
  this.hydrateAssociation(dreams, association, preloadedDreamsAndWhatTheyPointTo);
@@ -0,0 +1,41 @@
1
+ import protectAgainstPollutingAssignment from '../../helpers/protectAgainstPollutingAssignment.js';
2
+ /**
3
+ * @internal
4
+ *
5
+ * Returns an object containing only the keys of `row` that are columns
6
+ * the compiled schema knows about (per the provided column set).
7
+ *
8
+ * Under schema/image skew (e.g. a rolling deploy in which a migration adds
9
+ * a column while containers built against the previous schema are still
10
+ * draining, or application code is rolled back after an add-column
11
+ * migration), a `RETURNING *` / `select *` row can include columns this
12
+ * build has never heard of. Passing such keys to `setAttributes` would
13
+ * assign them as plain properties — invoking a same-named user-defined
14
+ * setter, or throwing on a getter-only property — so they must be dropped
15
+ * before hydration.
16
+ *
17
+ * Keys are intersected rather than enumerated from the column set so that
18
+ * a column missing from the row (the dropped-column direction of skew)
19
+ * simply does not appear, rather than appearing with an `undefined` value.
20
+ *
21
+ * Identity fast path: when every key of `row` is a known column (the
22
+ * no-skew steady state), `row` itself is returned unchanged, without
23
+ * allocating a copy; a filtered copy is built only when at least one
24
+ * unknown key is present.
25
+ *
26
+ * @param row - a raw database row
27
+ * @param columns - the compiled column set for the Dream class being hydrated
28
+ * @returns `row` itself when every key is a known column; otherwise a new
29
+ * object containing only the known-column entries of `row`
30
+ */
31
+ export default function filterRowToKnownColumns(row, columns) {
32
+ const keys = Object.keys(row);
33
+ if (keys.every(key => columns.has(key)))
34
+ return row;
35
+ const filtered = {};
36
+ keys.forEach(key => {
37
+ if (columns.has(key))
38
+ filtered[protectAgainstPollutingAssignment(key)] = row[key];
39
+ });
40
+ return filtered;
41
+ }
@@ -2,6 +2,7 @@ import ValidationError from '../../errors/ValidationError.js';
2
2
  import sqlAttributes from '../../helpers/sqlAttributes.js';
3
3
  import { DateTime } from '../../utils/datetime/DateTime.js';
4
4
  import Query from '../Query.js';
5
+ import filterRowToKnownColumns from './filterRowToKnownColumns.js';
5
6
  import runHooksFor from './runHooksFor.js';
6
7
  export default async function saveDream(dream, txn = null, { skipHooks = false } = {}) {
7
8
  const alreadyPersisted = dream.isPersisted;
@@ -30,7 +31,11 @@ export default async function saveDream(dream, txn = null, { skipHooks = false }
30
31
  if (!alreadyPersisted || hasUnsavedData) {
31
32
  const data = await Query.dbDriverClass(dream.connectionName || 'default').saveDream(dream, txn);
32
33
  dream['isPersisted'] = true;
33
- dream.setAttributes(data);
34
+ // the row returned by the driver comes from `RETURNING *` (or a
35
+ // `select *` reload on drivers without RETURNING support), so under
36
+ // schema/image skew it can contain columns this build's compiled
37
+ // schema doesn't know about; those must never reach setAttributes
38
+ dream.setAttributes(filterRowToKnownColumns(data, dream.columns()));
34
39
  }
35
40
  // set frozen attributes to what has already been saved
36
41
  dream['freezeAttributes']();
@@ -1,10 +1,17 @@
1
1
  import STIChildMissing from '../../errors/sti/STIChildMissing.js';
2
+ import filterRowToKnownColumns from './filterRowToKnownColumns.js';
2
3
  export default function sqlResultToDreamInstance(dreamClass, sqlResult) {
4
+ // base-model reads select `*`, so under schema/image skew (or with a
5
+ // column declared in ignoredColumns but not yet dropped) the row can
6
+ // contain columns the compiled schema doesn't know about. Passing such
7
+ // keys into the constructor would assign them as plain properties —
8
+ // invoking a same-named user-defined setter, or throwing on a
9
+ // getter-only property — so they are dropped before hydration.
3
10
  if (dreamClass['isSTIBase']) {
4
11
  const extendingDreamClass = findExtendingDreamClass(dreamClass, sqlResult.type);
5
12
  if (!extendingDreamClass)
6
13
  throw new STIChildMissing(dreamClass, sqlResult.type, sqlResult[dreamClass.primaryKey]);
7
- const dreamModel = new extendingDreamClass(sqlResult, {
14
+ const dreamModel = new extendingDreamClass(filterRowToKnownColumns(sqlResult, extendingDreamClass.columns()), {
8
15
  bypassUserDefinedSetters: true,
9
16
  isPersisted: true,
10
17
  _internalUseOnly: true,
@@ -13,7 +20,7 @@ export default function sqlResultToDreamInstance(dreamClass, sqlResult) {
13
20
  return dreamModel;
14
21
  }
15
22
  else {
16
- const dreamModel = new dreamClass(sqlResult, {
23
+ const dreamModel = new dreamClass(filterRowToKnownColumns(sqlResult, dreamClass.columns()), {
17
24
  bypassUserDefinedSetters: true,
18
25
  isPersisted: true,
19
26
  _internalUseOnly: true,
@@ -0,0 +1,38 @@
1
+ export default class CannotIgnoreAssociationColumn extends Error {
2
+ tableName;
3
+ columnName;
4
+ modelClass;
5
+ association;
6
+ columnRole;
7
+ constructor(tableName, columnName, modelClass, association, columnRole) {
8
+ super();
9
+ this.tableName = tableName;
10
+ this.columnName = columnName;
11
+ this.modelClass = modelClass;
12
+ this.association = association;
13
+ this.columnRole = columnRole;
14
+ }
15
+ get message() {
16
+ return `
17
+ The models backed by the "${this.tableName}" table declare
18
+ "${this.columnName}" in ignoredColumns, but "${this.columnName}" is the
19
+ ${this.columnRole} of the ${this.association.type} association
20
+ "${this.association.as}" on ${this.modelClass.sanitizedName}.
21
+
22
+ Associations read and write their columns by name, so ignoring
23
+ "${this.columnName}" would silently break "${this.association.as}" at
24
+ runtime: ${this.consequence}. Remove the
25
+ "${this.association.as}" association from ${this.modelClass.sanitizedName}
26
+ (or point it at a different column) before ignoring "${this.columnName}".
27
+ `;
28
+ }
29
+ get consequence() {
30
+ if (this.columnRole === 'primary key override')
31
+ return `associating a record would read "${this.columnName}" from an
32
+ instance that no longer hydrates it, silently persisting an empty foreign
33
+ key, and association loads would join on a column missing from the
34
+ generated schema`;
35
+ return `writes to the column would stop persisting, and association loads
36
+ would reference a column missing from the generated schema`;
37
+ }
38
+ }
@@ -0,0 +1,20 @@
1
+ export default class CannotIgnoreEncryptedColumn extends Error {
2
+ modelClass;
3
+ columnName;
4
+ constructor(modelClass, columnName) {
5
+ super();
6
+ this.modelClass = modelClass;
7
+ this.columnName = columnName;
8
+ }
9
+ get message() {
10
+ return `
11
+ ${this.modelClass.sanitizedName} declares "${this.columnName}" in
12
+ ignoredColumns, but "${this.columnName}" is the backing column of an
13
+ @Encrypted property on ${this.modelClass.sanitizedName}.
14
+
15
+ @Encrypted stores its ciphertext in this column, so it can never be
16
+ ignored. Remove the @Encrypted declaration (and its property) from
17
+ ${this.modelClass.sanitizedName} before ignoring "${this.columnName}".
18
+ `;
19
+ }
20
+ }
@@ -0,0 +1,18 @@
1
+ export default class CannotIgnorePrimaryKey extends Error {
2
+ modelClass;
3
+ constructor(modelClass) {
4
+ super();
5
+ this.modelClass = modelClass;
6
+ }
7
+ get message() {
8
+ return `
9
+ ${this.modelClass.sanitizedName} declares its primary key ("${this.modelClass.primaryKey}")
10
+ in ignoredColumns.
11
+
12
+ The primary key is required to identify rows for hydration, updates, and
13
+ associations, so it can never be ignored. Remove
14
+ "${this.modelClass.primaryKey}" from the ignoredColumns getter on
15
+ ${this.modelClass.sanitizedName}.
16
+ `;
17
+ }
18
+ }
@@ -0,0 +1,21 @@
1
+ export default class CannotIgnoreSoftDeleteColumn extends Error {
2
+ modelClass;
3
+ columnName;
4
+ constructor(modelClass, columnName) {
5
+ super();
6
+ this.modelClass = modelClass;
7
+ this.columnName = columnName;
8
+ }
9
+ get message() {
10
+ return `
11
+ ${this.modelClass.sanitizedName} declares "${this.columnName}" in
12
+ ignoredColumns, but ${this.modelClass.sanitizedName} is a SoftDelete model
13
+ and "${this.columnName}" is its deletedAtField.
14
+
15
+ SoftDelete writes this column on destroy and filters on it in its default
16
+ scope, so it can never be ignored. Remove the @SoftDelete decorator from
17
+ ${this.modelClass.sanitizedName} (or point deletedAtField at a different
18
+ column) before ignoring "${this.columnName}".
19
+ `;
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ export default class CannotIgnoreSortablePositionColumn extends Error {
2
+ modelClass;
3
+ columnName;
4
+ constructor(modelClass, columnName) {
5
+ super();
6
+ this.modelClass = modelClass;
7
+ this.columnName = columnName;
8
+ }
9
+ get message() {
10
+ return `
11
+ ${this.modelClass.sanitizedName} declares "${this.columnName}" in
12
+ ignoredColumns, but "${this.columnName}" is the position field of an
13
+ @Sortable declaration on ${this.modelClass.sanitizedName}.
14
+
15
+ @Sortable reads and rewrites this column to keep positions contiguous, so
16
+ it can never be ignored. Remove the @Sortable decorator (and its property)
17
+ from ${this.modelClass.sanitizedName} before ignoring "${this.columnName}".
18
+ `;
19
+ }
20
+ }
@@ -0,0 +1,24 @@
1
+ export default class CannotIgnoreSortableScopeColumn extends Error {
2
+ modelClass;
3
+ columnName;
4
+ positionField;
5
+ constructor(modelClass, columnName, positionField) {
6
+ super();
7
+ this.modelClass = modelClass;
8
+ this.columnName = columnName;
9
+ this.positionField = positionField;
10
+ }
11
+ get message() {
12
+ return `
13
+ ${this.modelClass.sanitizedName} declares "${this.columnName}" in
14
+ ignoredColumns, but "${this.columnName}" is a scope column of the @Sortable
15
+ declaration on "${this.positionField}" on ${this.modelClass.sanitizedName}.
16
+
17
+ @Sortable reads this column by name on every save and destroy to partition
18
+ position values, so it can never be ignored. Remove "${this.columnName}"
19
+ from the @Sortable scope on "${this.positionField}" (or remove the
20
+ @Sortable decorator and its property) on ${this.modelClass.sanitizedName}
21
+ before ignoring "${this.columnName}".
22
+ `;
23
+ }
24
+ }
@@ -0,0 +1,17 @@
1
+ export default class CannotIgnoreStiTypeColumn extends Error {
2
+ modelClass;
3
+ constructor(modelClass) {
4
+ super();
5
+ this.modelClass = modelClass;
6
+ }
7
+ get message() {
8
+ return `
9
+ ${this.modelClass.sanitizedName} participates in single-table inheritance
10
+ and declares the STI "type" column in ignoredColumns.
11
+
12
+ The "type" column determines which model class each row hydrates into, so
13
+ an STI model can never ignore it. Remove "type" from the ignoredColumns
14
+ getter on ${this.modelClass.sanitizedName}.
15
+ `;
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ export default class ConflictingIgnoredColumns extends Error {
2
+ tableName;
3
+ modelClasses;
4
+ constructor(tableName, modelClasses) {
5
+ super();
6
+ this.tableName = tableName;
7
+ this.modelClasses = modelClasses;
8
+ }
9
+ get message() {
10
+ const declarations = this.modelClasses
11
+ .map(modelClass => ` ${modelClass.sanitizedName}: [${[...modelClass.prototype.ignoredColumns]
12
+ .sort()
13
+ .map(column => `'${column}'`)
14
+ .join(', ')}]`)
15
+ .join('\n');
16
+ return `
17
+ Models sharing the table "${this.tableName}" declare different ignoredColumns:
18
+
19
+ ${declarations}
20
+
21
+ There is only one generated schema per table, so every model backed by a
22
+ table must agree on which of its columns are ignored. Align the
23
+ ignoredColumns getters (STI children inherit the base model's getter, so
24
+ the common fix is to declare ignored columns only on the base model).
25
+ `;
26
+ }
27
+ }
@@ -0,0 +1,21 @@
1
+ import camelize from '../../helpers/camelize.js';
2
+ export default class IgnoredColumnMustBeCamelCase extends Error {
3
+ modelClass;
4
+ columnName;
5
+ constructor(modelClass, columnName) {
6
+ super();
7
+ this.modelClass = modelClass;
8
+ this.columnName = columnName;
9
+ }
10
+ get message() {
11
+ return `
12
+ ${this.modelClass.sanitizedName} declares "${this.columnName}" in
13
+ ignoredColumns, but ignored columns must be declared in camelCase.
14
+
15
+ Dream camelizes database column names when generating types, so a
16
+ declaration that is not camelCase can never match a generated column and
17
+ would be silently inert. Declare "${camelize(this.columnName)}" in the
18
+ ignoredColumns getter on ${this.modelClass.sanitizedName} instead.
19
+ `;
20
+ }
21
+ }
@@ -11,6 +11,7 @@ import intersection from '../intersection.js';
11
11
  import sortBy from '../sortBy.js';
12
12
  import uniq from '../uniq.js';
13
13
  import ASTBuilder from './ASTBuilder.js';
14
+ import resolveIgnoredColumns from './resolveIgnoredColumns.js';
14
15
  /**
15
16
  * @internal
16
17
  *
@@ -265,12 +266,48 @@ may need to update the table getter in the corresponding Dream.
265
266
  default: uniq(models.flatMap(model => model['scopes'].default.map(scopeStatement => scopeStatement.method))),
266
267
  named: uniq(models.flatMap(model => model['scopes'].named.map(scopeStatement => scopeStatement.method))),
267
268
  },
268
- columns: await this.getColumnData(tableName, associationData),
269
+ columns: this.withoutIgnoredColumns(await this.getColumnData(tableName, associationData), tableName),
269
270
  virtualColumns: uniq(models.flatMap(model => model['virtualAttributes'].map(prop => prop.property) || [])),
270
271
  associations: associationData,
271
272
  serializerKeys,
272
273
  };
273
274
  }
275
+ /**
276
+ * @internal
277
+ *
278
+ * resolves the ignored columns declared by the models backed by the
279
+ * given table (validating the declarations; see resolveIgnoredColumns)
280
+ */
281
+ ignoredColumnsForTable(tableName) {
282
+ const dreamApp = DreamApp.getOrFail();
283
+ const allModels = Object.values(dreamApp.models).filter(model => model.prototype?.connectionName === this.connectionName);
284
+ const models = allModels.filter(model => model.table === tableName);
285
+ return resolveIgnoredColumns(models, tableName, allModels);
286
+ }
287
+ /**
288
+ * @internal
289
+ *
290
+ * returns the provided column data without the columns that the table's
291
+ * models declare in ignoredColumns. This is what removes ignored columns
292
+ * from the generated dream schema file: `columns()` reads the generated
293
+ * schema at runtime, so every column enumeration built from `columns()`
294
+ * (preload and join-load select lists, save hydration, attribute
295
+ * definition) inherits this filtering. A column that is ignored but not
296
+ * present in the introspected table (e.g. after the drop migration has
297
+ * run but before the declaration is removed) is a no-op.
298
+ */
299
+ withoutIgnoredColumns(columnData, tableName) {
300
+ const ignoredColumns = this.ignoredColumnsForTable(tableName);
301
+ if (!ignoredColumns.size)
302
+ return columnData;
303
+ return Object.keys(columnData)
304
+ .filter(columnName => !ignoredColumns.has(columnName))
305
+ .reduce((filtered, columnName) => {
306
+ ;
307
+ filtered[columnName] = columnData[columnName];
308
+ return filtered;
309
+ }, {});
310
+ }
274
311
  /**
275
312
  * @internal
276
313
  *