@rvoh/dream 2.38.0 → 2.40.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 (221) hide show
  1. package/README.md +2 -34
  2. package/dist/cjs/src/dream-app/index.js +31 -1
  3. package/dist/cjs/src/helpers/areEqual.js +2 -2
  4. package/dist/cjs/src/helpers/path/absoluteDreamPath.js +5 -3
  5. package/dist/cjs/src/serializer/DreamSerializer.js +5 -5
  6. package/dist/cjs/src/serializer/ObjectSerializer.js +5 -5
  7. package/dist/cjs/src/serializer/SerializerRenderer.js +21 -28
  8. package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +40 -33
  9. package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +38 -33
  10. package/dist/cjs/src/serializer/helpers/nullFlattenedSerializerKeys.js +44 -68
  11. package/dist/cjs/src/serializer/helpers/serializersForAssociatedClass.js +4 -3
  12. package/dist/esm/src/dream-app/index.js +31 -1
  13. package/dist/esm/src/helpers/areEqual.js +2 -2
  14. package/dist/esm/src/helpers/path/absoluteDreamPath.js +5 -3
  15. package/dist/esm/src/serializer/DreamSerializer.js +5 -5
  16. package/dist/esm/src/serializer/ObjectSerializer.js +5 -5
  17. package/dist/esm/src/serializer/SerializerRenderer.js +21 -28
  18. package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +40 -33
  19. package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +38 -33
  20. package/dist/esm/src/serializer/helpers/nullFlattenedSerializerKeys.js +44 -68
  21. package/dist/esm/src/serializer/helpers/serializersForAssociatedClass.js +4 -3
  22. package/dist/types/src/dream-app/index.d.ts +26 -3
  23. package/dist/types/src/serializer/DreamSerializer.d.ts +5 -5
  24. package/dist/types/src/serializer/ObjectSerializer.d.ts +5 -5
  25. package/dist/types/src/serializer/SerializerRenderer.d.ts +1 -1
  26. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +43 -35
  27. package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +41 -35
  28. package/dist/types/src/serializer/helpers/nullFlattenedSerializerKeys.d.ts +43 -33
  29. package/dist/types/src/serializer/helpers/serializersForAssociatedClass.d.ts +4 -3
  30. package/docs/assets/search.js +1 -1
  31. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  32. package/docs/classes/db.KyselyQueryDriver.html +49 -49
  33. package/docs/classes/db.PostgresQueryDriver.html +49 -49
  34. package/docs/classes/db.QueryDriverBase.html +49 -49
  35. package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
  36. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  37. package/docs/classes/errors.ColumnOverflow.html +3 -3
  38. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  39. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  40. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  41. package/docs/classes/errors.DecryptionError.html +2 -2
  42. package/docs/classes/errors.DecryptionParseError.html +2 -2
  43. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  44. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  45. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  46. package/docs/classes/errors.InvalidClockTime.html +2 -2
  47. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  48. package/docs/classes/errors.InvalidDateTime.html +2 -2
  49. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  50. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  51. package/docs/classes/errors.NotNullViolation.html +3 -3
  52. package/docs/classes/errors.RecordNotFound.html +3 -3
  53. package/docs/classes/errors.SortableScopeDidNotStabilize.html +3 -3
  54. package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +3 -3
  55. package/docs/classes/errors.ValidationError.html +3 -3
  56. package/docs/classes/index.CalendarDate.html +33 -33
  57. package/docs/classes/index.ClockTime.html +32 -32
  58. package/docs/classes/index.ClockTimeTz.html +35 -35
  59. package/docs/classes/index.DateTime.html +86 -86
  60. package/docs/classes/index.Decorators.html +19 -19
  61. package/docs/classes/index.Dream.html +125 -125
  62. package/docs/classes/index.DreamApp.html +26 -11
  63. package/docs/classes/index.DreamTransaction.html +3 -3
  64. package/docs/classes/index.Env.html +5 -5
  65. package/docs/classes/index.Query.html +65 -65
  66. package/docs/classes/system.CliFileWriter.html +4 -4
  67. package/docs/classes/system.DreamBin.html +3 -3
  68. package/docs/classes/system.DreamCLI.html +7 -7
  69. package/docs/classes/system.DreamImporter.html +2 -2
  70. package/docs/classes/system.DreamLogos.html +2 -2
  71. package/docs/classes/system.DreamSerializerBuilder.html +51 -44
  72. package/docs/classes/system.ObjectSerializerBuilder.html +49 -43
  73. package/docs/classes/system.PathHelpers.html +3 -3
  74. package/docs/classes/utils.Encrypt.html +4 -4
  75. package/docs/classes/utils.Range.html +2 -2
  76. package/docs/functions/db.closeAllDbConnections.html +1 -1
  77. package/docs/functions/db.dreamDbConnections.html +1 -1
  78. package/docs/functions/db.untypedDb.html +1 -1
  79. package/docs/functions/db.validateColumn.html +1 -1
  80. package/docs/functions/db.validateTable.html +1 -1
  81. package/docs/functions/errors.pgErrorType.html +1 -1
  82. package/docs/functions/index.DreamSerializer.html +6 -6
  83. package/docs/functions/index.ObjectSerializer.html +6 -6
  84. package/docs/functions/index.ReplicaSafe.html +1 -1
  85. package/docs/functions/index.STI.html +1 -1
  86. package/docs/functions/index.SoftDelete.html +1 -1
  87. package/docs/functions/utils.camelize.html +1 -1
  88. package/docs/functions/utils.capitalize.html +1 -1
  89. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  90. package/docs/functions/utils.compact.html +1 -1
  91. package/docs/functions/utils.groupBy.html +1 -1
  92. package/docs/functions/utils.hyphenize.html +1 -1
  93. package/docs/functions/utils.intersection.html +1 -1
  94. package/docs/functions/utils.isEmpty.html +1 -1
  95. package/docs/functions/utils.normalizeUnicode.html +1 -1
  96. package/docs/functions/utils.pascalize.html +1 -1
  97. package/docs/functions/utils.percent.html +1 -1
  98. package/docs/functions/utils.range.html +1 -1
  99. package/docs/functions/utils.round.html +1 -1
  100. package/docs/functions/utils.sanitizeString.html +1 -1
  101. package/docs/functions/utils.snakeify.html +1 -1
  102. package/docs/functions/utils.sort.html +1 -1
  103. package/docs/functions/utils.sortBy.html +1 -1
  104. package/docs/functions/utils.sortObjectByKey.html +1 -1
  105. package/docs/functions/utils.sortObjectByValue.html +1 -1
  106. package/docs/functions/utils.uncapitalize.html +1 -1
  107. package/docs/functions/utils.uniq.html +1 -1
  108. package/docs/index.html +2 -9
  109. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  110. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  111. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  112. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  113. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  114. package/docs/interfaces/types.DecoratorContext.html +2 -2
  115. package/docs/interfaces/types.DecryptCallbacks.html +2 -2
  116. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  117. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  118. package/docs/interfaces/types.DreamDbConfig.html +6 -6
  119. package/docs/interfaces/types.DurationObject.html +2 -2
  120. package/docs/interfaces/types.EncryptOptions.html +2 -2
  121. package/docs/interfaces/types.EncryptionLegacyKeyUsedEvent.html +4 -4
  122. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  123. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  124. package/docs/interfaces/types.IntrospectedColumn.html +7 -7
  125. package/docs/interfaces/types.IntrospectedDatabase.html +2 -2
  126. package/docs/interfaces/types.IntrospectedEnum.html +2 -2
  127. package/docs/interfaces/types.IntrospectedTable.html +5 -5
  128. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  129. package/docs/media/CONTRIBUTING.md +128 -0
  130. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  131. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  132. package/docs/types/openapi.OpenapiFormats.html +1 -1
  133. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  134. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  135. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  136. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  137. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  138. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  139. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  141. package/docs/types/openapi.OpenapiSchemaCombinatorFields.html +1 -1
  142. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  143. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  144. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  145. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  146. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  147. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  148. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  149. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  150. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  162. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  163. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  164. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  165. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  166. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  167. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  168. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  169. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  170. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  171. package/docs/types/types.CalendarDateObject.html +1 -1
  172. package/docs/types/types.Camelized.html +1 -1
  173. package/docs/types/types.ClockTimeObject.html +1 -1
  174. package/docs/types/types.DbConnectionType.html +1 -1
  175. package/docs/types/types.DbTypes.html +1 -1
  176. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  177. package/docs/types/types.DreamAttributes.html +1 -1
  178. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  179. package/docs/types/types.DreamClassColumn.html +1 -1
  180. package/docs/types/types.DreamColumn.html +1 -1
  181. package/docs/types/types.DreamColumnNames.html +1 -1
  182. package/docs/types/types.DreamLogLevel.html +1 -1
  183. package/docs/types/types.DreamLogger.html +2 -2
  184. package/docs/types/types.DreamModelSerializerType.html +1 -1
  185. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  186. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  187. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  188. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  189. package/docs/types/types.DreamSerializable.html +1 -1
  190. package/docs/types/types.DreamSerializableArray.html +1 -1
  191. package/docs/types/types.DreamSerializerKey.html +1 -1
  192. package/docs/types/types.DreamSerializers.html +1 -1
  193. package/docs/types/types.DreamVirtualColumns.html +1 -1
  194. package/docs/types/types.DurationUnit.html +1 -1
  195. package/docs/types/types.EncryptAlgorithm.html +1 -1
  196. package/docs/types/types.HasManyStatement.html +1 -1
  197. package/docs/types/types.HasOneStatement.html +1 -1
  198. package/docs/types/types.Hyphenized.html +1 -1
  199. package/docs/types/types.IntrospectedValueType.html +1 -1
  200. package/docs/types/types.Pascalized.html +1 -1
  201. package/docs/types/types.PrimaryKeyType.html +1 -1
  202. package/docs/types/types.RoundingPrecision.html +1 -1
  203. package/docs/types/types.SerializerCasing.html +1 -1
  204. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  205. package/docs/types/types.Snakeified.html +1 -1
  206. package/docs/types/types.StrictInterface.html +1 -1
  207. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  208. package/docs/types/types.UpdateableProperties.html +1 -1
  209. package/docs/types/types.ValidationType.html +1 -1
  210. package/docs/types/types.ViewModel.html +2 -2
  211. package/docs/types/types.ViewModelClass.html +1 -1
  212. package/docs/types/types.WeekdayName.html +1 -1
  213. package/docs/types/types.WhereStatementForDream.html +1 -1
  214. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  215. package/docs/variables/index.DreamConst.html +1 -1
  216. package/docs/variables/index.ops.html +1 -1
  217. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  218. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  219. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  220. package/docs/variables/system.primaryKeyTypes.html +1 -1
  221. package/package.json +9 -5
@@ -146,28 +146,29 @@ export default class ObjectSerializerBuilder {
146
146
  * - `openapi` - (required) OpenAPI schema definition for the computed value
147
147
  * - `flatten` - When `true`, spreads the returned object's properties directly into the
148
148
  * parent serialized output instead of nesting them under `name`. The `openapi` option
149
- * then describes the whole returned object, not each property, because Psychic adds it
150
- * to the parent's schema as an `allOf` branch: either an object schema with full
151
- * property bodies (`type: 'object'`, `properties`, and `required` listing the keys that
152
- * are always present), or `{ $serializer: SomeSerializer }` when the callback returns
153
- * what that serializer renders. A serializer ref with `many: true` does not type-check
154
- * here, since a list has no properties to spread. A `null` return, a serializer built over
155
- * `null` or `undefined` data, or an `undefined` return without `required: false` renders as
156
- * `null` each key the `openapi` requires: a key an object shape lists in its `required`
157
- * array, a key a `$serializer` or `$serializable` ref's serializer declares without
158
- * `required: false`, and each such key of an `allOf`, `anyOf` or `oneOf` member. Its
159
- * optional keys stay absent, and an `undefined` return with `required: false` adds no keys.
160
- * A returned plain object also renders as `null` each key its shape requires that it
161
- * leaves out, and its optional keys it leaves out stay absent. When the `openapi` is an
162
- * `anyOf` or `oneOf`, or a `$serializable` ref to an STI base, a present value also renders
163
- * as `null` each key another variant requires that the rendered one does not declare, so a
164
- * client sees the same keys whichever variant is rendered.
149
+ * then describes the whole returned object, not each property: either an object schema
150
+ * with full property bodies (`type: 'object'`, `properties`, and `required` listing the
151
+ * keys that are always present), or `{ $serializer: SomeSerializer }` when the callback
152
+ * returns what that serializer renders. A serializer ref with `many: true` does not
153
+ * type-check here, since a list has no properties to spread. Whatever the callback
154
+ * returns, each key any shape of the `openapi` requires renders, as `null` wherever the
155
+ * value does not supply it (a returned plain object's key set to `undefined` does not
156
+ * supply it): a key an object shape lists in its `required` array, a key a `$serializer` or
157
+ * `$serializable` ref's serializer declares without `required: false` (for an STI base,
158
+ * any child's serializer), and each such key of any `allOf`, `anyOf` or `oneOf` member. So
159
+ * a `null` return, a serializer built over `null` or `undefined` data, or an `undefined`
160
+ * return without `required: false` renders each of them as `null`, and a present value
161
+ * renders its own keys over them, so a key another shape requires renders as `null` even
162
+ * when the value's own shape leaves it optional; the payload always includes the same
163
+ * keys whichever shape the value takes and whether it is null. An optional key no shape
164
+ * requires stays absent unless the value supplies it, and an `undefined` return with
165
+ * `required: false` adds no keys.
165
166
  * Every key it adds follows the render's casing like the parent's own keys: a returned
166
167
  * plain object's top-level keys and the `openapi`'s `properties` keys are cased (so
167
168
  * `casing: 'snake'` renders `phoneNumber` as `phone_number`, present or `null`), while
168
- * nested values render as returned. Attributes render in declaration order, so a key the returned object shares with
169
- * another attribute takes the value of whichever is declared later; under the `allOf`,
170
- * that value must match both schemas
169
+ * nested values render as returned. Attributes render in declaration order, so a key the
170
+ * returned object shares with another attribute takes the value of whichever is declared
171
+ * later, and that value must match both the attribute's declaration and the `openapi`'s
171
172
  * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
172
173
  * when omitted, attributes are required by default
173
174
  * @returns The serializer builder for method chaining
@@ -217,23 +218,27 @@ export default class ObjectSerializerBuilder {
217
218
  * declaration order, so when the parent and the association share a key (e.g., `id`),
218
219
  * whichever is declared later wins: the association's value replaces an attribute declared
219
220
  * before the `rendersOne`, and an attribute declared after it replaces the association's.
220
- * When the association is `null`, each key its serializer requires renders as `null`: an
221
- * attribute, delegatedAttribute or customAttribute declared without `required: false`, and
222
- * every non-flattened `rendersOne` and `rendersMany` (for a polymorphic `BelongsTo`, each
223
- * key the serializer of any of its target classes requires, and for an association to an
224
- * STI base, each key the serializer of any of its STI children requires), in the same order
225
- * (so an `id` declared before the `rendersOne` becomes `null`), while a key declared with
226
- * `required: false` stays absent. A flattened `customAttribute` in that serializer adds the
221
+ * Each key its serializer requires always renders, as `null` wherever the association
222
+ * does not supply it: an attribute, delegatedAttribute or customAttribute declared without
223
+ * `required: false`, and every non-flattened `rendersOne` and `rendersMany` (for a
224
+ * polymorphic `BelongsTo`, each key the serializer of any of its target classes requires,
225
+ * and for an association to an STI base, each key the serializer of any of its STI children
226
+ * requires), in the same order (so an `id` declared before the `rendersOne` becomes `null`
227
+ * when the association is `null`). A flattened `customAttribute` in that serializer adds the
227
228
  * keys its `openapi` requires: a key an object shape lists in its `required` array, a key a
228
229
  * `$serializer` or `$serializable` ref's serializer declares without `required: false`, and
229
230
  * each such key of an `allOf`, `anyOf` or `oneOf` member; a ref with `many: true` adds none,
230
- * and a flattened `customAttribute` declared with `required: false` adds none. Nothing in
231
- * that serializer is rendered, so its custom attribute callbacks do not run. When the
232
- * association to an STI base or polymorphic `BelongsTo` is present, each key another STI
233
- * child's or target's serializer requires that the held one's serializer does not declare
234
- * also renders as `null`, so a client sees the same keys whichever is held. Psychic adds
235
- * the association's OpenAPI schema to the parent's as an `allOf` branch, so a shared key's
236
- * value must match both schemas, whichever declaration wins
231
+ * and a flattened `customAttribute` declared with `required: false` adds none. When the
232
+ * association is `null`, each of these keys renders as `null` and nothing in its serializer
233
+ * is rendered, so its custom attribute callbacks do not run. When it is present, the held
234
+ * object's serializer renders its own keys over them, so a key another STI child's or
235
+ * target's serializer requires renders as `null` where the held one's serializer does not
236
+ * render it, including a key that serializer declares with `required: false`; the payload
237
+ * always includes the same keys whichever is held and whether the association is `null`. A
238
+ * key declared with `required: false` that no serializer requires stays absent unless it
239
+ * renders. The association's OpenAPI schema describes the parent's payload too, so a shared
240
+ * key's value must match both the parent's declaration and the association's, whichever
241
+ * declaration wins
237
242
  * - `optional` - When `true`, allows the association to be null/missing without causing
238
243
  * an `OpenapiResponseValidationFailure` during Psychic controller unit specs. By default,
239
244
  * `rendersOne` expects the association to be present (mirroring the `optional` option on
@@ -1,17 +1,20 @@
1
1
  import snakeify from '../../helpers/snakeify.js';
2
2
  import DreamSerializerBuilder from '../builders/DreamSerializerBuilder.js';
3
- import ObjectSerializerBuilder from '../builders/ObjectSerializerBuilder.js';
4
3
  import { inferSerializersFromDreamClassOrViewModelClass } from './inferSerializerFromDreamOrViewModel.js';
5
4
  import { serializersForAssociatedClass } from './serializersForAssociatedClass.js';
6
5
  /**
7
- * The keys a flattened `rendersOne` adds to the parent's payload when its associated object is
8
- * null, returned as an object with each key set to `null`, ready to spread into the parent's
9
- * payload (`Object.keys` of it for the key names alone): each key one of `serializers` requires.
10
- * As a property read on an empty object is `undefined`, a null value carries no key it may omit:
11
- * an attribute, delegatedAttribute or customAttribute declared with `required: false` adds no key.
12
- * Every key is cased per `casing`, as the renderer cases it. There are several serializers when
13
- * the association is a polymorphic BelongsTo, one for each target class, or when the associated
14
- * class is an STI base, one for each child, and their required keys are unioned.
6
+ * The keys a flattened `rendersOne` always adds to the parent's payload, returned as an object
7
+ * with each key set to `null`, ready to spread into the parent's payload (`Object.keys` of it for
8
+ * the key names alone): each key one of `serializers` requires. When the associated object is
9
+ * null, these are the keys it renders; when it is present, the rendered serializer's own keys
10
+ * render over them, so each of these it leaves out stays `null` and the payload always includes
11
+ * these keys, whichever serializer renders and whether the object is null. As a property read on
12
+ * an empty object is `undefined`, a null value carries no key it may omit: an attribute,
13
+ * delegatedAttribute or customAttribute declared with `required: false` adds no key unless another
14
+ * of `serializers` requires it. Every key is cased per `casing`, as the renderer cases it. There
15
+ * are several serializers when the association is a polymorphic BelongsTo, one for each target
16
+ * class, or when the associated class is an STI base, one for each child, and their required keys
17
+ * are unioned.
15
18
  *
16
19
  * Each serializer is built with `undefined` data to read its declarations, as Psychic's OpenAPI
17
20
  * generation and `preloadFor` build it, and nothing is rendered: no attribute callback runs and
@@ -34,85 +37,58 @@ export function nullFlattenedSerializerKeys(serializers, options = {}) {
34
37
  return nullRequiredKeys(walkSerializers(serializers, newWalk(options)));
35
38
  }
36
39
  /**
37
- * The keys a flattened `customAttribute` with this `openapi` adds to the parent's payload when
40
+ * The keys a flattened `customAttribute` with this `openapi` always adds to the parent's payload,
41
+ * each set to `null`: each key any shape of `openapi` requires, walked as
42
+ * `nullFlattenedSerializerKeys` walks a flattened `customAttribute` and cased per `casing`. When
38
43
  * its callback returns null (or `undefined` without `required: false`), or a serializer built over
39
- * null data, each set to `null`: each key `openapi` requires, walked as `nullFlattenedSerializerKeys`
40
- * walks a flattened `customAttribute` and cased per `casing`.
44
+ * null data, these are the keys it renders; when it returns any other value, that value's own keys
45
+ * render over them, so each of these the value leaves out, or a returned plain object sets to
46
+ * `undefined`, stays `null`. With `required: false`, a callback that returns `undefined` adds none
47
+ * of them.
41
48
  */
42
49
  export function nullFlattenedOpenapiKeys(openapi, options = {}) {
43
50
  return nullRequiredKeys(walkOpenapi(openapi, newWalk(options)));
44
51
  }
45
52
  /**
46
- * The keys a present flattened `rendersOne` adds to the parent's payload, each set to `null`,
47
- * beside what `renderedSerializer` renders: each key one of `serializers` (every serializer the
48
- * association may render with) requires that `renderedSerializer` does not declare at all. A key
49
- * `renderedSerializer` declares is its own to render or, when `required: false`, to omit.
53
+ * The keys only the other variants of a flattened `rendersOne` contribute to its payload, each set
54
+ * to `null`: each key one of `serializers` (every serializer the association may render with)
55
+ * requires that `renderedSerializer` does not declare at all. A key `renderedSerializer` declares
56
+ * is left out even when it declares it with `required: false` and another serializer requires it;
57
+ * the renderer fills that key with `null` too, since it renders every key
58
+ * `nullFlattenedSerializerKeys` returns beneath the rendered serializer's own keys.
50
59
  */
51
60
  export function nullFlattenedSerializerKeysOfOtherVariants(serializers, renderedSerializer, options = {}) {
52
61
  return nullKeysRequiredByOthers(walkSerializers(serializers, newWalk(options)), Object.keys(walkSerializers([renderedSerializer], newWalk(options))));
53
62
  }
54
63
  /**
55
- * The keys a flattened `customAttribute` with this `openapi` adds to the parent's payload, each
56
- * set to `null`, when it renders `renderedVariant`: each key a variant of `openapi` requires that
64
+ * The keys only the other variants of a flattened `customAttribute` with this `openapi` contribute
65
+ * to its payload, each set to `null`: each key a variant of `openapi` requires that
57
66
  * `renderedVariant` does not declare. `renderedVariant` is any `openapi` shorthand: one member of
58
67
  * an `anyOf` or `oneOf`, or `{ $serializer: childSerializer }` for one child of a `$serializable`
59
68
  * ref to an STI base. A key requires its value when an object shape's `required` array lists it,
60
69
  * whether the shape's own `properties` declare it or a member of its `allOf`, `anyOf` or `oneOf`
61
- * does, or when a serializer declares it without `required: false`.
70
+ * does, or when a serializer declares it without `required: false`. A key `renderedVariant`
71
+ * declares is left out even when it is optional there and another variant requires it; the
72
+ * renderer fills that key with `null` too, since it renders every key `nullFlattenedOpenapiKeys`
73
+ * returns beneath the value's own keys.
62
74
  */
63
75
  export function nullFlattenedOpenapiKeysOfOtherVariants(openapi, renderedVariant, options = {}) {
64
76
  return nullKeysRequiredByOthers(walkOpenapi(openapi, newWalk(options)), Object.keys(walkOpenapi(renderedVariant, newWalk(options))));
65
77
  }
66
78
  /**
67
- * The keys a flattened `customAttribute` with this `openapi` adds to the parent's payload, each
68
- * set to `null`, beside the non-null `value` its callback returned, from which the rendered
69
- * variant is read. A serializer's declarations are the rendered variant, so a `$serializable` ref
70
- * to an STI base adds the keys of every other child, as `nullFlattenedOpenapiKeysOfOtherVariants`
71
- * reads them; the serializer renders each of its own keys. A plain object renders only the keys
72
- * it has, so it adds each key the rendered variant requires that the object leaves out as well as
73
- * each key another variant requires that the rendered one does not declare. An `openapi` with one
74
- * variant is the rendered one. Of several variants (the members of an `anyOf` or `oneOf`, or one
75
- * `$serializer` per child of a `$serializable` ref to an STI base, a member with several variants
76
- * of its own contributing each of them, and an `allOf` beside an `anyOf` or `oneOf` joining each
77
- * of its variants), the rendered one is the variant that declares every key the object has; when
78
- * several do, the first of them whose required keys the object all has, and when none is found,
79
- * the object adds each key any variant requires. The object's top-level keys render cased per
80
- * `casing`, so they are compared cased, and a key it has is never added.
79
+ * Lists the single shapes a value of a flattened `customAttribute` with this `openapi` may take,
80
+ * which a consumer can union into one description of the flattened payload: a `$serializer` ref to
81
+ * each serializer a `$serializable` ref resolves to (one per child for an STI base), or the members
82
+ * of an `anyOf` or `oneOf` (an `anyOf`'s members listed before a sibling `oneOf`'s, not paired with
83
+ * them), each member that itself has several shapes (a nested `anyOf` or `oneOf`, or a
84
+ * `$serializable` ref to an STI base) contributing those shapes in its place. An `allOf` beside
85
+ * the `anyOf` or `oneOf` describes every value, so each shape is that `allOf` with the shape added
86
+ * to its members. Anything else (a single shape, a ref with `many: true`, or a primitive) has no
87
+ * variants and returns an empty list. Each shape may be passed back as the `renderedVariant` of
88
+ * `nullFlattenedOpenapiKeysOfOtherVariants`. The renderer does not read a value's shape from this
89
+ * list: whatever shape the value takes, it renders every key `nullFlattenedOpenapiKeys` returns.
81
90
  */
82
- export function nullFlattenedOpenapiKeysForValue(openapi, value, options = {}) {
83
- if (value instanceof DreamSerializerBuilder || value instanceof ObjectSerializerBuilder)
84
- return nullKeysRequiredByOthers(walkOpenapi(openapi, newWalk(options)), Object.keys(walkSerializerBuilder(value, newWalk(options))));
85
- if (typeof value !== 'object' || value === null)
86
- return {};
87
- const { casing } = newWalk(options);
88
- const valueKeys = Object.keys(value).map(key => setCase(key, casing));
89
- const variants = openapiVariants(openapi);
90
- const candidates = variants
91
- .map(variant => ({ variant, requirements: walkOpenapi(variant, newWalk(options)) }))
92
- .filter(({ requirements }) => valueKeys.every(key => Object.prototype.hasOwnProperty.call(requirements, key)));
93
- const renderedVariant = variants.length < 2
94
- ? openapi
95
- : candidates.length === 1
96
- ? candidates[0].variant
97
- : candidates.find(({ requirements }) => Object.keys(requirements).every(key => !requirements[key] || valueKeys.includes(key)))?.variant;
98
- const requiredKeys = renderedVariant
99
- ? {
100
- ...nullFlattenedOpenapiKeysOfOtherVariants(openapi, renderedVariant, options),
101
- ...nullFlattenedOpenapiKeys(renderedVariant, options),
102
- }
103
- : nullFlattenedOpenapiKeys(openapi, options);
104
- valueKeys.forEach(key => delete requiredKeys[key]);
105
- return requiredKeys;
106
- }
107
- /**
108
- * The shapes a value of `openapi` may take, each one a variant a plain object may render: a
109
- * `$serializer` ref to each serializer a `$serializable` ref resolves to, or the members of an
110
- * `anyOf` or `oneOf`, each member that itself has several variants (a nested `anyOf` or `oneOf`,
111
- * or a `$serializable` ref to an STI base) contributing those variants in its place. An `allOf`
112
- * beside the `anyOf` or `oneOf` describes every value, so each variant is that `allOf` with the
113
- * variant added to its members.
114
- */
115
- function openapiVariants(openapi) {
91
+ export function flattenedOpenapiVariants(openapi) {
116
92
  if (typeof openapi !== 'object' || Array.isArray(openapi))
117
93
  return [];
118
94
  if ('$serializable' in openapi)
@@ -123,7 +99,7 @@ function openapiVariants(openapi) {
123
99
  ...(('anyOf' in openapi && openapi.anyOf) || []),
124
100
  ...(('oneOf' in openapi && openapi.oneOf) || []),
125
101
  ].flatMap(member => {
126
- const memberVariants = openapiVariants(member);
102
+ const memberVariants = flattenedOpenapiVariants(member);
127
103
  return memberVariants.length > 1 ? memberVariants : [member];
128
104
  });
129
105
  const sharedMembers = ('allOf' in openapi && openapi.allOf) || [];
@@ -1,9 +1,10 @@
1
1
  import uniq from '../../helpers/uniq.js';
2
2
  import { inferSerializersFromDreamClassOrViewModelClass } from './inferSerializerFromDreamOrViewModel.js';
3
3
  /**
4
- * Only used when flatten: true, and the associated object is null, in which case there is no
5
- * object to resolve a serializer from, but the renderer still needs the serializers whose keys it
6
- * renders as null in the parent's payload.
4
+ * Used by every flattened rendersOne render, whether the associated object is present or null: it
5
+ * lists every serializer the association may render with, whose required keys the renderer renders
6
+ * as null in the parent's payload, beneath the present object's own keys or alone when the object
7
+ * is null, where there is no object to resolve a serializer from.
7
8
  *
8
9
  * Resolves, in order, to the `serializer` option, the serializers of the `dreamClass` or
9
10
  * `viewModelClass` option, or the serializers of each class `parentClass` declares the association
@@ -14,7 +14,7 @@ import standardizeFullyQualifiedModelName from '../helpers/standardizeFullyQuali
14
14
  import expandStiClasses from '../helpers/sti/expandStiClasses.js';
15
15
  import inferSerializerFromDreamOrViewModel, { inferSerializersFromDreamClassOrViewModelClass } from '../serializer/helpers/inferSerializerFromDreamOrViewModel.js';
16
16
  import isDreamSerializer from '../serializer/helpers/isDreamSerializer.js';
17
- import { nullFlattenedOpenapiKeys, nullFlattenedOpenapiKeysOfOtherVariants, nullFlattenedSerializerKeys, nullFlattenedSerializerKeysOfOtherVariants } from '../serializer/helpers/nullFlattenedSerializerKeys.js';
17
+ import { flattenedOpenapiVariants, nullFlattenedOpenapiKeys, nullFlattenedOpenapiKeysOfOtherVariants, nullFlattenedSerializerKeys, nullFlattenedSerializerKeysOfOtherVariants } from '../serializer/helpers/nullFlattenedSerializerKeys.js';
18
18
  import serializerNameFromFullyQualifiedModelName from '../serializer/helpers/serializerNameFromFullyQualifiedModelName.js';
19
19
  import { DbConnectionType, LegacyCompatiblePrimaryKeyType } from '../types/db.js';
20
20
  import { DreamModelSerializerType, SimpleObjectSerializerType } from '../types/serializer.js';
@@ -72,6 +72,7 @@ export default class DreamApp {
72
72
  nullFlattenedSerializerKeysOfOtherVariants: typeof nullFlattenedSerializerKeysOfOtherVariants;
73
73
  nullFlattenedOpenapiKeys: typeof nullFlattenedOpenapiKeys;
74
74
  nullFlattenedOpenapiKeysOfOtherVariants: typeof nullFlattenedOpenapiKeysOfOtherVariants;
75
+ flattenedOpenapiVariants: typeof flattenedOpenapiVariants;
75
76
  serializerNameFromFullyQualifiedModelName: typeof serializerNameFromFullyQualifiedModelName;
76
77
  autogeneratedFileDisclaimer: typeof autogeneratedFileDisclaimer;
77
78
  };
@@ -159,6 +160,26 @@ export default class DreamApp {
159
160
  get packageManager(): "pnpm" | "yarn" | "npm" | "bun" | "deno";
160
161
  private _importExtension;
161
162
  get importExtension(): ".js" | ".ts" | "none";
163
+ private _importAliasPrefix;
164
+ /**
165
+ * The prefix of the import aliases the CLI generators write: the `@` in
166
+ * `@models/User.js`, `@serializers/UserSerializer.js` and
167
+ * `@spec/factories/UserFactory.js`. Set with
168
+ * `dreamApp.set('importAliasPrefix', '#')` to have them write
169
+ * `#models/User.js` and so on, which Node resolves as subpath imports
170
+ * declared under `imports` in the app's package.json.
171
+ *
172
+ * Defaults to `@`. The default becomes `#` in Dream 3; an app that wants
173
+ * to keep `@` should set it explicitly with
174
+ * `dreamApp.set('importAliasPrefix', '@')`.
175
+ *
176
+ * Psychic's resource controller-spec generator still writes its
177
+ * `@spec/unit/helpers/authentication.js` import with a literal `@` until
178
+ * Psychic reads this setting (planned for 3.17.0), so a Psychic app should
179
+ * keep its `@spec` alias, or wait for that release, before switching to
180
+ * `#` only.
181
+ */
182
+ get importAliasPrefix(): "@" | "#";
162
183
  /**
163
184
  * if set to true, it will bypass deprecation checks that run
164
185
  * during the sync hook. Defaults to false, we only recommend
@@ -242,7 +263,7 @@ export default class DreamApp {
242
263
  private requireTestDatabaseClaimSupport;
243
264
  load<RT extends 'models' | 'serializers'>(resourceType: RT, resourcePath: string, importCb: (path: string) => Promise<any>): Promise<void>;
244
265
  plugin(cb: (app: DreamApp) => void | Promise<void>): void;
245
- set<ApplyOpt extends DreamAppSetOption>(applyOption: ApplyOpt, options: ApplyOpt extends 'bypassDeprecationChecks' ? boolean : ApplyOpt extends 'db' ? DreamDbCredentialOptions | string : ApplyOpt extends 'encryption' ? DreamAppEncryptionOptions : ApplyOpt extends 'primaryKeyType' ? LegacyCompatiblePrimaryKeyType : ApplyOpt extends 'importExtension' ? GeneratorImportStyle : ApplyOpt extends 'logger' ? DreamLogger : ApplyOpt extends 'projectRoot' ? string : ApplyOpt extends 'inflections' ? () => void | Promise<void> : ApplyOpt extends 'packageManager' ? DreamAppAllowedPackageManagersEnum : ApplyOpt extends 'paths' ? DreamDirectoryPaths : ApplyOpt extends 'parallelTests' ? number : ApplyOpt extends 'unicodeNormalization' ? UnicodeNormalizationForm : ApplyOpt extends 'paginationPageSize' ? number : ApplyOpt extends 'paginationMaxPageSize' ? number : ApplyOpt extends 'sortableScopeLockTimeout' ? number : never, secondaryOptions?: ApplyOpt extends 'db' ? DreamDbCredentialOptions : never): void;
266
+ set<ApplyOpt extends DreamAppSetOption>(applyOption: ApplyOpt, options: ApplyOpt extends 'bypassDeprecationChecks' ? boolean : ApplyOpt extends 'db' ? DreamDbCredentialOptions | string : ApplyOpt extends 'encryption' ? DreamAppEncryptionOptions : ApplyOpt extends 'primaryKeyType' ? LegacyCompatiblePrimaryKeyType : ApplyOpt extends 'importExtension' ? GeneratorImportStyle : ApplyOpt extends 'importAliasPrefix' ? GeneratorImportAliasPrefix : ApplyOpt extends 'logger' ? DreamLogger : ApplyOpt extends 'projectRoot' ? string : ApplyOpt extends 'inflections' ? () => void | Promise<void> : ApplyOpt extends 'packageManager' ? DreamAppAllowedPackageManagersEnum : ApplyOpt extends 'paths' ? DreamDirectoryPaths : ApplyOpt extends 'parallelTests' ? number : ApplyOpt extends 'unicodeNormalization' ? UnicodeNormalizationForm : ApplyOpt extends 'paginationPageSize' ? number : ApplyOpt extends 'paginationMaxPageSize' ? number : ApplyOpt extends 'sortableScopeLockTimeout' ? number : never, secondaryOptions?: ApplyOpt extends 'db' ? DreamDbCredentialOptions : never): void;
246
267
  /**
247
268
  * Registers a callback for an app-level event. Callbacks for the same
248
269
  * event run in the order they were registered.
@@ -289,7 +310,7 @@ export interface DreamAppOpts {
289
310
  serializerCasing?: DreamSerializerCasing;
290
311
  parallelTests: number | undefined;
291
312
  }
292
- export type DreamAppSetOption = 'bypassDeprecationChecks' | 'db' | 'encryption' | 'inflections' | 'importExtension' | 'logger' | 'paths' | 'primaryKeyType' | 'projectRoot' | 'serializerCasing' | 'parallelTests' | 'unicodeNormalization' | 'paginationPageSize' | 'paginationMaxPageSize' | 'sortableScopeLockTimeout' | 'packageManager';
313
+ export type DreamAppSetOption = 'bypassDeprecationChecks' | 'db' | 'encryption' | 'inflections' | 'importExtension' | 'importAliasPrefix' | 'logger' | 'paths' | 'primaryKeyType' | 'projectRoot' | 'serializerCasing' | 'parallelTests' | 'unicodeNormalization' | 'paginationPageSize' | 'paginationMaxPageSize' | 'sortableScopeLockTimeout' | 'packageManager';
293
314
  export interface DreamDirectoryPaths {
294
315
  models?: string;
295
316
  serializers?: string;
@@ -447,4 +468,6 @@ export declare const DreamAppAllowedPackageManagersEnumValues: readonly ["pnpm",
447
468
  export type DreamAppAllowedPackageManagersEnum = (typeof DreamAppAllowedPackageManagersEnumValues)[number];
448
469
  export declare const GeneratorImportStyles: readonly [".js", ".ts", "none"];
449
470
  export type GeneratorImportStyle = (typeof GeneratorImportStyles)[number];
471
+ export declare const GeneratorImportAliasPrefixes: readonly ["@", "#"];
472
+ export type GeneratorImportAliasPrefix = (typeof GeneratorImportAliasPrefixes)[number];
450
473
  export {};
@@ -11,11 +11,11 @@ import DreamSerializerBuilder from './builders/DreamSerializerBuilder.js';
11
11
  *
12
12
  * A serializer must not read its data while being built: what it declares may not
13
13
  * depend on its data. Dream and Psychic build it without data to read its shape (the
14
- * OpenAPI schema, and the null and other-variant keys of a flattened association or
15
- * `customAttribute` that uses it), so a serializer that reads its data while being
16
- * built throws when a flattened association or `customAttribute` that uses it is
17
- * rendered. Read the data inside a `customAttribute` callback, which runs only when
18
- * the serializer renders, not while it is built.
14
+ * OpenAPI schema, and the keys a flattened association or `customAttribute` that uses
15
+ * it renders as `null` where its value does not supply them), so a serializer that
16
+ * reads its data while being built throws when a flattened association or
17
+ * `customAttribute` that uses it is rendered. Read the data inside a `customAttribute`
18
+ * callback, which runs only when the serializer renders, not while it is built.
19
19
  *
20
20
  * @param dreamClass - The Dream model class the serializer renders
21
21
  * @param data - The Dream instance to render
@@ -10,11 +10,11 @@ import ObjectSerializerBuilder from './builders/ObjectSerializerBuilder.js';
10
10
  *
11
11
  * A serializer must not read its data while being built: what it declares may not
12
12
  * depend on its data. Dream and Psychic build it without data to read its shape (the
13
- * OpenAPI schema, and the null and other-variant keys of a flattened association or
14
- * `customAttribute` that uses it), so a serializer that reads its data while being
15
- * built throws when a flattened association or `customAttribute` that uses it is
16
- * rendered. Read the data inside a `customAttribute` callback, which runs only when
17
- * the serializer renders, not while it is built.
13
+ * OpenAPI schema, and the keys a flattened association or `customAttribute` that uses
14
+ * it renders as `null` where its value does not supply them), so a serializer that
15
+ * reads its data while being built throws when a flattened association or
16
+ * `customAttribute` that uses it is rendered. Read the data inside a `customAttribute`
17
+ * callback, which runs only when the serializer renders, not while it is built.
18
18
  *
19
19
  * @param data - The object to render
20
20
  * @param passthroughData - Data available to this serializer and passed down to the serializers
@@ -10,6 +10,6 @@ export default class SerializerRenderer {
10
10
  private renderOpts;
11
11
  constructor(serializerBuilder: DreamSerializerBuilder<any, any> | ObjectSerializerBuilder<any, any> | null | undefined, passthroughData?: object, { casing }?: SerializerRendererOpts);
12
12
  render(): Record<string, any> | null;
13
- private setCaseOfKeys;
13
+ private setCaseOfDefinedKeys;
14
14
  private setCase;
15
15
  }
@@ -141,28 +141,29 @@ export default class DreamSerializerBuilder<DataTypeForOpenapi extends typeof Dr
141
141
  * - `openapi` - (required) OpenAPI schema definition for the computed value
142
142
  * - `flatten` - When `true`, spreads the returned object's properties directly into the
143
143
  * parent serialized output instead of nesting them under `name`. The `openapi` option
144
- * then describes the whole returned object, not each property, because Psychic adds it
145
- * to the parent's schema as an `allOf` branch: either an object schema with full
146
- * property bodies (`type: 'object'`, `properties`, and `required` listing the keys that
147
- * are always present), or `{ $serializer: SomeSerializer }` when the callback returns
148
- * what that serializer renders. A serializer ref with `many: true` does not type-check
149
- * here, since a list has no properties to spread. A `null` return, a serializer built over
150
- * `null` or `undefined` data, or an `undefined` return without `required: false` renders as
151
- * `null` each key the `openapi` requires: a key an object shape lists in its `required`
152
- * array, a key a `$serializer` or `$serializable` ref's serializer declares without
153
- * `required: false`, and each such key of an `allOf`, `anyOf` or `oneOf` member. Its
154
- * optional keys stay absent, and an `undefined` return with `required: false` adds no keys.
155
- * A returned plain object also renders as `null` each key its shape requires that it
156
- * leaves out, and its optional keys it leaves out stay absent. When the `openapi` is an
157
- * `anyOf` or `oneOf`, or a `$serializable` ref to an STI base, a present value also renders
158
- * as `null` each key another variant requires that the rendered one does not declare, so a
159
- * client sees the same keys whichever variant is rendered.
144
+ * then describes the whole returned object, not each property: either an object schema
145
+ * with full property bodies (`type: 'object'`, `properties`, and `required` listing the
146
+ * keys that are always present), or `{ $serializer: SomeSerializer }` when the callback
147
+ * returns what that serializer renders. A serializer ref with `many: true` does not
148
+ * type-check here, since a list has no properties to spread. Whatever the callback
149
+ * returns, each key any shape of the `openapi` requires renders, as `null` wherever the
150
+ * value does not supply it (a returned plain object's key set to `undefined` does not
151
+ * supply it): a key an object shape lists in its `required` array, a key a `$serializer` or
152
+ * `$serializable` ref's serializer declares without `required: false` (for an STI base,
153
+ * any child's serializer), and each such key of any `allOf`, `anyOf` or `oneOf` member. So
154
+ * a `null` return, a serializer built over `null` or `undefined` data, or an `undefined`
155
+ * return without `required: false` renders each of them as `null`, and a present value
156
+ * renders its own keys over them, so a key another shape requires renders as `null` even
157
+ * when the value's own shape leaves it optional; the payload always includes the same
158
+ * keys whichever shape the value takes and whether it is null. An optional key no shape
159
+ * requires stays absent unless the value supplies it, and an `undefined` return with
160
+ * `required: false` adds no keys.
160
161
  * Every key it adds follows the render's casing like the parent's own keys: a returned
161
162
  * plain object's top-level keys and the `openapi`'s `properties` keys are cased (so
162
163
  * `casing: 'snake'` renders `phoneNumber` as `phone_number`, present or `null`), while
163
- * nested values render as returned. Attributes render in declaration order, so a key the returned object shares with
164
- * another attribute takes the value of whichever is declared later; under the `allOf`,
165
- * that value must match both schemas
164
+ * nested values render as returned. Attributes render in declaration order, so a key the
165
+ * returned object shares with another attribute takes the value of whichever is declared
166
+ * later, and that value must match both the attribute's declaration and the `openapi`'s
166
167
  * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
167
168
  * when omitted, attributes are required by default
168
169
  * @returns The serializer builder for method chaining
@@ -204,23 +205,29 @@ export default class DreamSerializerBuilder<DataTypeForOpenapi extends typeof Dr
204
205
  * declaration order, so when the parent and the association share a key (e.g., `id`),
205
206
  * whichever is declared later wins: the association's value replaces an attribute declared
206
207
  * before the `rendersOne`, and an attribute declared after it replaces the association's.
207
- * When the association is `null`, each key its serializer requires renders as `null`: an
208
- * attribute, delegatedAttribute or customAttribute declared without `required: false`, and
209
- * every non-flattened `rendersOne` and `rendersMany` (for a polymorphic `BelongsTo`, each
210
- * key the serializer of any of its target classes requires, and for an association to an
211
- * STI base, each key the serializer of any of its STI children requires), in the same order
212
- * (so an `id` declared before the `rendersOne` becomes `null`), while a key declared with
213
- * `required: false` stays absent. A flattened `customAttribute` in that serializer adds the
208
+ * Each key its serializer requires always renders, as `null` wherever the association
209
+ * does not supply it: an attribute, delegatedAttribute or customAttribute declared without
210
+ * `required: false`, and every non-flattened `rendersOne` and `rendersMany` (for a
211
+ * polymorphic `BelongsTo`, each key the serializer of any of its target classes requires,
212
+ * and for an association to an STI base, each key the serializer of any of its STI children
213
+ * requires), in the same order (so an `id` declared before the `rendersOne` becomes `null`
214
+ * when the association is `null`). A flattened `customAttribute` in that serializer adds the
214
215
  * keys its `openapi` requires: a key an object shape lists in its `required` array, a key a
215
216
  * `$serializer` or `$serializable` ref's serializer declares without `required: false`, and
216
217
  * each such key of an `allOf`, `anyOf` or `oneOf` member; a ref with `many: true` adds none,
217
- * and a flattened `customAttribute` declared with `required: false` adds none. Nothing in
218
- * that serializer is rendered, so its custom attribute callbacks do not run. When the
219
- * association to an STI base or polymorphic `BelongsTo` is present, each key another STI
220
- * child's or target's serializer requires that the held one's serializer does not declare
221
- * also renders as `null`, so a client sees the same keys whichever is held. Psychic adds
222
- * the association's OpenAPI schema to the parent's as an `allOf` branch, so a shared key's
223
- * value must match both schemas, whichever declaration wins
218
+ * and a flattened `customAttribute` declared with `required: false` adds none. When the
219
+ * association is `null`, each of these keys renders as `null` and nothing in its serializer
220
+ * is rendered, so its custom attribute callbacks do not run. When it is present, the held
221
+ * object's serializer renders its own keys over them, so a key another STI child's or
222
+ * target's serializer requires renders as `null` where the held one's serializer does not
223
+ * render it, including a key that serializer declares with `required: false`; the payload
224
+ * always includes the same keys whichever is held and, for an association or a property
225
+ * given a `serializer` or `viewModelClass` option, whether it is `null` (a `null` property
226
+ * that is not an association and has neither option renders none of these keys, since no
227
+ * serializer is known for it). A key declared with `required: false` that no serializer
228
+ * requires stays absent unless it renders. The association's OpenAPI schema describes the
229
+ * parent's payload too, so a shared key's value must match both the parent's declaration
230
+ * and the association's, whichever declaration wins
224
231
  * - `optional` - When `true`, allows the association to be null/missing without causing
225
232
  * an `OpenapiResponseValidationFailure` during Psychic controller unit specs. By default,
226
233
  * `rendersOne` expects the association to be present (mirroring the `optional` option on
@@ -276,8 +283,9 @@ export default class DreamSerializerBuilder<DataTypeForOpenapi extends typeof Dr
276
283
  * If `true`, the rendered association's attributes are merged directly into
277
284
  * the parent object instead of being nested under the association key. A key
278
285
  * the parent shares with the association takes the value of whichever is
279
- * declared later, and when the association is `null`, each key its serializer
280
- * requires renders as `null`. See the `flatten` option of `rendersOne`.
286
+ * declared later, and each key its serializer requires renders, as `null`
287
+ * wherever the association does not supply it. See the `flatten` option of
288
+ * `rendersOne`.
281
289
  */
282
290
  flatten?: boolean;
283
291
  /**