@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
package/README.md CHANGED
@@ -171,39 +171,7 @@ await user.save()
171
171
 
172
172
  ### Specs
173
173
 
174
- Running specs is very easy, not much to explain here.
175
-
176
- ```
177
- pnpm spec
178
- ```
179
-
180
- We are using jest under the hood, and have a plugin enabled called `jest-plugin-context`, which allows us to branch specs out using the `context` function in place of `describe`, like so:
181
-
182
- ```ts
183
- describe('Dream#pluck', () => {
184
- let user1: User
185
- let user2: User
186
- beforeEach(async () => {
187
- user1 = await User.create({ email: 'fred@frewd', password: 'howyadoin' })
188
- user2 = await User.create({ email: 'how@yadoin', password: 'howyadoin' })
189
- })
190
-
191
- it('plucks the specified attributes and returns them as raw data', async () => {
192
- const records = await User.pluck('id')
193
- expect(records).toEqual([user1.id, user2.id])
194
- })
195
-
196
- context('with multiple fields', () => {
197
- it('should return multi-dimensional array', async () => {
198
- const records = await User.order('id').pluck('id', 'createdAt')
199
- expect(records).toEqual([
200
- [user1.id, user1.created_at],
201
- [user2.id, user2.created_at],
202
- ])
203
- })
204
- })
205
- })
206
- ```
174
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setting up the test databases and running the specs.
207
175
 
208
176
  ### Similarity matching
209
177
 
@@ -278,6 +246,6 @@ const pets = await user
278
246
 
279
247
  - **Ask them on [Stack Overflow](https://stackoverflow.com)**, using the `[dream]` tag.
280
248
 
281
- Dream is an open source library, so we encourage you to actively contribute. Visit our [Contributing](https://github.com/rvohealth/dream/CONTRIBUTING.md) guide to learn more about the processes we use for submitting pull requests or issues.
249
+ Dream is an open source library, so we encourage you to actively contribute. Visit our [Contributing](CONTRIBUTING.md) guide to learn more about the processes we use for submitting pull requests or issues.
282
250
 
283
251
  Are you trying to report a possible security vulnerability? Visit our [Security Policy](https://github.com/rvohealth/dream/SECURITY.md) for guidelines about how to proceed.
@@ -19,7 +19,7 @@ import standardizeFullyQualifiedModelName from '../helpers/standardizeFullyQuali
19
19
  import expandStiClasses from '../helpers/sti/expandStiClasses.js';
20
20
  import inferSerializerFromDreamOrViewModel, { inferSerializersFromDreamClassOrViewModelClass, } from '../serializer/helpers/inferSerializerFromDreamOrViewModel.js';
21
21
  import isDreamSerializer from '../serializer/helpers/isDreamSerializer.js';
22
- import { nullFlattenedOpenapiKeys, nullFlattenedOpenapiKeysOfOtherVariants, nullFlattenedSerializerKeys, nullFlattenedSerializerKeysOfOtherVariants, } from '../serializer/helpers/nullFlattenedSerializerKeys.js';
22
+ import { flattenedOpenapiVariants, nullFlattenedOpenapiKeys, nullFlattenedOpenapiKeysOfOtherVariants, nullFlattenedSerializerKeys, nullFlattenedSerializerKeysOfOtherVariants, } from '../serializer/helpers/nullFlattenedSerializerKeys.js';
23
23
  import serializerNameFromFullyQualifiedModelName from '../serializer/helpers/serializerNameFromFullyQualifiedModelName.js';
24
24
  import BaseClockTime from '../utils/datetime/BaseClockTime.js';
25
25
  import CalendarDate from '../utils/datetime/CalendarDate.js';
@@ -108,6 +108,7 @@ export default class DreamApp {
108
108
  nullFlattenedSerializerKeysOfOtherVariants,
109
109
  nullFlattenedOpenapiKeys,
110
110
  nullFlattenedOpenapiKeysOfOtherVariants,
111
+ flattenedOpenapiVariants,
111
112
  serializerNameFromFullyQualifiedModelName,
112
113
  autogeneratedFileDisclaimer,
113
114
  };
@@ -272,6 +273,28 @@ export default class DreamApp {
272
273
  get importExtension() {
273
274
  return this._importExtension;
274
275
  }
276
+ _importAliasPrefix = '@';
277
+ /**
278
+ * The prefix of the import aliases the CLI generators write: the `@` in
279
+ * `@models/User.js`, `@serializers/UserSerializer.js` and
280
+ * `@spec/factories/UserFactory.js`. Set with
281
+ * `dreamApp.set('importAliasPrefix', '#')` to have them write
282
+ * `#models/User.js` and so on, which Node resolves as subpath imports
283
+ * declared under `imports` in the app's package.json.
284
+ *
285
+ * Defaults to `@`. The default becomes `#` in Dream 3; an app that wants
286
+ * to keep `@` should set it explicitly with
287
+ * `dreamApp.set('importAliasPrefix', '@')`.
288
+ *
289
+ * Psychic's resource controller-spec generator still writes its
290
+ * `@spec/unit/helpers/authentication.js` import with a literal `@` until
291
+ * Psychic reads this setting (planned for 3.17.0), so a Psychic app should
292
+ * keep its `@spec` alias, or wait for that release, before switching to
293
+ * `#` only.
294
+ */
295
+ get importAliasPrefix() {
296
+ return this._importAliasPrefix;
297
+ }
275
298
  /**
276
299
  * if set to true, it will bypass deprecation checks that run
277
300
  * during the sync hook. Defaults to false, we only recommend
@@ -482,6 +505,9 @@ export default class DreamApp {
482
505
  case 'importExtension':
483
506
  this._importExtension = options;
484
507
  break;
508
+ case 'importAliasPrefix':
509
+ this._importAliasPrefix = options;
510
+ break;
485
511
  case 'logger':
486
512
  this._logger = options;
487
513
  break;
@@ -608,3 +634,7 @@ export const DreamAppAllowedPackageManagersEnumValues = ['pnpm', 'yarn', 'npm',
608
634
  // to style import suffixes. When integrating with other apps, this
609
635
  // suffix style can change, and may need to be configured.
610
636
  export const GeneratorImportStyles = ['.js', '.ts', 'none'];
637
+ // GeneratorImportAliasPrefixes are used by CLI generators to determine the
638
+ // prefix of the import aliases they write (`@models/User.js` vs
639
+ // `#models/User.js`). The default is `@`; it becomes `#` in Dream 3.
640
+ export const GeneratorImportAliasPrefixes = ['@', '#'];
@@ -28,7 +28,7 @@ function areEqualOrUndefined(a, b) {
28
28
  if (typeof a === 'boolean')
29
29
  return a === b;
30
30
  if (typeof a === 'number')
31
- return a === b;
31
+ return a === b || (Number.isNaN(a) && Number.isNaN(b));
32
32
  if (typeof a === 'string')
33
33
  return a === b;
34
34
  if (a instanceof DateTime)
@@ -40,7 +40,7 @@ function areEqualOrUndefined(a, b) {
40
40
  if (a instanceof ClockTimeTz)
41
41
  return b instanceof ClockTimeTz && a.equals(b);
42
42
  if (Array.isArray(a))
43
- return Array.isArray(b) && a.length === b.length && !a.find((value, index) => !areEqual(value, b[index]));
43
+ return Array.isArray(b) && a.length === b.length && a.every((value, index) => areEqual(value, b[index]));
44
44
  if (a instanceof Dream)
45
45
  return a.equals(b);
46
46
  return undefined;
@@ -1,3 +1,4 @@
1
+ import DreamApp from '../../dream-app/index.js';
1
2
  import addImportSuffix from '../cli/addImportSuffix.js';
2
3
  import pascalize from '../pascalize.js';
3
4
  export default function absoluteDreamPath(destinationDreamPathType, fullyQualifiedDestinationModelName) {
@@ -13,13 +14,14 @@ export default function absoluteDreamPath(destinationDreamPathType, fullyQualifi
13
14
  }
14
15
  }
15
16
  function rootPath(destinationDreamPathType) {
17
+ const prefix = DreamApp.getOrFail().importAliasPrefix;
16
18
  switch (destinationDreamPathType) {
17
19
  case 'models':
18
- return '@models/';
20
+ return `${prefix}models/`;
19
21
  case 'serializers':
20
- return '@serializers/';
22
+ return `${prefix}serializers/`;
21
23
  case 'factories':
22
- return '@spec/factories/';
24
+ return `${prefix}spec/factories/`;
23
25
  default: {
24
26
  // protection so that if a new DreamPaths is ever added, this will throw a type error at build time
25
27
  const _never = destinationDreamPathType;
@@ -10,11 +10,11 @@ import DreamSerializerBuilder from './builders/DreamSerializerBuilder.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 dreamClass - The Dream model class the serializer renders
20
20
  * @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
@@ -9,7 +9,7 @@ import { DateTime } from '../utils/datetime/DateTime.js';
9
9
  import DreamSerializerBuilder from './builders/DreamSerializerBuilder.js';
10
10
  import ObjectSerializerBuilder from './builders/ObjectSerializerBuilder.js';
11
11
  import inferSerializerFromDreamOrViewModel from './helpers/inferSerializerFromDreamOrViewModel.js';
12
- import { nullFlattenedOpenapiKeys, nullFlattenedOpenapiKeysForValue, nullFlattenedSerializerKeys, nullFlattenedSerializerKeysOfOtherVariants, } from './helpers/nullFlattenedSerializerKeys.js';
12
+ import { nullFlattenedOpenapiKeys, nullFlattenedSerializerKeys, } from './helpers/nullFlattenedSerializerKeys.js';
13
13
  import { serializersForAssociatedClass } from './helpers/serializersForAssociatedClass.js';
14
14
  export default class SerializerRenderer {
15
15
  serializerBuilder;
@@ -79,29 +79,27 @@ export default class SerializerRenderer {
79
79
  return accumulator;
80
80
  if (attribute.options.flatten) {
81
81
  const flattenOptions = { casing: this.renderOpts.casing, passthroughData };
82
- // a flattened customAttribute's keys are the parent's own keys, cased like them: a
83
- // callback that returns null or undefined, or a serializer built over null or undefined
84
- // data (which renders null), renders each key its openapi requires as null, as a null
85
- // flattened rendersOne does, and one that returns a value of one variant of its openapi
86
- // renders as null each key another variant requires that the rendered one does not
87
- // declare, and each key the rendered one requires that a returned plain object leaves out
82
+ // a flattened customAttribute's keys are the parent's own keys, cased like them, and
83
+ // always include each key any shape of its openapi requires: a callback that returns
84
+ // null or undefined, or a serializer built over null or undefined data (which renders
85
+ // null), renders each of them as null, as a null flattened rendersOne does, and a
86
+ // present value renders its own keys over them, so a required key it leaves out is null
88
87
  const valueIsSerializerBuilder = value instanceof DreamSerializerBuilder || value instanceof ObjectSerializerBuilder;
89
88
  const valueRendersNull = value === null ||
90
89
  value === undefined ||
91
90
  (valueIsSerializerBuilder &&
92
91
  (value['data'] ?? null) === null);
92
+ const nullRequiredKeys = nullFlattenedOpenapiKeys(attribute.options.openapi, flattenOptions);
93
93
  if (valueRendersNull)
94
- return {
95
- ...accumulator,
96
- ...nullFlattenedOpenapiKeys(attribute.options.openapi, flattenOptions),
97
- };
94
+ return { ...accumulator, ...nullRequiredKeys };
98
95
  const renderedValue = applyRenderingOptionsToAttribute(null, value, attribute.name, {}, this.passthroughData, this.renderOpts);
99
96
  return {
100
97
  ...accumulator,
101
- ...nullFlattenedOpenapiKeysForValue(attribute.options.openapi, value, flattenOptions),
98
+ ...nullRequiredKeys,
102
99
  // a returned serializer renders its keys in the render's casing; a returned plain
103
- // object's top-level keys are cased here, its nested values left as returned
104
- ...(valueIsSerializerBuilder ? renderedValue : this.setCaseOfKeys(renderedValue)),
100
+ // object's top-level keys are cased here, its nested values left as returned, and a
101
+ // key it sets to undefined is skipped, so a required key renders null beneath it
102
+ ...(valueIsSerializerBuilder ? renderedValue : this.setCaseOfDefinedKeys(renderedValue)),
105
103
  };
106
104
  }
107
105
  else {
@@ -118,12 +116,6 @@ export default class SerializerRenderer {
118
116
  case 'rendersOne': {
119
117
  const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
120
118
  const associatedObject = data[attribute.name];
121
- if (!associatedObject && attribute.options.flatten) {
122
- return {
123
- ...accumulator,
124
- ...nullFlattenedSerializerKeys(serializersForAssociatedClass(data instanceof Dream ? data.constructor : null, attribute.name, attribute.options), { casing: this.renderOpts.casing, passthroughData }),
125
- };
126
- }
127
119
  const serializer = associatedObject
128
120
  ? serializerForAssociatedObject(associatedObject, attribute.options, renderTimeResolutionContext(attribute.options, 'rendersOne', attribute.name))
129
121
  : null;
@@ -134,12 +126,11 @@ export default class SerializerRenderer {
134
126
  if (attribute.options.flatten) {
135
127
  return {
136
128
  ...accumulator,
137
- // every payload carries the keys of every serializer the association may render with
138
- // (each STI child, each polymorphic target), so each key another one requires that the
139
- // rendered one does not declare renders as null
140
- ...(serializer
141
- ? nullFlattenedSerializerKeysOfOtherVariants(serializersForAssociatedClass(data instanceof Dream ? data.constructor : null, attribute.name, attribute.options), serializer, { casing: this.renderOpts.casing, passthroughData })
142
- : {}),
129
+ // a flattened rendersOne always renders each key any serializer the association may
130
+ // render with (each STI child, each polymorphic target) requires: as null when the
131
+ // associated object is null, and beneath the rendered serializer's own keys when it is
132
+ // present, so a required key the rendered serializer leaves out renders as null
133
+ ...nullFlattenedSerializerKeys(serializersForAssociatedClass(data instanceof Dream ? data.constructor : null, attribute.name, attribute.options), { casing: this.renderOpts.casing, passthroughData }),
143
134
  // passthrough data must be passed both into the serializer and render
144
135
  // because, if the serializer does accept passthrough data, then passing it in is how
145
136
  // it gets into the serializer, but if it does not accept passthrough data, and therefore
@@ -204,11 +195,13 @@ export default class SerializerRenderer {
204
195
  }, renderedAttributes);
205
196
  return renderedAttributes;
206
197
  }
207
- setCaseOfKeys(value) {
198
+ setCaseOfDefinedKeys(value) {
208
199
  if (typeof value !== 'object' || value === null || Array.isArray(value))
209
200
  return value;
210
201
  return Object.keys(value).reduce((casedValue, key) => {
211
- casedValue[this.setCase(key)] = value[key];
202
+ const keyValue = value[key];
203
+ if (keyValue !== undefined)
204
+ casedValue[this.setCase(key)] = keyValue;
212
205
  return casedValue;
213
206
  }, {});
214
207
  }
@@ -110,28 +110,29 @@ export default class DreamSerializerBuilder {
110
110
  * - `openapi` - (required) OpenAPI schema definition for the computed value
111
111
  * - `flatten` - When `true`, spreads the returned object's properties directly into the
112
112
  * parent serialized output instead of nesting them under `name`. The `openapi` option
113
- * then describes the whole returned object, not each property, because Psychic adds it
114
- * to the parent's schema as an `allOf` branch: either an object schema with full
115
- * property bodies (`type: 'object'`, `properties`, and `required` listing the keys that
116
- * are always present), or `{ $serializer: SomeSerializer }` when the callback returns
117
- * what that serializer renders. A serializer ref with `many: true` does not type-check
118
- * here, since a list has no properties to spread. A `null` return, a serializer built over
119
- * `null` or `undefined` data, or an `undefined` return without `required: false` renders as
120
- * `null` each key the `openapi` requires: a key an object shape lists in its `required`
121
- * array, a key a `$serializer` or `$serializable` ref's serializer declares without
122
- * `required: false`, and each such key of an `allOf`, `anyOf` or `oneOf` member. Its
123
- * optional keys stay absent, and an `undefined` return with `required: false` adds no keys.
124
- * A returned plain object also renders as `null` each key its shape requires that it
125
- * leaves out, and its optional keys it leaves out stay absent. When the `openapi` is an
126
- * `anyOf` or `oneOf`, or a `$serializable` ref to an STI base, a present value also renders
127
- * as `null` each key another variant requires that the rendered one does not declare, so a
128
- * client sees the same keys whichever variant is rendered.
113
+ * then describes the whole returned object, not each property: either an object schema
114
+ * with full property bodies (`type: 'object'`, `properties`, and `required` listing the
115
+ * keys that are always present), or `{ $serializer: SomeSerializer }` when the callback
116
+ * returns what that serializer renders. A serializer ref with `many: true` does not
117
+ * type-check here, since a list has no properties to spread. Whatever the callback
118
+ * returns, each key any shape of the `openapi` requires renders, as `null` wherever the
119
+ * value does not supply it (a returned plain object's key set to `undefined` does not
120
+ * supply it): a key an object shape lists in its `required` array, a key a `$serializer` or
121
+ * `$serializable` ref's serializer declares without `required: false` (for an STI base,
122
+ * any child's serializer), and each such key of any `allOf`, `anyOf` or `oneOf` member. So
123
+ * a `null` return, a serializer built over `null` or `undefined` data, or an `undefined`
124
+ * return without `required: false` renders each of them as `null`, and a present value
125
+ * renders its own keys over them, so a key another shape requires renders as `null` even
126
+ * when the value's own shape leaves it optional; the payload always includes the same
127
+ * keys whichever shape the value takes and whether it is null. An optional key no shape
128
+ * requires stays absent unless the value supplies it, and an `undefined` return with
129
+ * `required: false` adds no keys.
129
130
  * Every key it adds follows the render's casing like the parent's own keys: a returned
130
131
  * plain object's top-level keys and the `openapi`'s `properties` keys are cased (so
131
132
  * `casing: 'snake'` renders `phoneNumber` as `phone_number`, present or `null`), while
132
- * nested values render as returned. Attributes render in declaration order, so a key the returned object shares with
133
- * another attribute takes the value of whichever is declared later; under the `allOf`,
134
- * that value must match both schemas
133
+ * nested values render as returned. Attributes render in declaration order, so a key the
134
+ * returned object shares with another attribute takes the value of whichever is declared
135
+ * later, and that value must match both the attribute's declaration and the `openapi`'s
135
136
  * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
136
137
  * when omitted, attributes are required by default
137
138
  * @returns The serializer builder for method chaining
@@ -181,23 +182,29 @@ export default class DreamSerializerBuilder {
181
182
  * declaration order, so when the parent and the association share a key (e.g., `id`),
182
183
  * whichever is declared later wins: the association's value replaces an attribute declared
183
184
  * before the `rendersOne`, and an attribute declared after it replaces the association's.
184
- * When the association is `null`, each key its serializer requires renders as `null`: an
185
- * attribute, delegatedAttribute or customAttribute declared without `required: false`, and
186
- * every non-flattened `rendersOne` and `rendersMany` (for a polymorphic `BelongsTo`, each
187
- * key the serializer of any of its target classes requires, and for an association to an
188
- * STI base, each key the serializer of any of its STI children requires), in the same order
189
- * (so an `id` declared before the `rendersOne` becomes `null`), while a key declared with
190
- * `required: false` stays absent. A flattened `customAttribute` in that serializer adds the
185
+ * Each key its serializer requires always renders, as `null` wherever the association
186
+ * does not supply it: an attribute, delegatedAttribute or customAttribute declared without
187
+ * `required: false`, and every non-flattened `rendersOne` and `rendersMany` (for a
188
+ * polymorphic `BelongsTo`, each key the serializer of any of its target classes requires,
189
+ * and for an association to an STI base, each key the serializer of any of its STI children
190
+ * requires), in the same order (so an `id` declared before the `rendersOne` becomes `null`
191
+ * when the association is `null`). A flattened `customAttribute` in that serializer adds the
191
192
  * keys its `openapi` requires: a key an object shape lists in its `required` array, a key a
192
193
  * `$serializer` or `$serializable` ref's serializer declares without `required: false`, and
193
194
  * each such key of an `allOf`, `anyOf` or `oneOf` member; a ref with `many: true` adds none,
194
- * and a flattened `customAttribute` declared with `required: false` adds none. Nothing in
195
- * that serializer is rendered, so its custom attribute callbacks do not run. When the
196
- * association to an STI base or polymorphic `BelongsTo` is present, each key another STI
197
- * child's or target's serializer requires that the held one's serializer does not declare
198
- * also renders as `null`, so a client sees the same keys whichever is held. Psychic adds
199
- * the association's OpenAPI schema to the parent's as an `allOf` branch, so a shared key's
200
- * value must match both schemas, whichever declaration wins
195
+ * and a flattened `customAttribute` declared with `required: false` adds none. When the
196
+ * association is `null`, each of these keys renders as `null` and nothing in its serializer
197
+ * is rendered, so its custom attribute callbacks do not run. When it is present, the held
198
+ * object's serializer renders its own keys over them, so a key another STI child's or
199
+ * target's serializer requires renders as `null` where the held one's serializer does not
200
+ * render it, including a key that serializer declares with `required: false`; the payload
201
+ * always includes the same keys whichever is held and, for an association or a property
202
+ * given a `serializer` or `viewModelClass` option, whether it is `null` (a `null` property
203
+ * that is not an association and has neither option renders none of these keys, since no
204
+ * serializer is known for it). A key declared with `required: false` that no serializer
205
+ * requires stays absent unless it renders. The association's OpenAPI schema describes the
206
+ * parent's payload too, so a shared key's value must match both the parent's declaration
207
+ * and the association's, whichever declaration wins
201
208
  * - `optional` - When `true`, allows the association to be null/missing without causing
202
209
  * an `OpenapiResponseValidationFailure` during Psychic controller unit specs. By default,
203
210
  * `rendersOne` expects the association to be present (mirroring the `optional` option on
@@ -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