@rvoh/dream 1.2.1 → 1.3.1

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 (216) hide show
  1. package/dist/cjs/src/Dream.js +101 -33
  2. package/dist/cjs/src/db/errors.js +16 -6
  3. package/dist/cjs/src/dream/QueryDriver/Kysely.js +33 -20
  4. package/dist/cjs/src/dream/internal/printSerializerHierarchyLevel.js +17 -0
  5. package/dist/cjs/src/dream-app/cache.js +12 -2
  6. package/dist/cjs/src/errors/db/CheckConstraintViolation.js +15 -0
  7. package/dist/cjs/src/errors/db/DataTypeColumnTypeMismatch.js +1 -5
  8. package/dist/cjs/src/errors/db/NotNullViolation.js +15 -0
  9. package/dist/cjs/src/helpers/cli/generateMigration.js +1 -0
  10. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +20 -5
  11. package/dist/cjs/src/helpers/cli/generateStiMigrationContent.js +8 -2
  12. package/dist/cjs/src/index.js +6 -2
  13. package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
  14. package/dist/esm/src/Dream.js +102 -34
  15. package/dist/esm/src/db/errors.js +14 -4
  16. package/dist/esm/src/dream/QueryDriver/Kysely.js +34 -21
  17. package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +14 -0
  18. package/dist/esm/src/dream-app/cache.js +12 -2
  19. package/dist/esm/src/errors/db/CheckConstraintViolation.js +12 -0
  20. package/dist/esm/src/errors/db/DataTypeColumnTypeMismatch.js +1 -5
  21. package/dist/esm/src/errors/db/NotNullViolation.js +12 -0
  22. package/dist/esm/src/helpers/cli/generateMigration.js +1 -0
  23. package/dist/esm/src/helpers/cli/generateMigrationContent.js +20 -5
  24. package/dist/esm/src/helpers/cli/generateStiMigrationContent.js +8 -2
  25. package/dist/esm/src/index.js +2 -0
  26. package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
  27. package/dist/types/src/Dream.d.ts +77 -52
  28. package/dist/types/src/db/errors.d.ts +14 -2
  29. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +1 -0
  30. package/dist/types/src/dream/internal/printSerializerHierarchyLevel.d.ts +7 -0
  31. package/dist/types/src/errors/db/CheckConstraintViolation.d.ts +10 -0
  32. package/dist/types/src/errors/db/NotNullViolation.d.ts +10 -0
  33. package/dist/types/src/helpers/cli/generateMigrationContent.d.ts +2 -1
  34. package/dist/types/src/helpers/cli/generateStiMigrationContent.d.ts +2 -1
  35. package/dist/types/src/index.d.ts +2 -0
  36. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +1 -1
  37. package/dist/types/src/types/dream.d.ts +1 -1
  38. package/dist/types/src/types/dream.ts +2 -2
  39. package/docs/assets/navigation.js +1 -1
  40. package/docs/assets/search.js +1 -1
  41. package/docs/classes/Benchmark.html +2 -2
  42. package/docs/classes/CalendarDate.html +2 -2
  43. package/docs/classes/CheckConstraintViolation.html +14 -0
  44. package/docs/classes/CliFileWriter.html +2 -2
  45. package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
  46. package/docs/classes/DataTypeColumnTypeMismatch.html +3 -3
  47. package/docs/classes/Decorators.html +19 -19
  48. package/docs/classes/Dream.html +209 -308
  49. package/docs/classes/DreamApp.html +4 -4
  50. package/docs/classes/DreamBin.html +2 -2
  51. package/docs/classes/DreamCLI.html +4 -4
  52. package/docs/classes/DreamImporter.html +2 -2
  53. package/docs/classes/DreamLogos.html +2 -2
  54. package/docs/classes/DreamMigrationHelpers.html +7 -7
  55. package/docs/classes/DreamSerializerBuilder.html +8 -8
  56. package/docs/classes/DreamTransaction.html +2 -2
  57. package/docs/classes/Encrypt.html +2 -2
  58. package/docs/classes/Env.html +2 -2
  59. package/docs/classes/GlobalNameNotSet.html +3 -3
  60. package/docs/classes/NonLoadedAssociation.html +3 -3
  61. package/docs/classes/NotNullViolation.html +14 -0
  62. package/docs/classes/ObjectSerializerBuilder.html +8 -8
  63. package/docs/classes/Query.html +60 -60
  64. package/docs/classes/Range.html +2 -2
  65. package/docs/classes/RecordNotFound.html +3 -3
  66. package/docs/classes/ValidationError.html +3 -3
  67. package/docs/functions/DreamSerializer.html +1 -1
  68. package/docs/functions/ObjectSerializer.html +1 -1
  69. package/docs/functions/ReplicaSafe.html +1 -1
  70. package/docs/functions/STI.html +1 -1
  71. package/docs/functions/SoftDelete.html +1 -1
  72. package/docs/functions/camelize.html +1 -1
  73. package/docs/functions/capitalize.html +1 -1
  74. package/docs/functions/cloneDeepSafe.html +1 -1
  75. package/docs/functions/closeAllDbConnections.html +1 -1
  76. package/docs/functions/compact.html +1 -1
  77. package/docs/functions/dreamDbConnections.html +1 -1
  78. package/docs/functions/dreamPath.html +1 -1
  79. package/docs/functions/expandStiClasses.html +1 -1
  80. package/docs/functions/generateDream.html +1 -1
  81. package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
  82. package/docs/functions/groupBy.html +1 -1
  83. package/docs/functions/hyphenize.html +1 -1
  84. package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
  85. package/docs/functions/inferSerializersFromDreamClassOrViewModelClass.html +1 -1
  86. package/docs/functions/intersection.html +1 -1
  87. package/docs/functions/isDreamSerializer.html +1 -1
  88. package/docs/functions/isEmpty.html +1 -1
  89. package/docs/functions/loadRepl.html +1 -1
  90. package/docs/functions/lookupClassByGlobalName.html +1 -1
  91. package/docs/functions/normalizeUnicode.html +1 -1
  92. package/docs/functions/pascalize.html +1 -1
  93. package/docs/functions/pgErrorType.html +1 -1
  94. package/docs/functions/range-1.html +1 -1
  95. package/docs/functions/relativeDreamPath.html +1 -1
  96. package/docs/functions/round.html +1 -1
  97. package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
  98. package/docs/functions/sharedPathPrefix.html +1 -1
  99. package/docs/functions/snakeify.html +1 -1
  100. package/docs/functions/sort.html +1 -1
  101. package/docs/functions/sortBy.html +1 -1
  102. package/docs/functions/sortObjectByKey.html +1 -1
  103. package/docs/functions/sortObjectByValue.html +1 -1
  104. package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
  105. package/docs/functions/uncapitalize.html +1 -1
  106. package/docs/functions/uniq.html +1 -1
  107. package/docs/functions/untypedDb.html +1 -1
  108. package/docs/functions/validateColumn.html +1 -1
  109. package/docs/functions/validateTable.html +1 -1
  110. package/docs/interfaces/BelongsToStatement.html +2 -2
  111. package/docs/interfaces/DecoratorContext.html +2 -2
  112. package/docs/interfaces/DreamAppInitOptions.html +2 -2
  113. package/docs/interfaces/DreamAppOpts.html +2 -2
  114. package/docs/interfaces/EncryptOptions.html +2 -2
  115. package/docs/interfaces/InternalAnyTypedSerializerRendersMany.html +2 -2
  116. package/docs/interfaces/InternalAnyTypedSerializerRendersOne.html +2 -2
  117. package/docs/interfaces/OpenapiDescription.html +2 -2
  118. package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
  119. package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
  120. package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
  121. package/docs/interfaces/SerializerRendererOpts.html +2 -2
  122. package/docs/modules.html +2 -0
  123. package/docs/types/Camelized.html +1 -1
  124. package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
  125. package/docs/types/DateTime.html +1 -1
  126. package/docs/types/DbConnectionType.html +1 -1
  127. package/docs/types/DbTypes.html +1 -1
  128. package/docs/types/DreamAppAllowedPackageManagersEnum.html +1 -1
  129. package/docs/types/DreamAssociationMetadata.html +1 -1
  130. package/docs/types/DreamAttributes.html +1 -1
  131. package/docs/types/DreamClassAssociationAndStatement.html +1 -1
  132. package/docs/types/DreamClassColumn.html +1 -1
  133. package/docs/types/DreamColumn.html +1 -1
  134. package/docs/types/DreamColumnNames.html +1 -1
  135. package/docs/types/DreamLogLevel.html +1 -1
  136. package/docs/types/DreamLogger.html +1 -1
  137. package/docs/types/DreamModelSerializerType.html +1 -1
  138. package/docs/types/DreamOrViewModelClassSerializerKey.html +1 -1
  139. package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
  140. package/docs/types/DreamParamSafeAttributes.html +1 -1
  141. package/docs/types/DreamParamSafeColumnNames.html +1 -1
  142. package/docs/types/DreamSerializable.html +1 -1
  143. package/docs/types/DreamSerializableArray.html +1 -1
  144. package/docs/types/DreamSerializerKey.html +1 -1
  145. package/docs/types/DreamSerializers.html +1 -1
  146. package/docs/types/DreamTableSchema.html +1 -1
  147. package/docs/types/DreamVirtualColumns.html +1 -1
  148. package/docs/types/EncryptAlgorithm.html +1 -1
  149. package/docs/types/HasManyStatement.html +1 -1
  150. package/docs/types/HasOneStatement.html +1 -1
  151. package/docs/types/Hyphenized.html +1 -1
  152. package/docs/types/IdType.html +1 -1
  153. package/docs/types/OpenapiAllTypes.html +1 -1
  154. package/docs/types/OpenapiFormats.html +1 -1
  155. package/docs/types/OpenapiNumberFormats.html +1 -1
  156. package/docs/types/OpenapiPrimitiveBaseTypes.html +1 -1
  157. package/docs/types/OpenapiPrimitiveTypes.html +1 -1
  158. package/docs/types/OpenapiSchemaArray.html +1 -1
  159. package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
  160. package/docs/types/OpenapiSchemaBase.html +1 -1
  161. package/docs/types/OpenapiSchemaBody.html +1 -1
  162. package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
  163. package/docs/types/OpenapiSchemaCommonFields.html +1 -1
  164. package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
  165. package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
  166. package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
  167. package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
  168. package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  169. package/docs/types/OpenapiSchemaInteger.html +1 -1
  170. package/docs/types/OpenapiSchemaNull.html +1 -1
  171. package/docs/types/OpenapiSchemaNumber.html +1 -1
  172. package/docs/types/OpenapiSchemaObject.html +1 -1
  173. package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
  174. package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
  175. package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
  176. package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  177. package/docs/types/OpenapiSchemaObjectBase.html +1 -1
  178. package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
  179. package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
  180. package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
  181. package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
  182. package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
  183. package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  184. package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  185. package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  186. package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  187. package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  188. package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  189. package/docs/types/OpenapiSchemaString.html +1 -1
  190. package/docs/types/OpenapiShorthandAllTypes.html +1 -1
  191. package/docs/types/OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  192. package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
  193. package/docs/types/OpenapiTypeField.html +1 -1
  194. package/docs/types/Pascalized.html +1 -1
  195. package/docs/types/PrimaryKeyType.html +1 -1
  196. package/docs/types/RoundingPrecision.html +1 -1
  197. package/docs/types/SerializerCasing.html +1 -1
  198. package/docs/types/SimpleObjectSerializerType.html +1 -1
  199. package/docs/types/Snakeified.html +1 -1
  200. package/docs/types/Timestamp.html +1 -1
  201. package/docs/types/UpdateableAssociationProperties.html +1 -1
  202. package/docs/types/UpdateableProperties.html +1 -1
  203. package/docs/types/ValidationType.html +1 -1
  204. package/docs/types/ViewModel.html +1 -1
  205. package/docs/types/ViewModelClass.html +1 -1
  206. package/docs/types/WhereStatementForDream.html +1 -1
  207. package/docs/types/WhereStatementForDreamClass.html +1 -1
  208. package/docs/variables/DateTime-1.html +1 -1
  209. package/docs/variables/DreamAppAllowedPackageManagersEnumValues.html +1 -1
  210. package/docs/variables/DreamConst.html +1 -1
  211. package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
  212. package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
  213. package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
  214. package/docs/variables/ops.html +1 -1
  215. package/docs/variables/primaryKeyTypes.html +1 -1
  216. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import yoctocolors from 'yoctocolors';
2
- import { pgErrorType } from './db/errors.js';
2
+ import { pgErrorType, UNIQUE_VIOLATION } from './db/errors.js';
3
3
  import db from './db/index.js';
4
4
  import associationToGetterSetterProp from './decorators/field/association/associationToGetterSetterProp.js';
5
5
  import { blankAssociationsFactory } from './decorators/field/association/shared.js';
@@ -19,6 +19,7 @@ import destroyDream from './dream/internal/destroyDream.js';
19
19
  import { destroyOptions, reallyDestroyOptions, undestroyOptions, } from './dream/internal/destroyOptions.js';
20
20
  import ensureSTITypeFieldIsSet from './dream/internal/ensureSTITypeFieldIsSet.js';
21
21
  import findOrCreateBy from './dream/internal/findOrCreateBy.js';
22
+ import printSerializerHierarchyLevel from './dream/internal/printSerializerHierarchyLevel.js';
22
23
  import reload from './dream/internal/reload.js';
23
24
  import runValidations from './dream/internal/runValidations.js';
24
25
  import saveDream from './dream/internal/saveDream.js';
@@ -47,10 +48,8 @@ import cloneDeepSafe from './helpers/cloneDeepSafe.js';
47
48
  import { DateTime } from './helpers/DateTime.js';
48
49
  import cachedTypeForAttribute from './helpers/db/cachedTypeForAttribute.js';
49
50
  import isJsonColumn from './helpers/db/types/isJsonColumn.js';
50
- import { indent } from './helpers/indent.js';
51
51
  import notEqual from './helpers/notEqual.js';
52
52
  import { inferSerializersFromDreamClassOrViewModelClass } from './serializer/helpers/inferSerializerFromDreamOrViewModel.js';
53
- import { serializerForAssociatedClass } from './serializer/helpers/serializerForAssociatedClass.js';
54
53
  export default class Dream {
55
54
  DB;
56
55
  /**
@@ -487,42 +486,65 @@ export default class Dream {
487
486
  static recursiveSerializationMap(serializer, { forDisplay = false, forDisplayDepth = 0, } = {}) {
488
487
  const serializerBuilder = serializer(undefined, undefined);
489
488
  const serializerAssociations = serializerBuilder['attributes'].filter(attribute => ['rendersOne', 'rendersMany', 'delegatedAttribute'].includes(attribute.type));
490
- return serializerAssociations.reduce((acc, serializerAssociation) => {
489
+ return serializerAssociations.reduce((accumulator, serializerAssociation) => {
491
490
  const serializerAssociationName = serializerAssociation.targetName ??
492
491
  serializerAssociation.name;
493
492
  const serializerAssociationType = serializerAssociation.type;
494
493
  const association = this['getAssociationMetadata'](serializerAssociationName);
495
494
  if (!association)
496
- return acc;
497
- const associatedClasses = association.modelCB();
498
- const associatedClass = Array.isArray(associatedClasses) ? associatedClasses[0] : associatedClasses;
499
- if (!associatedClass)
495
+ return accumulator;
496
+ const maybeAssociatedClasses = association.modelCB();
497
+ if (!maybeAssociatedClasses)
500
498
  throw new Error(`No class defined on ${serializerAssociationName} association on ${this.sanitizedName}`);
501
- const associationSerializer = serializerForAssociatedClass(this.prototype, serializerAssociationName, serializerAssociation.options);
502
- if (!associationSerializer)
503
- throw new Error(`No serializer found to render ${serializerAssociationName} on ${this.sanitizedName}`);
504
- if (forDisplay && serializerAssociationType !== 'delegatedAttribute') {
505
- const hierarchyLine = '└───';
506
- const indentation = indent((hierarchyLine.length + 1) * forDisplayDepth, {
507
- tabWidth: 1,
508
- });
509
- const prefix = `${hierarchyLine} `;
510
- const nestedAssociationDisplay = indentation + `${prefix}${serializerAssociationType} ${yoctocolors.cyan(serializerAssociationName)}`;
511
- console.log(nestedAssociationDisplay);
512
- console.log(yoctocolors.gray(indentation +
513
- indent(prefix.length, { tabWidth: 1 }) +
514
- associationSerializer.globalName));
515
- }
516
- acc[association.as] = {
517
- parentDreamClass: this,
518
- nestedSerializerInfo: serializerAssociation.type === 'delegatedAttribute'
519
- ? {}
520
- : associatedClass['recursiveSerializationMap'](associationSerializer, {
499
+ const associatedClasses = Array.isArray(maybeAssociatedClasses)
500
+ ? maybeAssociatedClasses
501
+ : [maybeAssociatedClasses];
502
+ /////////////////////////////////////////////////
503
+ // map associated classes to their serializers //
504
+ /////////////////////////////////////////////////
505
+ const associatedClassSerializerTuples = associatedClasses.flatMap(associatedClass => {
506
+ /**
507
+ * `serializers` is an array because `associatedClass` may be an STI
508
+ * base, with each of its STI children having its own serializer
509
+ */
510
+ const serializers = serializerAssociation.options.serializer
511
+ ? [serializerAssociation.options.serializer]
512
+ : inferSerializersFromDreamClassOrViewModelClass(associatedClass, serializerAssociation.options.serializerKey);
513
+ if (!serializers.length)
514
+ throw new Error(`No serializer found to render ${serializerAssociationName} on ${this.sanitizedName}`);
515
+ return serializers.map(serializer => [associatedClass, serializer]);
516
+ });
517
+ /////////////////////////////////////////////////////
518
+ // end:map associated classes to their serializers //
519
+ /////////////////////////////////////////////////////
520
+ ///////////////////////////////////////////////////////////////////////////////////////////////////
521
+ // reduce over all associated serializers, recursively building out their associated serializers //
522
+ ///////////////////////////////////////////////////////////////////////////////////////////////////
523
+ const innerAssociationSerializerInfo = associatedClassSerializerTuples.reduce((innerAccumulator, [associatedClass, associatedSerializer]) => {
524
+ if (forDisplay && serializerAssociationType !== 'delegatedAttribute') {
525
+ printSerializerHierarchyLevel({
526
+ serializerAssociationType,
527
+ serializerAssociationName,
528
+ associationSerializer: associatedSerializer,
529
+ forDisplayDepth,
530
+ });
531
+ }
532
+ return {
533
+ ...innerAccumulator,
534
+ ...associatedClass['recursiveSerializationMap'](associatedSerializer, {
521
535
  forDisplay,
522
536
  forDisplayDepth: forDisplayDepth + 1,
523
537
  }),
538
+ };
539
+ }, {});
540
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
541
+ // end:reduce over all associated serializers, recursively building out their associated serializers //
542
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
543
+ accumulator[association.as] = {
544
+ parentDreamClass: this,
545
+ nestedSerializerInfo: serializerAssociation.type === 'delegatedAttribute' ? {} : innerAssociationSerializerInfo,
524
546
  };
525
- return acc;
547
+ return accumulator;
526
548
  }, {});
527
549
  }
528
550
  /**
@@ -657,11 +679,57 @@ export default class Dream {
657
679
  return map;
658
680
  }
659
681
  /**
660
- * @internal
682
+ * Checks whether the specified association has been defined on this Dream model.
683
+ *
684
+ * ```ts
685
+ * const user = User.new()
686
+ *
687
+ * user.hasAssociation('posts')
688
+ * // true (if User has a posts association defined)
689
+ *
690
+ * user.hasAssociation('nonExistentAssociation')
691
+ * // false
692
+ *
693
+ * // Useful for conditional association handling
694
+ * if (user.hasAssociation('posts')) {
695
+ * user = await user.load('posts').execute()
696
+ * }
697
+ * ```
698
+ *
699
+ * @param associationName - The name of the association to check for
700
+ * @returns `true` if the association exists on this model, `false` otherwise
701
+ */
702
+ hasAssociation(associationName) {
703
+ return !!(this.associationMetadataByType.belongsTo.find(association => association.as === associationName) ||
704
+ this.associationMetadataByType.hasOne.find(association => association.as === associationName) ||
705
+ this.associationMetadataByType.hasMany.find(association => association.as === associationName));
706
+ }
707
+ /**
708
+ * Returns all of the association names for this Dream class.
709
+ * This includes all BelongsTo, HasOne, and HasMany associations
710
+ * defined on the model.
661
711
  *
662
- * Returns all of the association names for this dream class
712
+ * This is useful for introspection, debugging, or dynamically working
713
+ * with all associations on a model.
714
+ *
715
+ * ```ts
716
+ * class User extends ApplicationModel {
717
+ * @deco.HasMany('Post')
718
+ * public posts: Post[]
719
+ *
720
+ * @deco.HasOne('Profile')
721
+ * public profile: Profile
722
+ *
723
+ * @deco.BelongsTo('Company')
724
+ * public company: Company
725
+ * }
726
+ *
727
+ * User.associationNames
728
+ * // ['posts', 'profile', 'company']
729
+ * })
730
+ * ```
663
731
  *
664
- * @returns All of the association names for this dream class
732
+ * @returns An array containing all association names defined on this Dream class
665
733
  */
666
734
  static get associationNames() {
667
735
  const allAssociations = [
@@ -853,7 +921,7 @@ export default class Dream {
853
921
  return dreamModel;
854
922
  }
855
923
  catch (err) {
856
- if (pgErrorType(err) === 'UNIQUE_CONSTRAINT_VIOLATION') {
924
+ if (pgErrorType(err) === UNIQUE_VIOLATION) {
857
925
  const dreamModel = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
858
926
  if (!dreamModel)
859
927
  throw new CreateOrFindByFailedToCreateAndFind(this);
@@ -907,7 +975,7 @@ export default class Dream {
907
975
  }, skipHooks ? { skipHooks } : undefined);
908
976
  }
909
977
  catch (err) {
910
- if (pgErrorType(err) === 'UNIQUE_CONSTRAINT_VIOLATION') {
978
+ if (pgErrorType(err) === UNIQUE_VIOLATION) {
911
979
  const existingRecord = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
912
980
  if (!existingRecord)
913
981
  throw new CreateOrUpdateByFailedToCreateAndUpdate(this);
@@ -7,12 +7,22 @@
7
7
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
8
8
  // @ts-ignore
9
9
  import pg from 'pg';
10
- const UNIQUE_CONSTRAINT_VIOLATION = 'UNIQUE_CONSTRAINT_VIOLATION';
11
- const INVALID_INPUT_SYNTAX = 'INVALID_INPUT_SYNTAX';
10
+ export const CHECK_VIOLATION = 'CHECK_VIOLATION';
11
+ export const FOREIGN_KEY_VIOLATION = 'FOREIGN_KEY_VIOLATION';
12
+ export const INTEGRITY_CONSTRAINT_VIOLATION = 'INTEGRITY_CONSTRAINT_VIOLATION';
13
+ export const INVALID_INPUT_SYNTAX = 'INVALID_INPUT_SYNTAX';
14
+ export const NOT_NULL_VIOLATION = 'NOT_NULL_VIOLATION';
15
+ export const RESTRICT_VIOLATION = 'RESTRICT_VIOLATION';
16
+ export const UNIQUE_VIOLATION = 'UNIQUE_VIOLATION';
12
17
  export const PG_ERRORS = {
13
- '23505': UNIQUE_CONSTRAINT_VIOLATION,
14
- '22P02': INVALID_INPUT_SYNTAX,
18
+ '23505': UNIQUE_VIOLATION,
15
19
  '22007': INVALID_INPUT_SYNTAX,
20
+ '22P02': INVALID_INPUT_SYNTAX,
21
+ '23502': NOT_NULL_VIOLATION,
22
+ '23514': CHECK_VIOLATION,
23
+ '23000': INTEGRITY_CONSTRAINT_VIOLATION,
24
+ '23001': RESTRICT_VIOLATION,
25
+ '23503': FOREIGN_KEY_VIOLATION,
16
26
  };
17
27
  function pgErrorFromCode(code) {
18
28
  return PG_ERRORS[code] || null;
@@ -3,7 +3,7 @@ import pluralize from 'pluralize-esm';
3
3
  import writeSyncFile from '../../bin/helpers/sync.js';
4
4
  import { CliFileWriter } from '../../cli/CliFileWriter.js';
5
5
  import DreamCLI from '../../cli/index.js';
6
- import { pgErrorType } from '../../db/errors.js';
6
+ import { CHECK_VIOLATION, INVALID_INPUT_SYNTAX, NOT_NULL_VIOLATION, pgErrorType } from '../../db/errors.js';
7
7
  import _db from '../../db/index.js';
8
8
  import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
9
9
  import PackageManager from '../../dream-app/helpers/PackageManager.js';
@@ -18,7 +18,9 @@ import MissingThroughAssociationSource from '../../errors/associations/MissingTh
18
18
  import ThroughAssociationConditionsIncompatibleWithThroughAssociationSource from '../../errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js';
19
19
  import CannotNegateSimilarityClause from '../../errors/CannotNegateSimilarityClause.js';
20
20
  import CannotPassUndefinedAsAValueToAWhereClause from '../../errors/CannotPassUndefinedAsAValueToAWhereClause.js';
21
+ import CheckConstraintViolation from '../../errors/db/CheckConstraintViolation.js';
21
22
  import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
23
+ import NotNullViolation from '../../errors/db/NotNullViolation.js';
22
24
  import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
23
25
  import CalendarDate from '../../helpers/CalendarDate.js';
24
26
  import camelize from '../../helpers/camelize.js';
@@ -31,6 +33,7 @@ import _dropDb from '../../helpers/db/dropDb.js';
31
33
  import loadPgClient from '../../helpers/db/loadPgClient.js';
32
34
  import runMigration from '../../helpers/db/runMigration.js';
33
35
  import EnvInternal from '../../helpers/EnvInternal.js';
36
+ import groupBy from '../../helpers/groupBy.js';
34
37
  import isEmpty from '../../helpers/isEmpty.js';
35
38
  import namespaceColumn from '../../helpers/namespaceColumn.js';
36
39
  import normalizeUnicode from '../../helpers/normalizeUnicode.js';
@@ -433,11 +436,21 @@ export default class KyselyQueryDriver extends QueryDriverBase {
433
436
  }
434
437
  catch (error) {
435
438
  switch (pgErrorType(error)) {
436
- case 'INVALID_INPUT_SYNTAX':
439
+ case INVALID_INPUT_SYNTAX:
437
440
  throw new DataTypeColumnTypeMismatch({
438
441
  dream,
439
442
  error: error instanceof Error ? error : new Error('database column type error'),
440
443
  });
444
+ case NOT_NULL_VIOLATION:
445
+ throw new NotNullViolation({
446
+ dream,
447
+ error: error instanceof Error ? error : new Error('not null violation'),
448
+ });
449
+ case CHECK_VIOLATION:
450
+ throw new CheckConstraintViolation({
451
+ dream,
452
+ error: error instanceof Error ? error : new Error('check constraint violation'),
453
+ });
441
454
  }
442
455
  throw error;
443
456
  }
@@ -1202,7 +1215,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1202
1215
  const keys = Object.keys(preloadStatement);
1203
1216
  for (const key of keys) {
1204
1217
  const nestedDreams = await this.applyOnePreload(key, dream, this.applyablePreloadOnStatements(preloadOnStatements[key]));
1205
- if (nestedDreams) {
1218
+ if (nestedDreams.length) {
1206
1219
  await this.applyPreload(preloadStatement[key], preloadOnStatements[key], nestedDreams);
1207
1220
  }
1208
1221
  }
@@ -1697,18 +1710,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1697
1710
  *
1698
1711
  * Used to preload polymorphic belongs to associations
1699
1712
  */
1700
- async preloadPolymorphicBelongsTo(association, dreams) {
1701
- if (!association.polymorphic)
1702
- throw new Error(`Association ${association.as} points to an array of models but is not designated polymorphic`);
1703
- if (association.type !== 'BelongsTo')
1704
- throw new Error(`Polymorphic association ${association.as} points to an array of models but is ${association.type}. Only BelongsTo associations may point to an array of models.`);
1705
- const nestedDreamsForNextRoundOfPreloading = [];
1706
- for (const associatedModel of association.modelCB()) {
1707
- await this.preloadPolymorphicAssociationModel(dreams, association, associatedModel, nestedDreamsForNextRoundOfPreloading);
1708
- }
1709
- return nestedDreamsForNextRoundOfPreloading;
1713
+ async preloadPolymorphicBelongsTo(association, associatedModels, dreams) {
1714
+ return compact(await Promise.all(associatedModels.map(associatedModel => this.preloadPolymorphicAssociationModel(dreams, association, associatedModel)))).flat();
1710
1715
  }
1711
- async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass, nestedDreamsForNextRoundOfPreloading) {
1716
+ async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass) {
1712
1717
  const relevantAssociatedModels = dreams.filter((dream) => {
1713
1718
  const field = association.foreignKeyTypeField();
1714
1719
  return dream[field] === associatedDreamClass['stiBaseClassOrOwnClassName'] || dream[field] === null;
@@ -1727,7 +1732,6 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1727
1732
  [associatedDreamClass.primaryKey]: relevantAssociatedModels.map((dream) => dream[association.foreignKey()]),
1728
1733
  })
1729
1734
  .all();
1730
- loadedAssociations.forEach((loadedAssociation) => nestedDreamsForNextRoundOfPreloading.push(loadedAssociation));
1731
1735
  //////////////////////////////////////////////////////////////////////////////////////////////
1732
1736
  // Associate each loaded association with each dream based on primary key and foreign key type
1733
1737
  //////////////////////////////////////////////////////////////////////////////////////////////
@@ -1742,6 +1746,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1742
1746
  ///////////////////////////////////////////////////////////////////////////////////////////////////
1743
1747
  // end: Associate each loaded association with each dream based on primary key and foreign key type
1744
1748
  ///////////////////////////////////////////////////////////////////////////////////////////////////
1749
+ return loadedAssociations;
1745
1750
  }
1746
1751
  }
1747
1752
  /**
@@ -1780,12 +1785,18 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1780
1785
  *
1781
1786
  * Applies a preload statement
1782
1787
  */
1783
- async applyOnePreload(associationName, dreams, onStatement = {}) {
1788
+ async applyOnePreload(associationNameAndMaybeAlias, dreams, onStatement = {}) {
1784
1789
  if (!Array.isArray(dreams))
1785
1790
  dreams = [dreams];
1786
- const dream = dreams.find(dream => dream['getAssociationMetadata'](associationName));
1787
- if (!dream)
1788
- return;
1791
+ const { name: associationName } = associationStringToNameAndAlias(associationNameAndMaybeAlias);
1792
+ dreams = dreams.filter(dream => dream.hasAssociation(associationName));
1793
+ const groupedDreams = groupBy(dreams, dream => dream.sanitizedConstructorName);
1794
+ return (await Promise.all(Object.keys(groupedDreams).map(key => this._applyOnePreload(associationName, groupedDreams[key], onStatement)))).flat();
1795
+ }
1796
+ async _applyOnePreload(associationName, dreams, onStatement = {}) {
1797
+ if (!dreams.length)
1798
+ return [];
1799
+ const dream = dreams[0];
1789
1800
  const { name, alias: _alias } = associationStringToNameAndAlias(associationName);
1790
1801
  const alias = _alias || name;
1791
1802
  const association = dream['getAssociationMetadata'](name);
@@ -1793,8 +1804,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1793
1804
  throw new UnexpectedUndefined();
1794
1805
  const dreamClass = dream.constructor;
1795
1806
  const dreamClassToHydrate = association.modelCB();
1796
- if ((association.polymorphic && association.type === 'BelongsTo') || Array.isArray(dreamClassToHydrate))
1797
- return this.preloadPolymorphicBelongsTo(association, dreams);
1807
+ if (Array.isArray(dreamClassToHydrate)) {
1808
+ const preloadedPolymorphicBelongsTos = await this.preloadPolymorphicBelongsTo(association, dreamClassToHydrate, dreams);
1809
+ return preloadedPolymorphicBelongsTos;
1810
+ }
1798
1811
  const dreamClassToHydrateColumns = [...dreamClassToHydrate.columns()];
1799
1812
  const columnsToPluck = dreamClassToHydrateColumns.map(column => this.namespaceColumn(column.toString(), alias));
1800
1813
  columnsToPluck.push(this.namespaceColumn(dreamClass.primaryKey, dreamClass.table));
@@ -0,0 +1,14 @@
1
+ import yoctocolors from 'yoctocolors';
2
+ import { indent } from '../../helpers/indent.js';
3
+ export default function printSerializerHierarchyLevel({ serializerAssociationType, serializerAssociationName, associationSerializer, forDisplayDepth, }) {
4
+ const hierarchyLine = '└───';
5
+ const indentation = indent((hierarchyLine.length + 1) * forDisplayDepth, {
6
+ tabWidth: 1,
7
+ });
8
+ const prefix = `${hierarchyLine} `;
9
+ const nestedAssociationDisplay = indentation + `${prefix}${serializerAssociationType} ${yoctocolors.cyan(serializerAssociationName)}`;
10
+ console.log(nestedAssociationDisplay);
11
+ console.log(yoctocolors.gray(indentation +
12
+ indent(prefix.length, { tabWidth: 1 }) +
13
+ associationSerializer.globalName));
14
+ }
@@ -1,9 +1,19 @@
1
+ import EnvInternal from '../helpers/EnvInternal.js';
1
2
  let _dreamApp = undefined;
2
3
  export function cacheDreamApp(dreamconf) {
3
4
  _dreamApp = dreamconf;
4
5
  }
5
6
  export function getCachedDreamAppOrFail() {
6
- if (!_dreamApp)
7
- throw new Error('Must call `initializeDreamApp` or `initializePsychicApp` before loading cached DreamApp.getOrFail()');
7
+ if (!_dreamApp) {
8
+ const baseErrorMessage = 'Must call `initializeDreamApp` or `initializePsychicApp` before loading cached DreamApp.getOrFail().';
9
+ if (EnvInternal.isTest) {
10
+ throw new Error(`${baseErrorMessage}
11
+
12
+ Check for specs running directly in a \`describe\` or \`context\` block rather than nested within an \`it\` block.`);
13
+ }
14
+ else {
15
+ throw new Error(baseErrorMessage);
16
+ }
17
+ }
8
18
  return _dreamApp;
9
19
  }
@@ -0,0 +1,12 @@
1
+ export default class CheckConstraintViolation extends Error {
2
+ dream;
3
+ error;
4
+ constructor({ dream, error }) {
5
+ super();
6
+ this.dream = dream;
7
+ this.error = error;
8
+ }
9
+ get message() {
10
+ return this.error.message;
11
+ }
12
+ }
@@ -7,10 +7,6 @@ export default class DataTypeColumnTypeMismatch extends Error {
7
7
  this.error = error;
8
8
  }
9
9
  get message() {
10
- return `\
11
- Failed to save ${this.dream.sanitizedConstructorName}:
12
-
13
- ${this.error.message}
14
- `;
10
+ return this.error.message;
15
11
  }
16
12
  }
@@ -0,0 +1,12 @@
1
+ export default class NotNullViolation extends Error {
2
+ dream;
3
+ error;
4
+ constructor({ dream, error }) {
5
+ super();
6
+ this.dream = dream;
7
+ this.error = error;
8
+ }
9
+ get message() {
10
+ return this.error.message;
11
+ }
12
+ }
@@ -19,6 +19,7 @@ export default async function generateMigration({ migrationName, columnsWithType
19
19
  table: snakeify(pluralize(pascalizePath(fullyQualifiedParentName))),
20
20
  columnsWithTypes,
21
21
  primaryKeyType: primaryKeyType(),
22
+ stiChildClassName: pascalizePath(fullyQualifiedModelName),
22
23
  });
23
24
  }
24
25
  else if (fullyQualifiedModelName) {
@@ -5,13 +5,17 @@ import foreignKeyTypeFromPrimaryKey from '../db/foreignKeyTypeFromPrimaryKey.js'
5
5
  import snakeify from '../snakeify.js';
6
6
  const STI_TYPE_COLUMN_NAME = 'type';
7
7
  const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
8
- export default function generateMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', } = {}) {
8
+ export default function generateMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, } = {}) {
9
9
  const altering = createOrAlter === 'alter';
10
10
  let requireCitextExtension = false;
11
- const { columnDefs, columnDrops, indexDefs, indexDrops } = columnsWithTypes.reduce((acc, attribute) => {
11
+ const checkConstraints = [];
12
+ const { columnDefs, columnDrops, indexDefs, indexDrops } = columnsWithTypes.reduce((acc, attributeDeclaration) => {
12
13
  const { columnDefs, columnDrops, indexDefs, indexDrops } = acc;
13
- const [nonStandardAttributeName, attributeType, ...descriptors] = attribute.split(':');
14
- const optional = optionalFromDescriptors(descriptors);
14
+ const [nonStandardAttributeName, attributeType, ...descriptors] = attributeDeclaration.split(':');
15
+ const userWantsThisOptional = optionalFromDescriptors(descriptors);
16
+ // when creating a migration for an STI child, we don't want to include notNull;
17
+ // instead, we'll add a check constraint that uses the STI child class name
18
+ const optional = userWantsThisOptional || !!stiChildClassName;
15
19
  const sqlAttributeType = getAttributeType(attributeType, descriptors);
16
20
  let attributeName = snakeify(nonStandardAttributeName);
17
21
  if (attributeName === undefined)
@@ -20,6 +24,17 @@ export default function generateMigrationContent({ table, columnsWithTypes = [],
20
24
  return acc;
21
25
  if (attributeType === 'citext')
22
26
  requireCitextExtension = true;
27
+ if (stiChildClassName && !userWantsThisOptional) {
28
+ checkConstraints.push(`
29
+
30
+ await db.schema
31
+ .alterTable('${table}')
32
+ .addCheckConstraint(
33
+ '${table}_not_null_${attributeName}',
34
+ sql\`type != '${stiChildClassName}' OR ${attributeName} IS NOT NULL\`,
35
+ )
36
+ .execute()`);
37
+ }
23
38
  switch (attributeType) {
24
39
  case 'belongs_to':
25
40
  columnDefs.push(generateBelongsToStr(attributeName, {
@@ -96,7 +111,7 @@ ${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
96
111
  ".addColumn('created_at', 'timestamp', col => col.notNull())" +
97
112
  newlineDoubleIndent +
98
113
  ".addColumn('updated_at', 'timestamp', col => col.notNull())"}
99
- .execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(newlineDoubleIndent)}
114
+ .execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(newlineDoubleIndent)}${checkConstraints.join('')}
100
115
  }
101
116
 
102
117
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1,4 +1,10 @@
1
1
  import generateMigrationContent from './generateMigrationContent.js';
2
- export default function generateStiMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', } = {}) {
3
- return generateMigrationContent({ table, columnsWithTypes, primaryKeyType, createOrAlter: 'alter' });
2
+ export default function generateStiMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', stiChildClassName, }) {
3
+ return generateMigrationContent({
4
+ table,
5
+ columnsWithTypes,
6
+ primaryKeyType,
7
+ createOrAlter: 'alter',
8
+ stiChildClassName,
9
+ });
4
10
  }
@@ -21,7 +21,9 @@ export { default as Query } from './dream/Query.js';
21
21
  export { default as Encrypt } from './encrypt/index.js';
22
22
  export { default as NonLoadedAssociation } from './errors/associations/NonLoadedAssociation.js';
23
23
  export { default as CreateOrFindByFailedToCreateAndFind } from './errors/CreateOrFindByFailedToCreateAndFind.js';
24
+ export { default as CheckConstraintViolation } from './errors/db/CheckConstraintViolation.js';
24
25
  export { default as DataTypeColumnTypeMismatch } from './errors/db/DataTypeColumnTypeMismatch.js';
26
+ export { default as NotNullViolation } from './errors/db/NotNullViolation.js';
25
27
  export { default as GlobalNameNotSet } from './errors/dream-app/GlobalNameNotSet.js';
26
28
  export { default as RecordNotFound } from './errors/RecordNotFound.js';
27
29
  export { default as ValidationError } from './errors/ValidationError.js';
@@ -11,7 +11,8 @@ export function serializerForAssociatedClass(object, associationName, options) {
11
11
  return null;
12
12
  const dream = object;
13
13
  const association = dream['getAssociationMetadata'](associationName);
14
- const associatedClasses = association.modelCB();
15
- const associatedClass = Array.isArray(associatedClasses) ? associatedClasses[0] : associatedClasses;
14
+ const associatedClass = association.modelCB();
15
+ if (Array.isArray(associatedClass))
16
+ throw new Error('rendersOne flatten is incompatible with a polymorphic belongs-to association');
16
17
  return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey)[0] ?? null;
17
18
  }