@rvoh/dream 1.2.0 → 1.3.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 (212) hide show
  1. package/dist/cjs/src/Dream.js +99 -31
  2. package/dist/cjs/src/db/errors.js +5 -1
  3. package/dist/cjs/src/dream/QueryDriver/Kysely.js +46 -31
  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/DataTypeColumnTypeMismatch.js +19 -0
  7. package/dist/cjs/src/helpers/cli/generateMigration.js +1 -0
  8. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +20 -5
  9. package/dist/cjs/src/helpers/cli/generateStiMigrationContent.js +8 -2
  10. package/dist/cjs/src/helpers/sqlAttributes.js +7 -1
  11. package/dist/cjs/src/index.js +4 -2
  12. package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
  13. package/dist/esm/src/Dream.js +99 -31
  14. package/dist/esm/src/db/errors.js +5 -1
  15. package/dist/esm/src/dream/QueryDriver/Kysely.js +46 -31
  16. package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +14 -0
  17. package/dist/esm/src/dream-app/cache.js +12 -2
  18. package/dist/esm/src/errors/db/DataTypeColumnTypeMismatch.js +16 -0
  19. package/dist/esm/src/helpers/cli/generateMigration.js +1 -0
  20. package/dist/esm/src/helpers/cli/generateMigrationContent.js +20 -5
  21. package/dist/esm/src/helpers/cli/generateStiMigrationContent.js +8 -2
  22. package/dist/esm/src/helpers/sqlAttributes.js +7 -1
  23. package/dist/esm/src/index.js +1 -0
  24. package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
  25. package/dist/types/src/Dream.d.ts +77 -52
  26. package/dist/types/src/db/errors.d.ts +3 -1
  27. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +1 -0
  28. package/dist/types/src/dream/internal/printSerializerHierarchyLevel.d.ts +7 -0
  29. package/dist/types/src/errors/db/DataTypeColumnTypeMismatch.d.ts +10 -0
  30. package/dist/types/src/helpers/cli/generateMigrationContent.d.ts +2 -1
  31. package/dist/types/src/helpers/cli/generateStiMigrationContent.d.ts +2 -1
  32. package/dist/types/src/index.d.ts +1 -0
  33. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +1 -1
  34. package/dist/types/src/types/dream.d.ts +1 -1
  35. package/dist/types/src/types/dream.ts +2 -2
  36. package/docs/assets/navigation.js +1 -1
  37. package/docs/assets/search.js +1 -1
  38. package/docs/classes/Benchmark.html +2 -2
  39. package/docs/classes/CalendarDate.html +2 -2
  40. package/docs/classes/CliFileWriter.html +2 -2
  41. package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
  42. package/docs/classes/DataTypeColumnTypeMismatch.html +14 -0
  43. package/docs/classes/Decorators.html +19 -19
  44. package/docs/classes/Dream.html +209 -308
  45. package/docs/classes/DreamApp.html +4 -4
  46. package/docs/classes/DreamBin.html +2 -2
  47. package/docs/classes/DreamCLI.html +4 -4
  48. package/docs/classes/DreamImporter.html +2 -2
  49. package/docs/classes/DreamLogos.html +2 -2
  50. package/docs/classes/DreamMigrationHelpers.html +7 -7
  51. package/docs/classes/DreamSerializerBuilder.html +8 -8
  52. package/docs/classes/DreamTransaction.html +2 -2
  53. package/docs/classes/Encrypt.html +2 -2
  54. package/docs/classes/Env.html +2 -2
  55. package/docs/classes/GlobalNameNotSet.html +3 -3
  56. package/docs/classes/NonLoadedAssociation.html +3 -3
  57. package/docs/classes/ObjectSerializerBuilder.html +8 -8
  58. package/docs/classes/Query.html +60 -60
  59. package/docs/classes/Range.html +2 -2
  60. package/docs/classes/RecordNotFound.html +3 -3
  61. package/docs/classes/ValidationError.html +3 -3
  62. package/docs/functions/DreamSerializer.html +1 -1
  63. package/docs/functions/ObjectSerializer.html +1 -1
  64. package/docs/functions/ReplicaSafe.html +1 -1
  65. package/docs/functions/STI.html +1 -1
  66. package/docs/functions/SoftDelete.html +1 -1
  67. package/docs/functions/camelize.html +1 -1
  68. package/docs/functions/capitalize.html +1 -1
  69. package/docs/functions/cloneDeepSafe.html +1 -1
  70. package/docs/functions/closeAllDbConnections.html +1 -1
  71. package/docs/functions/compact.html +1 -1
  72. package/docs/functions/dreamDbConnections.html +1 -1
  73. package/docs/functions/dreamPath.html +1 -1
  74. package/docs/functions/expandStiClasses.html +1 -1
  75. package/docs/functions/generateDream.html +1 -1
  76. package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
  77. package/docs/functions/groupBy.html +1 -1
  78. package/docs/functions/hyphenize.html +1 -1
  79. package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
  80. package/docs/functions/inferSerializersFromDreamClassOrViewModelClass.html +1 -1
  81. package/docs/functions/intersection.html +1 -1
  82. package/docs/functions/isDreamSerializer.html +1 -1
  83. package/docs/functions/isEmpty.html +1 -1
  84. package/docs/functions/loadRepl.html +1 -1
  85. package/docs/functions/lookupClassByGlobalName.html +1 -1
  86. package/docs/functions/normalizeUnicode.html +1 -1
  87. package/docs/functions/pascalize.html +1 -1
  88. package/docs/functions/pgErrorType.html +1 -1
  89. package/docs/functions/range-1.html +1 -1
  90. package/docs/functions/relativeDreamPath.html +1 -1
  91. package/docs/functions/round.html +1 -1
  92. package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
  93. package/docs/functions/sharedPathPrefix.html +1 -1
  94. package/docs/functions/snakeify.html +1 -1
  95. package/docs/functions/sort.html +1 -1
  96. package/docs/functions/sortBy.html +1 -1
  97. package/docs/functions/sortObjectByKey.html +1 -1
  98. package/docs/functions/sortObjectByValue.html +1 -1
  99. package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
  100. package/docs/functions/uncapitalize.html +1 -1
  101. package/docs/functions/uniq.html +1 -1
  102. package/docs/functions/untypedDb.html +1 -1
  103. package/docs/functions/validateColumn.html +1 -1
  104. package/docs/functions/validateTable.html +1 -1
  105. package/docs/interfaces/BelongsToStatement.html +2 -2
  106. package/docs/interfaces/DecoratorContext.html +2 -2
  107. package/docs/interfaces/DreamAppInitOptions.html +2 -2
  108. package/docs/interfaces/DreamAppOpts.html +2 -2
  109. package/docs/interfaces/EncryptOptions.html +2 -2
  110. package/docs/interfaces/InternalAnyTypedSerializerRendersMany.html +2 -2
  111. package/docs/interfaces/InternalAnyTypedSerializerRendersOne.html +2 -2
  112. package/docs/interfaces/OpenapiDescription.html +2 -2
  113. package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
  114. package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
  115. package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
  116. package/docs/interfaces/SerializerRendererOpts.html +2 -2
  117. package/docs/modules.html +1 -0
  118. package/docs/types/Camelized.html +1 -1
  119. package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
  120. package/docs/types/DateTime.html +1 -1
  121. package/docs/types/DbConnectionType.html +1 -1
  122. package/docs/types/DbTypes.html +1 -1
  123. package/docs/types/DreamAppAllowedPackageManagersEnum.html +1 -1
  124. package/docs/types/DreamAssociationMetadata.html +1 -1
  125. package/docs/types/DreamAttributes.html +1 -1
  126. package/docs/types/DreamClassAssociationAndStatement.html +1 -1
  127. package/docs/types/DreamClassColumn.html +1 -1
  128. package/docs/types/DreamColumn.html +1 -1
  129. package/docs/types/DreamColumnNames.html +1 -1
  130. package/docs/types/DreamLogLevel.html +1 -1
  131. package/docs/types/DreamLogger.html +1 -1
  132. package/docs/types/DreamModelSerializerType.html +1 -1
  133. package/docs/types/DreamOrViewModelClassSerializerKey.html +1 -1
  134. package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
  135. package/docs/types/DreamParamSafeAttributes.html +1 -1
  136. package/docs/types/DreamParamSafeColumnNames.html +1 -1
  137. package/docs/types/DreamSerializable.html +1 -1
  138. package/docs/types/DreamSerializableArray.html +1 -1
  139. package/docs/types/DreamSerializerKey.html +1 -1
  140. package/docs/types/DreamSerializers.html +1 -1
  141. package/docs/types/DreamTableSchema.html +1 -1
  142. package/docs/types/DreamVirtualColumns.html +1 -1
  143. package/docs/types/EncryptAlgorithm.html +1 -1
  144. package/docs/types/HasManyStatement.html +1 -1
  145. package/docs/types/HasOneStatement.html +1 -1
  146. package/docs/types/Hyphenized.html +1 -1
  147. package/docs/types/IdType.html +1 -1
  148. package/docs/types/OpenapiAllTypes.html +1 -1
  149. package/docs/types/OpenapiFormats.html +1 -1
  150. package/docs/types/OpenapiNumberFormats.html +1 -1
  151. package/docs/types/OpenapiPrimitiveBaseTypes.html +1 -1
  152. package/docs/types/OpenapiPrimitiveTypes.html +1 -1
  153. package/docs/types/OpenapiSchemaArray.html +1 -1
  154. package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
  155. package/docs/types/OpenapiSchemaBase.html +1 -1
  156. package/docs/types/OpenapiSchemaBody.html +1 -1
  157. package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
  158. package/docs/types/OpenapiSchemaCommonFields.html +1 -1
  159. package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
  160. package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
  161. package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
  162. package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
  163. package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  164. package/docs/types/OpenapiSchemaInteger.html +1 -1
  165. package/docs/types/OpenapiSchemaNull.html +1 -1
  166. package/docs/types/OpenapiSchemaNumber.html +1 -1
  167. package/docs/types/OpenapiSchemaObject.html +1 -1
  168. package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
  169. package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
  170. package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
  171. package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  172. package/docs/types/OpenapiSchemaObjectBase.html +1 -1
  173. package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
  174. package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
  175. package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
  176. package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
  177. package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
  178. package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  179. package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  180. package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  181. package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  182. package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  183. package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  184. package/docs/types/OpenapiSchemaString.html +1 -1
  185. package/docs/types/OpenapiShorthandAllTypes.html +1 -1
  186. package/docs/types/OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  187. package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
  188. package/docs/types/OpenapiTypeField.html +1 -1
  189. package/docs/types/Pascalized.html +1 -1
  190. package/docs/types/PrimaryKeyType.html +1 -1
  191. package/docs/types/RoundingPrecision.html +1 -1
  192. package/docs/types/SerializerCasing.html +1 -1
  193. package/docs/types/SimpleObjectSerializerType.html +1 -1
  194. package/docs/types/Snakeified.html +1 -1
  195. package/docs/types/Timestamp.html +1 -1
  196. package/docs/types/UpdateableAssociationProperties.html +1 -1
  197. package/docs/types/UpdateableProperties.html +1 -1
  198. package/docs/types/ValidationType.html +1 -1
  199. package/docs/types/ViewModel.html +1 -1
  200. package/docs/types/ViewModelClass.html +1 -1
  201. package/docs/types/WhereStatementForDream.html +1 -1
  202. package/docs/types/WhereStatementForDreamClass.html +1 -1
  203. package/docs/variables/DateTime-1.html +1 -1
  204. package/docs/variables/DreamAppAllowedPackageManagersEnumValues.html +1 -1
  205. package/docs/variables/DreamConst.html +1 -1
  206. package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
  207. package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
  208. package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
  209. package/docs/variables/ops.html +1 -1
  210. package/docs/variables/primaryKeyTypes.html +1 -1
  211. package/package.json +2 -2
  212. package/CHANGELOG.md +0 -48
@@ -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 = [
@@ -7,8 +7,12 @@
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
12
  export const PG_ERRORS = {
11
- 23505: 'UNIQUE_CONSTRAINT_VIOLATION',
13
+ '23505': UNIQUE_CONSTRAINT_VIOLATION,
14
+ '22P02': INVALID_INPUT_SYNTAX,
15
+ '22007': INVALID_INPUT_SYNTAX,
12
16
  };
13
17
  function pgErrorFromCode(code) {
14
18
  return PG_ERRORS[code] || null;
@@ -3,6 +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
7
  import _db from '../../db/index.js';
7
8
  import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
8
9
  import PackageManager from '../../dream-app/helpers/PackageManager.js';
@@ -17,6 +18,7 @@ import MissingThroughAssociationSource from '../../errors/associations/MissingTh
17
18
  import ThroughAssociationConditionsIncompatibleWithThroughAssociationSource from '../../errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js';
18
19
  import CannotNegateSimilarityClause from '../../errors/CannotNegateSimilarityClause.js';
19
20
  import CannotPassUndefinedAsAValueToAWhereClause from '../../errors/CannotPassUndefinedAsAValueToAWhereClause.js';
21
+ import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
20
22
  import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
21
23
  import CalendarDate from '../../helpers/CalendarDate.js';
22
24
  import camelize from '../../helpers/camelize.js';
@@ -29,6 +31,7 @@ import _dropDb from '../../helpers/db/dropDb.js';
29
31
  import loadPgClient from '../../helpers/db/loadPgClient.js';
30
32
  import runMigration from '../../helpers/db/runMigration.js';
31
33
  import EnvInternal from '../../helpers/EnvInternal.js';
34
+ import groupBy from '../../helpers/groupBy.js';
32
35
  import isEmpty from '../../helpers/isEmpty.js';
33
36
  import namespaceColumn from '../../helpers/namespaceColumn.js';
34
37
  import normalizeUnicode from '../../helpers/normalizeUnicode.js';
@@ -413,19 +416,31 @@ export default class KyselyQueryDriver extends QueryDriverBase {
413
416
  static async saveDream(dream, txn = null) {
414
417
  const db = txn?.kyselyTransaction ?? _db('primary');
415
418
  const sqlifiedAttributes = sqlAttributes(dream);
416
- if (dream.isPersisted) {
417
- const query = db
418
- .updateTable(dream.table)
419
- .set(sqlifiedAttributes)
420
- .where(namespaceColumn(dream.primaryKey, dream.table), '=', dream.primaryKeyValue);
421
- return await executeDatabaseQuery(query.returning([...dream.columns()]), 'executeTakeFirstOrThrow');
419
+ try {
420
+ if (dream.isPersisted) {
421
+ const query = db
422
+ .updateTable(dream.table)
423
+ .set(sqlifiedAttributes)
424
+ .where(namespaceColumn(dream.primaryKey, dream.table), '=', dream.primaryKeyValue);
425
+ return await executeDatabaseQuery(query.returning([...dream.columns()]), 'executeTakeFirstOrThrow');
426
+ }
427
+ else {
428
+ const query = db
429
+ .insertInto(dream.table)
430
+ .values(sqlifiedAttributes)
431
+ .returning([...dream.columns()]);
432
+ return await executeDatabaseQuery(query, 'executeTakeFirstOrThrow');
433
+ }
422
434
  }
423
- else {
424
- const query = db
425
- .insertInto(dream.table)
426
- .values(sqlifiedAttributes)
427
- .returning([...dream.columns()]);
428
- return await executeDatabaseQuery(query, 'executeTakeFirstOrThrow');
435
+ catch (error) {
436
+ switch (pgErrorType(error)) {
437
+ case 'INVALID_INPUT_SYNTAX':
438
+ throw new DataTypeColumnTypeMismatch({
439
+ dream,
440
+ error: error instanceof Error ? error : new Error('database column type error'),
441
+ });
442
+ }
443
+ throw error;
429
444
  }
430
445
  }
431
446
  dbConnectionType(sqlCommandType) {
@@ -1188,7 +1203,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1188
1203
  const keys = Object.keys(preloadStatement);
1189
1204
  for (const key of keys) {
1190
1205
  const nestedDreams = await this.applyOnePreload(key, dream, this.applyablePreloadOnStatements(preloadOnStatements[key]));
1191
- if (nestedDreams) {
1206
+ if (nestedDreams.length) {
1192
1207
  await this.applyPreload(preloadStatement[key], preloadOnStatements[key], nestedDreams);
1193
1208
  }
1194
1209
  }
@@ -1683,18 +1698,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1683
1698
  *
1684
1699
  * Used to preload polymorphic belongs to associations
1685
1700
  */
1686
- async preloadPolymorphicBelongsTo(association, dreams) {
1687
- if (!association.polymorphic)
1688
- throw new Error(`Association ${association.as} points to an array of models but is not designated polymorphic`);
1689
- if (association.type !== 'BelongsTo')
1690
- 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.`);
1691
- const nestedDreamsForNextRoundOfPreloading = [];
1692
- for (const associatedModel of association.modelCB()) {
1693
- await this.preloadPolymorphicAssociationModel(dreams, association, associatedModel, nestedDreamsForNextRoundOfPreloading);
1694
- }
1695
- return nestedDreamsForNextRoundOfPreloading;
1701
+ async preloadPolymorphicBelongsTo(association, associatedModels, dreams) {
1702
+ return compact(await Promise.all(associatedModels.map(associatedModel => this.preloadPolymorphicAssociationModel(dreams, association, associatedModel)))).flat();
1696
1703
  }
1697
- async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass, nestedDreamsForNextRoundOfPreloading) {
1704
+ async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass) {
1698
1705
  const relevantAssociatedModels = dreams.filter((dream) => {
1699
1706
  const field = association.foreignKeyTypeField();
1700
1707
  return dream[field] === associatedDreamClass['stiBaseClassOrOwnClassName'] || dream[field] === null;
@@ -1713,7 +1720,6 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1713
1720
  [associatedDreamClass.primaryKey]: relevantAssociatedModels.map((dream) => dream[association.foreignKey()]),
1714
1721
  })
1715
1722
  .all();
1716
- loadedAssociations.forEach((loadedAssociation) => nestedDreamsForNextRoundOfPreloading.push(loadedAssociation));
1717
1723
  //////////////////////////////////////////////////////////////////////////////////////////////
1718
1724
  // Associate each loaded association with each dream based on primary key and foreign key type
1719
1725
  //////////////////////////////////////////////////////////////////////////////////////////////
@@ -1728,6 +1734,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1728
1734
  ///////////////////////////////////////////////////////////////////////////////////////////////////
1729
1735
  // end: Associate each loaded association with each dream based on primary key and foreign key type
1730
1736
  ///////////////////////////////////////////////////////////////////////////////////////////////////
1737
+ return loadedAssociations;
1731
1738
  }
1732
1739
  }
1733
1740
  /**
@@ -1766,12 +1773,18 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1766
1773
  *
1767
1774
  * Applies a preload statement
1768
1775
  */
1769
- async applyOnePreload(associationName, dreams, onStatement = {}) {
1776
+ async applyOnePreload(associationNameAndMaybeAlias, dreams, onStatement = {}) {
1770
1777
  if (!Array.isArray(dreams))
1771
1778
  dreams = [dreams];
1772
- const dream = dreams.find(dream => dream['getAssociationMetadata'](associationName));
1773
- if (!dream)
1774
- return;
1779
+ const { name: associationName } = associationStringToNameAndAlias(associationNameAndMaybeAlias);
1780
+ dreams = dreams.filter(dream => dream.hasAssociation(associationName));
1781
+ const groupedDreams = groupBy(dreams, dream => dream.sanitizedConstructorName);
1782
+ return (await Promise.all(Object.keys(groupedDreams).map(key => this._applyOnePreload(associationName, groupedDreams[key], onStatement)))).flat();
1783
+ }
1784
+ async _applyOnePreload(associationName, dreams, onStatement = {}) {
1785
+ if (!dreams.length)
1786
+ return [];
1787
+ const dream = dreams[0];
1775
1788
  const { name, alias: _alias } = associationStringToNameAndAlias(associationName);
1776
1789
  const alias = _alias || name;
1777
1790
  const association = dream['getAssociationMetadata'](name);
@@ -1779,8 +1792,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
1779
1792
  throw new UnexpectedUndefined();
1780
1793
  const dreamClass = dream.constructor;
1781
1794
  const dreamClassToHydrate = association.modelCB();
1782
- if ((association.polymorphic && association.type === 'BelongsTo') || Array.isArray(dreamClassToHydrate))
1783
- return this.preloadPolymorphicBelongsTo(association, dreams);
1795
+ if (Array.isArray(dreamClassToHydrate)) {
1796
+ const preloadedPolymorphicBelongsTos = await this.preloadPolymorphicBelongsTo(association, dreamClassToHydrate, dreams);
1797
+ return preloadedPolymorphicBelongsTos;
1798
+ }
1784
1799
  const dreamClassToHydrateColumns = [...dreamClassToHydrate.columns()];
1785
1800
  const columnsToPluck = dreamClassToHydrateColumns.map(column => this.namespaceColumn(column.toString(), alias));
1786
1801
  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,16 @@
1
+ export default class DataTypeColumnTypeMismatch 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 `\
11
+ Failed to save ${this.dream.sanitizedConstructorName}:
12
+
13
+ ${this.error.message}
14
+ `;
15
+ }
16
+ }
@@ -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
  }
@@ -37,6 +37,12 @@ export default function sqlAttributes(dream) {
37
37
  return result;
38
38
  }, {});
39
39
  }
40
+ /**
41
+ * Convert datetimes to UTC
42
+ */
40
43
  function valueToDatetime(val) {
41
- return typeof val === 'string' ? DateTime.fromISO(val, { zone: 'UTC' }) : val;
44
+ if (typeof val !== 'string')
45
+ return val;
46
+ const datetime = DateTime.fromISO(val, { zone: 'UTC' });
47
+ return datetime.isValid ? datetime : val;
42
48
  }
@@ -21,6 +21,7 @@ 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 DataTypeColumnTypeMismatch } from './errors/db/DataTypeColumnTypeMismatch.js';
24
25
  export { default as GlobalNameNotSet } from './errors/dream-app/GlobalNameNotSet.js';
25
26
  export { default as RecordNotFound } from './errors/RecordNotFound.js';
26
27
  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
  }