@rvoh/dream 2.25.0 → 2.26.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 +10 -1
  2. package/dist/cjs/src/cli/index.js +31 -5
  3. package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +127 -6
  4. package/dist/cjs/src/dream/internal/printSerializerHierarchyLevel.js +4 -3
  5. package/dist/cjs/src/dream/internal/resolveSerializerAssociationEdges.js +14 -1
  6. package/dist/cjs/src/errors/DropEnumValueRetypeFailed.js +208 -0
  7. package/dist/cjs/src/errors/serializers/MissingSerializersDefinition.js +16 -2
  8. package/dist/cjs/src/errors/serializers/MissingSerializersDefinitionForKey.js +5 -2
  9. package/dist/cjs/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.js +5 -3
  10. package/dist/cjs/src/errors/serializers/NoSerializersResolvedForKey.js +5 -2
  11. package/dist/cjs/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js +5 -3
  12. package/dist/cjs/src/errors/serializers/serializerResolutionContextMessage.js +46 -0
  13. package/dist/cjs/src/helpers/cli/generateFactoryContent.js +7 -1
  14. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +80 -13
  15. package/dist/cjs/src/helpers/cli/syncWithFailureContext.js +41 -0
  16. package/dist/cjs/src/serializer/SerializerRenderer.js +32 -4
  17. package/dist/cjs/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +28 -10
  18. package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +6 -1
  19. package/dist/cjs/src/serializer/helpers/serializerGlobalName.js +16 -0
  20. package/dist/esm/src/Dream.js +10 -1
  21. package/dist/esm/src/cli/index.js +31 -5
  22. package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +127 -6
  23. package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +4 -3
  24. package/dist/esm/src/dream/internal/resolveSerializerAssociationEdges.js +14 -1
  25. package/dist/esm/src/errors/DropEnumValueRetypeFailed.js +208 -0
  26. package/dist/esm/src/errors/serializers/MissingSerializersDefinition.js +16 -2
  27. package/dist/esm/src/errors/serializers/MissingSerializersDefinitionForKey.js +5 -2
  28. package/dist/esm/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.js +5 -3
  29. package/dist/esm/src/errors/serializers/NoSerializersResolvedForKey.js +5 -2
  30. package/dist/esm/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js +5 -3
  31. package/dist/esm/src/errors/serializers/serializerResolutionContextMessage.js +46 -0
  32. package/dist/esm/src/helpers/cli/generateFactoryContent.js +7 -1
  33. package/dist/esm/src/helpers/cli/generateMigrationContent.js +80 -13
  34. package/dist/esm/src/helpers/cli/syncWithFailureContext.js +41 -0
  35. package/dist/esm/src/serializer/SerializerRenderer.js +32 -4
  36. package/dist/esm/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +28 -10
  37. package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +6 -1
  38. package/dist/esm/src/serializer/helpers/serializerGlobalName.js +16 -0
  39. package/dist/types/src/Dream.d.ts +8 -2
  40. package/dist/types/src/cli/index.d.ts +1 -1
  41. package/dist/types/src/errors/DropEnumValueRetypeFailed.d.ts +89 -0
  42. package/dist/types/src/errors/serializers/MissingSerializersDefinition.d.ts +3 -1
  43. package/dist/types/src/errors/serializers/MissingSerializersDefinitionForKey.d.ts +3 -1
  44. package/dist/types/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.d.ts +3 -1
  45. package/dist/types/src/errors/serializers/NoSerializersResolvedForKey.d.ts +3 -1
  46. package/dist/types/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.d.ts +3 -1
  47. package/dist/types/src/errors/serializers/serializerResolutionContextMessage.d.ts +20 -0
  48. package/dist/types/src/helpers/cli/syncWithFailureContext.d.ts +58 -0
  49. package/dist/types/src/serializer/helpers/inferSerializerFromDreamOrViewModel.d.ts +11 -4
  50. package/dist/types/src/serializer/helpers/serializerGlobalName.d.ts +15 -0
  51. package/dist/types/src/types/serializer.d.ts +38 -0
  52. package/dist/types/src/types/types/serializer.ts +40 -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 +40 -40
  56. package/docs/classes/db.PostgresQueryDriver.html +41 -41
  57. package/docs/classes/db.QueryDriverBase.html +39 -39
  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 +123 -137
  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 +64 -64
  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
@@ -175,19 +175,8 @@ export default function generateMigrationContent({ connectionName = 'default', t
175
175
  }
176
176
  return acc;
177
177
  }, { columnDefs: [], columnDrops: [], indexDefs: [] });
178
- if (!table) {
179
- return `\
180
- import { Kysely, sql } from 'kysely'
181
-
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
- export async function up(db: Kysely<any>): Promise<void> {
184
- }
185
-
186
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
- export async function down(db: Kysely<any>): Promise<void> {
188
- }\
189
- `;
190
- }
178
+ if (!table)
179
+ return emptyMigrationContent();
191
180
  // An alter migration (either `-to-`/add or `-from-`/remove) with no valid
192
181
  // columns to add/drop would otherwise emit a bare `.alterTable(...).execute()`
193
182
  // with no column operation at all, which Postgres rejects at migration-run
@@ -207,6 +196,22 @@ export async function down(db: Kysely<any>): Promise<void> {
207
196
  !stiChildClassName) {
208
197
  throw new NoColumnsToAlterMigration(table, alterDirection, migrationName, alterMarker);
209
198
  }
199
+ // The placeholder table is the standalone `g:migration` markerless path (see
200
+ // MIGRATION_TABLE_NAME_PLACEHOLDER above): the migration name resolved no
201
+ // table, so there is nothing to alter. With no columns declared either, there
202
+ // is no column operation to scaffold, so emit genuinely empty `up`/`down`
203
+ // bodies for the developer to fill in rather than an
204
+ // `alterTable('<table-name>')` statement that cannot run as generated.
205
+ //
206
+ // The test is `columnsWithTypes`, deliberately, and not `columnDefs`: the two
207
+ // diverge. A declaration the reduce above filters out entirely (e.g.
208
+ // `comments:has_many`) leaves `columnDefs` empty while the user did type a
209
+ // column, and emptying the bodies there would silently discard it. Markerless
210
+ // *with* columns therefore keeps the placeholder scaffold — the table name is
211
+ // the only part the generator cannot work out, and `g:migration`'s help
212
+ // already documents that case as a stub to hand-edit.
213
+ if (altering && table === MIGRATION_TABLE_NAME_PLACEHOLDER && columnsWithTypes.length === 0)
214
+ return emptyMigrationContent();
210
215
  const citextExtension = requireCitextExtension
211
216
  ? ` await DreamMigrationHelpers.createExtension(db, 'citext')\n\n`
212
217
  : '';
@@ -273,6 +278,68 @@ ${downBody}
273
278
  }\
274
279
  `;
275
280
  }
281
+ /**
282
+ * A migration file with nothing in it: correct imports, the standard `up`/`down`
283
+ * signatures, and empty bodies.
284
+ *
285
+ * Three deliberate departures from the scaffolded migrations above. All three
286
+ * exist so that the file this generator writes is clean the instant it lands in
287
+ * a consumer's project — under their formatter as well as their linter. See
288
+ * `spec/unit/cli/generateMigrationContent.spec.ts`
289
+ * ("the generated empty migration is clean under a consumer's tooling"), which
290
+ * runs this exact output through Prettier and through ESLint's
291
+ * `recommended` + `typescript-eslint recommended` and pins zero of both.
292
+ *
293
+ * - The bodies are `{}`, closed on the signature's own line, not `{\n}`, and
294
+ * the file ends in a newline. Prettier collapses an empty block onto one line
295
+ * and requires a final newline, so either spelling is a formatting violation
296
+ * the moment it is written and fails `prettier --check` in the consumer's CI
297
+ * (and this repo's own `pnpm lint`, which ends in `pnpm prettier . --check`);
298
+ * `writeGeneratedFile` writes this string byte-for-byte with no formatting
299
+ * pass. The final newline is why this template does not end with the
300
+ * backslash line-continuation the scaffolded return above uses. (Those
301
+ * scaffolds are missing their own final newline, which is pre-existing and
302
+ * pinned by ~90 `toEqual` expectations across three spec files; it is not
303
+ * this template's to fix. `generateFactoryContent` and
304
+ * `generateUnitSpecContent` already terminate their output the way this one
305
+ * now does.)
306
+ * - `sql` is left out of the import list. Every other generated migration
307
+ * imports it unconditionally because most bodies need it, but an empty body
308
+ * never does, and an unused import is an error under
309
+ * `@typescript-eslint/no-unused-vars` in a file that would otherwise have
310
+ * nothing to report.
311
+ * - `db` keeps its name — the developer's first keystroke in the body is going
312
+ * to be `db.`, and dropping the parameter (with the `Kysely` import that only
313
+ * it uses) would make them type both back — and the existing
314
+ * `no-explicit-any` directive is widened to disable `no-unused-vars` too.
315
+ * `db` is unused until the body is written, and `no-unused-vars` is
316
+ * *error*-level under `typescript-eslint`'s `recommended` with
317
+ * `args: 'after-used'`, so without the directive `psy g:migration <name> &&
318
+ * pnpm lint` fails on a brand-new, untouched file. (The `await`-less `async`
319
+ * needs nothing: `require-await` deliberately ignores empty function bodies.)
320
+ * An `_db` rename is not a substitute: `no-unused-vars` sets no
321
+ * `argsIgnorePattern` by default, so `_db` reports too, and the developer
322
+ * then has to rename it back.
323
+ *
324
+ * The cost of the widened directive is that the `no-unused-vars` half goes
325
+ * stale once the body uses `db`, which ESLint 9 reports via its default
326
+ * `reportUnusedDisableDirectives`. That is a *warning* rather than the two
327
+ * *errors* it replaces, it lands in the file the developer is already
328
+ * editing, and `eslint --fix` removes just that rule name from the directive
329
+ * and leaves `no-explicit-any` in place — so it is strictly the cheaper of
330
+ * the two, in severity and in effort.
331
+ */
332
+ function emptyMigrationContent() {
333
+ return `\
334
+ import { Kysely } from 'kysely'
335
+
336
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
337
+ export async function up(db: Kysely<any>): Promise<void> {}
338
+
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
340
+ export async function down(db: Kysely<any>): Promise<void> {}
341
+ `;
342
+ }
276
343
  function getAttributeType(attributeType, descriptors) {
277
344
  switch (attributeType) {
278
345
  case 'string':
@@ -0,0 +1,41 @@
1
+ import DreamCLI from '../../cli/index.js';
2
+ /**
3
+ * Runs the type/schema sync a command performs after its own work has already
4
+ * succeeded, and — when that sync fails — logs a message naming what completed
5
+ * and naming the sync as the step that failed, before re-raising the original
6
+ * error untouched.
7
+ *
8
+ * Without this, a failing sync surfaces a bare sync error immediately after
9
+ * e.g. `db:rollback`, which reads as a failed rollback even though the
10
+ * rollback itself is already committed.
11
+ *
12
+ * The original error is re-thrown rather than wrapped, so the error a caller
13
+ * sees, and the command's non-zero exit status, are exactly what they were
14
+ * before. Callers keep their own `EnvInternal.isTest` / `--skip-sync` guards:
15
+ * this wrapper takes the sync call, not the decision to make it.
16
+ */
17
+ export default async function syncWithFailureContext({ commandName, completedWork, didNotRun, recoveryAdvice }, sync) {
18
+ try {
19
+ await sync();
20
+ }
21
+ catch (error) {
22
+ DreamCLI.logger.log(syncFailureMessage({ commandName, completedWork, didNotRun, recoveryAdvice }));
23
+ throw error;
24
+ }
25
+ }
26
+ /**
27
+ * @internal
28
+ *
29
+ * Builds the explanatory message logged when a post-work sync fails.
30
+ */
31
+ export function syncFailureMessage({ commandName, completedWork, didNotRun, recoveryAdvice, }) {
32
+ const skipped = didNotRun ? `; ${didNotRun}` : '';
33
+ const recovery = recoveryAdvice ?? 'run `sync` on its own';
34
+ return `
35
+ ${commandName}: the sync step failed — not ${commandName}'s own work.
36
+
37
+ ${completedWork}. What failed afterward is the sync that ${commandName} runs to regenerate the auto-generated type and schema files${skipped}.
38
+
39
+ The sync error itself is printed alongside this message. Once it is resolved, ${recovery}.
40
+ `;
41
+ }
@@ -97,7 +97,7 @@ export default class SerializerRenderer {
97
97
  const associatedObject = data[attribute.name];
98
98
  let serializer = null;
99
99
  if (associatedObject) {
100
- serializer = serializerForAssociatedObject(associatedObject, attribute.options);
100
+ serializer = serializerForAssociatedObject(associatedObject, attribute.options, renderTimeResolutionContext(attribute.options, 'rendersOne', attribute.name));
101
101
  }
102
102
  else if (attribute.options.flatten) {
103
103
  /**
@@ -146,8 +146,12 @@ export default class SerializerRenderer {
146
146
  const associatedObjects = data[attribute.name];
147
147
  if (!associatedObjects)
148
148
  throw new RendersManyMustReceiveArray(attribute, associatedObjects);
149
+ // Hoisted out of the map: the context is identical for every element, is immutable, and is
150
+ // read only while an error message is being built, so one object serves the whole
151
+ // collection rather than one per element.
152
+ const resolutionContext = renderTimeResolutionContext(attribute.options, 'rendersMany', attribute.name);
149
153
  accumulator[outputAttributeName] = compact(associatedObjects).map(associatedObject => {
150
- const serializer = serializerForAssociatedObject(associatedObject, attribute.options);
154
+ const serializer = serializerForAssociatedObject(associatedObject, attribute.options, resolutionContext);
151
155
  return (
152
156
  // passthrough data going into the serializer is the argument that gets
153
157
  // used in the custom attribute callback function
@@ -206,8 +210,32 @@ function _applyRenderingOptionsToAttribute(data, value, attributeName, options,
206
210
  return round(value, precision);
207
211
  return value ?? null;
208
212
  }
209
- function serializerForAssociatedObject(associatedObject, options) {
213
+ /**
214
+ * `resolutionContext` is error-only: it names the rendersOne/rendersMany this object is being
215
+ * rendered through, so that a serializer that cannot be resolved for it says which association led
216
+ * here rather than only which class failed. It is `undefined` whenever no resolution will happen —
217
+ * see `renderTimeResolutionContext`.
218
+ */
219
+ function serializerForAssociatedObject(associatedObject, options, resolutionContext) {
210
220
  if (options.serializer)
211
221
  return options.serializer;
212
- return inferSerializerFromDreamOrViewModel(associatedObject, options.serializerKey);
222
+ return inferSerializerFromDreamOrViewModel(associatedObject, options.serializerKey, resolutionContext);
223
+ }
224
+ /**
225
+ * Builds the error-only resolution context for one rendered association, or `undefined` when the
226
+ * render will not resolve a serializer at all.
227
+ *
228
+ * Rendering is a hot path — once per rendered association per request, and `rendersMany` renders one
229
+ * element at a time from a collection — so this deliberately allocates nothing it cannot use:
230
+ * `serializerForAssociatedObject` returns `options.serializer` before resolving anything when the
231
+ * association declares its own serializer, and in that case there is no resolution to describe.
232
+ * Callers build this once per rendered association and share it across every element.
233
+ *
234
+ * The renderer holds no serializer object, so the edge carries no `declaredBy` — see
235
+ * `SerializerResolutionContext`.
236
+ */
237
+ function renderTimeResolutionContext(options, type, associationName) {
238
+ if (options.serializer)
239
+ return undefined;
240
+ return { edge: { type, associationName } };
213
241
  }
@@ -9,33 +9,51 @@ import expandStiClasses from '../../helpers/sti/expandStiClasses.js';
9
9
  import uniq from '../../helpers/uniq.js';
10
10
  import isDreamSerializer from './isDreamSerializer.js';
11
11
  export const DEFAULT_SERIALIZER_KEY = 'default';
12
- export default function inferSerializerFromDreamOrViewModel(obj, serializerKey = DEFAULT_SERIALIZER_KEY) {
12
+ /**
13
+ * `resolutionContext` says how resolution reached `obj` — the `rendersOne`/`rendersMany` edge that
14
+ * led here, and the STI base it was expanded from. It is threaded down purely so that the errors
15
+ * below can name the route instead of only the class, and it is optional so that a caller with no
16
+ * route to describe (and every existing caller outside this repo) is unaffected. See
17
+ * `SerializerResolutionContext`.
18
+ */
19
+ export default function inferSerializerFromDreamOrViewModel(obj, serializerKey = DEFAULT_SERIALIZER_KEY, resolutionContext) {
13
20
  if (!obj)
14
21
  return null;
15
22
  const serializers = obj.serializers;
16
23
  if (!serializers)
17
- throw new MissingSerializersDefinition(obj);
24
+ throw new MissingSerializersDefinition(obj, serializerKey, resolutionContext);
18
25
  const serializerOrGlobalName = serializers[serializerKey];
19
26
  if (!serializerOrGlobalName)
20
- throw new MissingSerializersDefinitionForKey(obj, serializerKey);
27
+ throw new MissingSerializersDefinitionForKey(obj, serializerKey, resolutionContext);
21
28
  if (isDreamSerializer(serializerOrGlobalName))
22
29
  return serializerOrGlobalName;
23
30
  const globalName = serializerOrGlobalName;
24
31
  const dreamApp = DreamApp.getOrFail();
25
32
  const serializer = dreamApp.serializers[globalName];
26
33
  if (!serializer)
27
- throw new NoGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName);
34
+ throw new NoGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName, resolutionContext);
28
35
  if (!isDreamSerializer(serializer))
29
- throw new NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName, serializer);
36
+ throw new NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName, serializer, resolutionContext);
30
37
  return serializer;
31
38
  }
32
- export function inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey = undefined) {
39
+ export function inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey = undefined, resolutionContext) {
33
40
  if (!classDef)
34
41
  return [];
35
42
  const classes = expandStiClasses(classDef);
36
- const serializers = classes.map(classDef => inferSerializerFromDreamOrViewModel(classDef.prototype, serializerKey));
43
+ // This is the one place that knows *whether* the class an error ends up naming came from STI
44
+ // expansion: below this line the class the caller passed is gone, replaced by one child per
45
+ // iteration, and every downstream error names a child. Identity rather than `isSTIBase` because
46
+ // `classDef` may be a ViewModelClass, which `expandStiClasses` hands straight back.
47
+ const expandedFromStiBase = classes.length !== 1 || classes[0] !== classDef;
48
+ const childResolutionContext = expandedFromStiBase
49
+ ? { ...resolutionContext, stiBase: stiBaseName(classDef) }
50
+ : resolutionContext;
51
+ const serializers = classes.map(classDef => inferSerializerFromDreamOrViewModel(classDef.prototype, serializerKey, childResolutionContext));
37
52
  return uniq(compact(serializers));
38
53
  }
54
+ function stiBaseName(classDef) {
55
+ return classDef.sanitizedName ?? classDef.name;
56
+ }
39
57
  /**
40
58
  * As `inferSerializersFromDreamClassOrViewModelClass`, but guarantees a non-empty result.
41
59
  *
@@ -51,9 +69,9 @@ export function inferSerializersFromDreamClassOrViewModelClass(classDef, seriali
51
69
  * preload-path building and serialization display — so that they raise one typed error between
52
70
  * them.
53
71
  */
54
- export function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef, serializerKey) {
55
- const serializers = inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey);
72
+ export function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef, serializerKey, resolutionContext) {
73
+ const serializers = inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey, resolutionContext);
56
74
  if (serializers.length === 0)
57
- throw new NoSerializersResolvedForKey(classDef, serializerKey);
75
+ throw new NoSerializersResolvedForKey(classDef, serializerKey, resolutionContext);
58
76
  return serializers;
59
77
  }
@@ -28,5 +28,10 @@ export function serializerForAssociatedClass(object, associationName, options) {
28
28
  // would flatten. Unioning every child's serializer here — which is what buildSerializerPreloadPaths
29
29
  // does for preload paths — is meaningless: exactly one set of keys can be flattened into the
30
30
  // payload.
31
- return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey)[0] ?? null;
31
+ return (inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey, {
32
+ // Reached only from SerializerRenderer's rendersOne branch, so the edge type is fixed. No
33
+ // declaring serializer: the renderer holds the rendered attribute, not the serializer that
34
+ // declared it.
35
+ edge: { type: 'rendersOne', associationName },
36
+ })[0] ?? null);
32
37
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @internal
3
+ *
4
+ * The single reader of a serializer's `globalName`.
5
+ *
6
+ * Serializers are plain functions; `globalName` is stamped onto them by `importSerializers` when the
7
+ * app loads them, so the property is not on any serializer type and every reader would otherwise
8
+ * have to invent its own cast — three different spellings of it existed before this helper.
9
+ *
10
+ * `undefined` is a legitimate answer: a serializer built inline (a spec, or a `{ serializer }`
11
+ * option) is never stamped. Callers decide what that means for them — the resolution-context message
12
+ * simply drops the declarer.
13
+ */
14
+ export default function serializerGlobalName(serializer) {
15
+ return serializer.globalName;
16
+ }
@@ -8,7 +8,7 @@ import LoadBuilder from './dream/LoadBuilder.js';
8
8
  import Query from './dream/Query.js';
9
9
  import { OnStatementForSpecificColumns, PassthroughOnClause, WhereStatement } from './types/associations/shared.js';
10
10
  import { AssociationTableNames, DbConnectionType } from './types/db.js';
11
- import { AllDefaultScopeNames, AssociationNameToDream, AttributeKeys, CreateOrFindByExtraOpts, DefaultOrNamedScopeName, DreamAssociationNames, DreamAssociationNamesWithoutRequiredOnClauses, DreamAssociationNamesWithPassthroughOnClauses, DreamAssociationNamesWithRequiredOnClauses, DreamAttributes, DreamBelongsToAssociationNames, DreamColumnNames, DreamHasManyAssociationNames, DreamHasOneAssociationNames, DreamModelAssociationNames, DreamParamSafeColumnNames, DreamPrimaryKeyType, DreamSerializerKey, FindablePropertiesForClass, GlobalModelNames, GlobalSerializerName, JoinAndStatements, OrderDir, PassthroughColumnNames, PassthroughOnClauseKeys, PluckEachArgs, PrimaryKeyForFind, RequiredOnClauseKeys, TableColumnNames, UpdateableAssociationProperties, UpdateableProperties, UpdateablePropertiesForClass, UpdateOrCreateByExtraOpts } from './types/dream.js';
11
+ import { AllDefaultScopeNames, AssociationNameToDream, AttributeKeys, CreateOrFindByExtraOpts, DefaultOrNamedScopeName, DreamAssociationNames, DreamAssociationNamesWithoutRequiredOnClauses, DreamAssociationNamesWithPassthroughOnClauses, DreamAssociationNamesWithRequiredOnClauses, DreamAttributes, DreamBelongsToAssociationNames, DreamColumnNames, DreamHasManyAssociationNames, DreamHasOneAssociationNames, DreamModelAssociationNames, DreamPrimaryKeyType, DreamSerializerKey, FindablePropertiesForClass, GlobalModelNames, GlobalSerializerName, JoinAndStatements, OrderDir, PassthroughColumnNames, PassthroughOnClauseKeys, PluckEachArgs, PrimaryKeyForFind, RequiredOnClauseKeys, TableColumnNames, UpdateableAssociationProperties, UpdateableProperties, UpdateablePropertiesForClass, UpdateOrCreateByExtraOpts } from './types/dream.js';
12
12
  import { BaseModelColumnTypes, CursorPaginatedDreamQueryOptions, CursorPaginatedDreamQueryResult, DefaultQueryTypeOptions, FindEachOpts, LoadForModifierFn, PaginatedDreamQueryOptions, PaginatedDreamQueryResult, QueryWithJoinedAssociationsType, QueryWithJoinedAssociationsTypeAndNoPreload } from './types/query.js';
13
13
  import { DreamModelSerializerType, SimpleObjectSerializerType } from './types/serializer.js';
14
14
  import { ValidationType } from './types/validation.js';
@@ -458,9 +458,15 @@ export default class Dream {
458
458
  * * foreign key fields for belongs to associations (these should usually be verified before being set)
459
459
  * * type fields corresponding to polymorphic associations
460
460
  *
461
+ * @internal
462
+ *
463
+ * Not part of the public API. Psychic reaches it through the bracketed
464
+ * back-door (`dreamClass['paramSafeColumnsOrFallback']()`), the same escape
465
+ * hatch used for other Dream internals such as `virtualAttributes`.
466
+ *
461
467
  * @returns A subset of columns for the given dream class
462
468
  */
463
- static paramSafeColumnsOrFallback<T extends typeof Dream, I extends InstanceType<T>, ParamSafeColumnsOverride extends InstanceType<T>['paramSafeColumns' & keyof InstanceType<T>] extends never ? undefined : InstanceType<T>['paramSafeColumns' & keyof InstanceType<T>] & string[], ReturnVal extends ParamSafeColumnsOverride extends string[] ? Extract<DreamParamSafeColumnNames<I>, ParamSafeColumnsOverride[number] & DreamParamSafeColumnNames<I>>[] : DreamParamSafeColumnNames<I>[]>(this: T): ReturnVal;
469
+ private static paramSafeColumnsOrFallback;
464
470
  private static defaultParamSafeColumns;
465
471
  /**
466
472
  * @internal
@@ -23,7 +23,7 @@ export declare const columnsWithTypesDescriptionForStiChild: string;
23
23
  * example that drifts from what the generator actually produces then fails the
24
24
  * suite instead of shipping.
25
25
  */
26
- export declare const migrationNameArgumentDescription = "Kebab-case name describing the change. A -to-<table_name>\n anywhere in the name \u2014 not just at the end \u2014 auto-generates\n an add-columns alterTable scaffold for that table;\n -from-<table_name> generates a remove-columns scaffold, but\n only when the name contains no -to- at all. The text after\n the marker's first occurrence is snake-cased and pluralized\n into the table name, so add-a-to-b-to-cs targets b_to_cs. A\n hand-written migration must avoid both strings anywhere in\n its name.\n \n Examples:\n add-phone-to-users phone:encrypted # scaffolds alterTable('users', ...)\n remove-status-from-posts status:string # scaffolds alterTable('posts', ...)\n create-many-to-many-posts-users # WRONG: selects 'many_posts_users'\n # -to- inside \"many-to-many\" matches: with no columns\n # this errors; with columns it silently scaffolds it\n create-posts-users-join-table # correct: no marker match\n # scaffolds alterTable('<table-name>') to hand-edit";
26
+ export declare const migrationNameArgumentDescription = "Kebab-case name describing the change. A -to-<table_name>\n anywhere in the name \u2014 not just at the end \u2014 auto-generates\n an add-columns alterTable scaffold for that table;\n -from-<table_name> generates a remove-columns scaffold, but\n only when the name contains no -to- at all. The text after\n the marker's first occurrence is snake-cased and pluralized\n into the table name, so add-a-to-b-to-cs targets b_to_cs. A\n hand-written migration must avoid both strings anywhere in\n its name.\n \n Examples:\n add-phone-to-users phone:encrypted # scaffolds alterTable('users', ...)\n remove-status-from-posts status:string # scaffolds alterTable('posts', ...)\n create-many-to-many-posts-users # WRONG: selects 'many_posts_users'\n # -to- inside \"many-to-many\" matches: with no columns\n # this errors; with columns it silently scaffolds it\n create-posts-users-join-table # correct: no marker match\n # with no columns, generates empty up/down methods to\n # write by hand; with columns, scaffolds\n # alterTable('<table-name>') to hand-edit";
27
27
  export default class DreamCLI {
28
28
  /**
29
29
  * Starts the Dream console
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A check constraint discovered on the column `dropEnumValue` was retyping.
3
+ */
4
+ export interface DropEnumValueCheckConstraint {
5
+ /** the constraint's name in `pg_constraint`, e.g. `pets_species_check` */
6
+ name: string;
7
+ /** the constraint as Postgres renders it, e.g. `CHECK (((species)::text <> 'forg'::text))` */
8
+ definition: string;
9
+ }
10
+ export interface DropEnumValueRetypeFailedOpts {
11
+ enumName: string;
12
+ value: string;
13
+ table: string;
14
+ column: string;
15
+ /**
16
+ * `toText` for the widen step that runs before the enum type is dropped and
17
+ * recreated; `toEnum` for the restore step that runs after it.
18
+ */
19
+ retypeDirection: 'toText' | 'toEnum';
20
+ /**
21
+ * Whether the pre-flight catalog read resolved `table` to a real relation. When it did
22
+ * not, `checkConstraints` being empty means "not read", not "none defined", and the
23
+ * message says so instead of asserting a negative it cannot support.
24
+ */
25
+ relationResolved: boolean;
26
+ checkConstraints: DropEnumValueCheckConstraint[];
27
+ originalError: Error;
28
+ }
29
+ /**
30
+ * Quote every segment of a possibly schema-qualified table name the way Kysely quotes the
31
+ * identifiers it emits, so a `to_regclass` argument resolves exactly the relation the retype
32
+ * targeted. Exported because `DreamMigrationHelpers`' catalog read and the diagnostic query in
33
+ * this message must name the relation identically; it lives here because that helper already
34
+ * imports this module, and the reverse import would be a cycle.
35
+ */
36
+ export declare function quotedRelationName(table: string): string;
37
+ /**
38
+ * Raised when one of the two column retypes inside
39
+ * `DreamMigrationHelpers.dropEnumValue` fails.
40
+ *
41
+ * The raw Postgres error for the common cause of this - a check constraint on the
42
+ * column that references the enum - is `operator does not exist: text <> my_enum`.
43
+ * It names no table, no column and no constraint, and the `text` in it comes from
44
+ * `dropEnumValue`'s own internal widen step, so it appears nowhere in the
45
+ * migration the developer wrote. This error supplies all of that, and keeps the
46
+ * original error reachable through both `originalError` and `cause`.
47
+ *
48
+ * It never claims a check constraint caused a failure it cannot tie to one. The SQLSTATE gate
49
+ * separates "a stored expression could not be re-derived" from "a bad value" — but it cannot
50
+ * say *which* expression, because an index predicate raises the identical code, so even on the
51
+ * gated path the constraints stay context and the message names the other candidates.
52
+ */
53
+ export default class DropEnumValueRetypeFailed extends Error {
54
+ readonly enumName: string;
55
+ readonly value: string;
56
+ readonly table: string;
57
+ readonly column: string;
58
+ readonly retypeDirection: 'toText' | 'toEnum';
59
+ readonly relationResolved: boolean;
60
+ readonly checkConstraints: DropEnumValueCheckConstraint[];
61
+ readonly originalError: Error;
62
+ constructor({ enumName, value, table, column, retypeDirection, relationResolved, checkConstraints, originalError, }: DropEnumValueRetypeFailedOpts);
63
+ get message(): string;
64
+ private get failingStepDescription();
65
+ private get stepExplanation();
66
+ /**
67
+ * True when the original error is one Postgres raises because a stored expression on the
68
+ * column could not be re-derived against its new type. A blocking check constraint is one
69
+ * source of that; a partial index predicate, an expression index and an uncastable column
70
+ * default are others. Anything else (a bad value, a missing relation, a permission error) is
71
+ * not evidence that any stored expression was involved.
72
+ */
73
+ private get originalErrorBlockedAnExpression();
74
+ private get checkConstraintsSection();
75
+ private get guidanceSection();
76
+ /**
77
+ * What the SQLSTATE does and does not establish, and every expression that raises it. The
78
+ * candidates are listed rather than diagnosed on purpose: `dropEnumValue` reads the column's
79
+ * check constraints and nothing else, so naming one of them as the cause would be a guess
80
+ * that sends the caller to drop a constraint that changes nothing.
81
+ */
82
+ private get expressionBlockerSection();
83
+ private get checkConstraintCandidateNote();
84
+ /**
85
+ * The copy-pasteable remedy, offered only when there is a constraint to drop and phrased as
86
+ * conditional on that constraint actually being the blocker — which this error cannot know.
87
+ */
88
+ private get constraintRemedySection();
89
+ }
@@ -1,7 +1,9 @@
1
1
  import Dream from '../../Dream.js';
2
2
  import { ViewModel } from '../../types/dream.js';
3
+ import { SerializerResolutionContext } from '../../types/serializer.js';
3
4
  export default class MissingSerializersDefinition extends Error {
5
+ #private;
4
6
  private viewModel;
5
- constructor(viewModel: Dream | ViewModel);
7
+ constructor(viewModel: Dream | ViewModel, serializerKey?: string, resolutionContext?: SerializerResolutionContext);
6
8
  get message(): string;
7
9
  }
@@ -1,8 +1,10 @@
1
1
  import Dream from '../../Dream.js';
2
2
  import { ViewModel } from '../../types/dream.js';
3
+ import { SerializerResolutionContext } from '../../types/serializer.js';
3
4
  export default class MissingSerializersDefinitionForKey extends Error {
4
5
  private viewModel;
5
6
  private serializerKey;
6
- constructor(viewModel: Dream | ViewModel, serializerKey: string);
7
+ private resolutionContext?;
8
+ constructor(viewModel: Dream | ViewModel, serializerKey: string, resolutionContext?: SerializerResolutionContext | undefined);
7
9
  get message(): string;
8
10
  }
@@ -1,9 +1,11 @@
1
1
  import Dream from '../../Dream.js';
2
2
  import { ViewModel } from '../../types/dream.js';
3
+ import { SerializerResolutionContext } from '../../types/serializer.js';
3
4
  export default class NoGlobalSerializerForSpecifiedKey extends Error {
4
5
  private viewModel;
5
6
  private serializerKey;
6
7
  private globalName;
7
- constructor(viewModel: Dream | ViewModel, serializerKey: string, globalName: string);
8
+ private resolutionContext?;
9
+ constructor(viewModel: Dream | ViewModel, serializerKey: string, globalName: string, resolutionContext?: SerializerResolutionContext | undefined);
8
10
  get message(): string;
9
11
  }
@@ -1,5 +1,6 @@
1
1
  import Dream from '../../Dream.js';
2
2
  import { ViewModelClass } from '../../types/dream.js';
3
+ import { SerializerResolutionContext } from '../../types/serializer.js';
3
4
  /**
4
5
  * Raised when resolving a serializer key against a class yields an empty set of serializers.
5
6
  *
@@ -13,6 +14,7 @@ import { ViewModelClass } from '../../types/dream.js';
13
14
  export default class NoSerializersResolvedForKey extends Error {
14
15
  private classDef;
15
16
  private serializerKey;
16
- constructor(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey: string);
17
+ private resolutionContext?;
18
+ constructor(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey: string, resolutionContext?: SerializerResolutionContext | undefined);
17
19
  get message(): string;
18
20
  }
@@ -1,10 +1,12 @@
1
1
  import Dream from '../../Dream.js';
2
2
  import { ViewModel } from '../../types/dream.js';
3
+ import { SerializerResolutionContext } from '../../types/serializer.js';
3
4
  export default class NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey extends Error {
4
5
  private viewModel;
5
6
  private serializerKey;
6
7
  private globalName;
7
8
  private serializer;
8
- constructor(viewModel: Dream | ViewModel, serializerKey: string, globalName: string, serializer: any);
9
+ private resolutionContext?;
10
+ constructor(viewModel: Dream | ViewModel, serializerKey: string, globalName: string, serializer: any, resolutionContext?: SerializerResolutionContext | undefined);
9
11
  get message(): string;
10
12
  }
@@ -0,0 +1,20 @@
1
+ import { SerializerResolutionContext } from '../../types/serializer.js';
2
+ /**
3
+ * @internal
4
+ *
5
+ * Renders the trailing "how did resolution reach this class" block shared by the five
6
+ * serializer-resolution errors.
7
+ *
8
+ * Every line is independently well-formed and a line is omitted entirely — no placeholder, no empty
9
+ * slot — when the path that raised the error does not have that fact. The paths differ, and
10
+ * deliberately so:
11
+ *
12
+ * - the serializer key is on every path, since it is an argument of resolution itself;
13
+ * - the render edge is present only when a `rendersOne`/`rendersMany` led to the failing class. The
14
+ * three root entry points (`Dream.serializationMap`, `Dream.displaySerialization` and
15
+ * `buildSerializerPreloadPaths`) resolve a class the caller named directly, so nothing led there;
16
+ * - the declaring serializer is present only while preload paths and serialization maps are built.
17
+ * `SerializerRenderer` holds the rendered attribute but not the serializer that declared it;
18
+ * - the STI base is present only where expanding one produced the class the error names.
19
+ */
20
+ export default function serializerResolutionContextMessage(serializerKey: string | undefined, context: SerializerResolutionContext | undefined): string;
@@ -0,0 +1,58 @@
1
+ export interface SyncFailureContext {
2
+ /**
3
+ * The command the sync is running on behalf of, e.g. `db:rollback`.
4
+ */
5
+ commandName: string;
6
+ /**
7
+ * What the command finished before the sync ran, written as a sentence with
8
+ * no trailing period, e.g. `The rollback completed successfully`. It must
9
+ * only claim the steps that genuinely ran to completion — for a command
10
+ * whose sync sits in the middle of a sequence, name those steps rather than
11
+ * the command as a whole.
12
+ */
13
+ completedWork: string;
14
+ /**
15
+ * What the sync failure prevented from running, written as a clause with no
16
+ * trailing period, e.g. `db:seed did not run`. Omit it when the sync is the
17
+ * last thing the command does, in which case nothing was skipped.
18
+ */
19
+ didNotRun?: string | undefined;
20
+ /**
21
+ * What the operator should do once the underlying sync error is fixed,
22
+ * written as an imperative clause with no trailing period, e.g.
23
+ * ``run `sync` on its own``. It is rendered as
24
+ * `Once it is resolved, ${recoveryAdvice}.`
25
+ *
26
+ * This is deliberately per-command rather than templated off `commandName`,
27
+ * because "re-run the command" is only correct for commands that are safe to
28
+ * repeat. `db:rollback` is the counter-example that motivates the field: its
29
+ * work is already committed by the time the sync runs, so re-running it rolls
30
+ * back a *further* migration rather than retrying the step that failed.
31
+ *
32
+ * Defaults to ``run `sync` on its own`` — the retry that is safe after any of
33
+ * these commands, since it repeats only the step that actually failed.
34
+ */
35
+ recoveryAdvice?: string | undefined;
36
+ }
37
+ /**
38
+ * Runs the type/schema sync a command performs after its own work has already
39
+ * succeeded, and — when that sync fails — logs a message naming what completed
40
+ * and naming the sync as the step that failed, before re-raising the original
41
+ * error untouched.
42
+ *
43
+ * Without this, a failing sync surfaces a bare sync error immediately after
44
+ * e.g. `db:rollback`, which reads as a failed rollback even though the
45
+ * rollback itself is already committed.
46
+ *
47
+ * The original error is re-thrown rather than wrapped, so the error a caller
48
+ * sees, and the command's non-zero exit status, are exactly what they were
49
+ * before. Callers keep their own `EnvInternal.isTest` / `--skip-sync` guards:
50
+ * this wrapper takes the sync call, not the decision to make it.
51
+ */
52
+ export default function syncWithFailureContext({ commandName, completedWork, didNotRun, recoveryAdvice }: SyncFailureContext, sync: () => Promise<void>): Promise<void>;
53
+ /**
54
+ * @internal
55
+ *
56
+ * Builds the explanatory message logged when a post-work sync fails.
57
+ */
58
+ export declare function syncFailureMessage({ commandName, completedWork, didNotRun, recoveryAdvice, }: SyncFailureContext): string;
@@ -1,9 +1,16 @@
1
1
  import Dream from '../../Dream.js';
2
2
  import { ViewModel, ViewModelClass } from '../../types/dream.js';
3
- import { DreamModelSerializerType, SerializerType, SimpleObjectSerializerType } from '../../types/serializer.js';
3
+ import { DreamModelSerializerType, SerializerResolutionContext, SerializerType, SimpleObjectSerializerType } from '../../types/serializer.js';
4
4
  export declare const DEFAULT_SERIALIZER_KEY = "default";
5
- export default function inferSerializerFromDreamOrViewModel<T extends Dream | ViewModel | null | undefined, ReturnType extends T extends null | undefined ? null : T extends Dream | ViewModel ? SerializerType : never>(obj: T, serializerKey?: string | undefined): ReturnType;
6
- export declare function inferSerializersFromDreamClassOrViewModelClass(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey?: string | undefined): (DreamModelSerializerType | SimpleObjectSerializerType)[];
5
+ /**
6
+ * `resolutionContext` says how resolution reached `obj` — the `rendersOne`/`rendersMany` edge that
7
+ * led here, and the STI base it was expanded from. It is threaded down purely so that the errors
8
+ * below can name the route instead of only the class, and it is optional so that a caller with no
9
+ * route to describe (and every existing caller outside this repo) is unaffected. See
10
+ * `SerializerResolutionContext`.
11
+ */
12
+ export default function inferSerializerFromDreamOrViewModel<T extends Dream | ViewModel | null | undefined, ReturnType extends T extends null | undefined ? null : T extends Dream | ViewModel ? SerializerType : never>(obj: T, serializerKey?: string | undefined, resolutionContext?: SerializerResolutionContext): ReturnType;
13
+ export declare function inferSerializersFromDreamClassOrViewModelClass(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey?: string | undefined, resolutionContext?: SerializerResolutionContext): (DreamModelSerializerType | SimpleObjectSerializerType)[];
7
14
  /**
8
15
  * As `inferSerializersFromDreamClassOrViewModelClass`, but guarantees a non-empty result.
9
16
  *
@@ -19,4 +26,4 @@ export declare function inferSerializersFromDreamClassOrViewModelClass(classDef:
19
26
  * preload-path building and serialization display — so that they raise one typed error between
20
27
  * them.
21
28
  */
22
- export declare function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey: string): (DreamModelSerializerType | SimpleObjectSerializerType)[];
29
+ export declare function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey: string, resolutionContext?: SerializerResolutionContext): (DreamModelSerializerType | SimpleObjectSerializerType)[];