@rvoh/dream 0.40.4 → 0.40.6

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 (186) hide show
  1. package/dist/cjs/src/Dream.js +64 -12
  2. package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +46 -6
  3. package/dist/cjs/src/dream/DreamInstanceTransactionBuilder.js +2 -2
  4. package/dist/cjs/src/dream/LeftJoinLoadBuilder.js +3 -3
  5. package/dist/cjs/src/dream/LoadBuilder.js +3 -3
  6. package/dist/cjs/src/dream/internal/associations/createAssociation.js +1 -6
  7. package/dist/cjs/src/dream/internal/destroyDream.js +1 -1
  8. package/dist/cjs/src/dream/internal/findOrCreateBy.js +16 -0
  9. package/dist/cjs/src/dream/internal/runHooksFor.js +4 -4
  10. package/dist/cjs/src/dream/internal/saveDream.js +1 -1
  11. package/dist/cjs/src/dream/internal/updateOrCreateBy.js +18 -0
  12. package/dist/cjs/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +18 -0
  13. package/dist/esm/src/Dream.js +64 -12
  14. package/dist/esm/src/dream/DreamClassTransactionBuilder.js +46 -6
  15. package/dist/esm/src/dream/DreamInstanceTransactionBuilder.js +2 -2
  16. package/dist/esm/src/dream/LeftJoinLoadBuilder.js +3 -3
  17. package/dist/esm/src/dream/LoadBuilder.js +3 -3
  18. package/dist/esm/src/dream/internal/associations/createAssociation.js +1 -6
  19. package/dist/esm/src/dream/internal/destroyDream.js +1 -1
  20. package/dist/esm/src/dream/internal/findOrCreateBy.js +13 -0
  21. package/dist/esm/src/dream/internal/runHooksFor.js +4 -4
  22. package/dist/esm/src/dream/internal/saveDream.js +1 -1
  23. package/dist/esm/src/dream/internal/updateOrCreateBy.js +15 -0
  24. package/dist/esm/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +15 -0
  25. package/dist/types/src/Dream.d.ts +42 -9
  26. package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +72 -37
  27. package/dist/types/src/dream/DreamInstanceTransactionBuilder.d.ts +3 -3
  28. package/dist/types/src/dream/LeftJoinLoadBuilder.d.ts +2 -2
  29. package/dist/types/src/dream/LoadBuilder.d.ts +2 -2
  30. package/dist/types/src/dream/Query.d.ts +1 -1
  31. package/dist/types/src/dream/internal/findOrCreateBy.d.ts +4 -0
  32. package/dist/types/src/dream/internal/runHooksFor.d.ts +2 -2
  33. package/dist/types/src/dream/internal/updateOrCreateBy.d.ts +4 -0
  34. package/dist/types/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.d.ts +6 -0
  35. package/dist/types/src/types/dream.d.ts +7 -0
  36. package/dist/types/src/types/dream.ts +9 -0
  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/CreateOrFindByFailedToCreateAndFind.html +3 -3
  41. package/docs/classes/Decorators.html +19 -19
  42. package/docs/classes/Dream.html +205 -185
  43. package/docs/classes/DreamApp.html +4 -4
  44. package/docs/classes/DreamBin.html +2 -2
  45. package/docs/classes/DreamCLI.html +4 -4
  46. package/docs/classes/DreamImporter.html +2 -2
  47. package/docs/classes/DreamLogos.html +2 -2
  48. package/docs/classes/DreamMigrationHelpers.html +7 -7
  49. package/docs/classes/DreamSerializer.html +2 -2
  50. package/docs/classes/DreamTransaction.html +2 -2
  51. package/docs/classes/Encrypt.html +2 -2
  52. package/docs/classes/Env.html +2 -2
  53. package/docs/classes/GlobalNameNotSet.html +3 -3
  54. package/docs/classes/NonLoadedAssociation.html +3 -3
  55. package/docs/classes/Query.html +51 -51
  56. package/docs/classes/Range.html +2 -2
  57. package/docs/classes/RecordNotFound.html +3 -3
  58. package/docs/classes/ValidationError.html +3 -3
  59. package/docs/functions/Attribute.html +1 -1
  60. package/docs/functions/RendersMany.html +1 -1
  61. package/docs/functions/RendersOne.html +1 -1
  62. package/docs/functions/ReplicaSafe.html +1 -1
  63. package/docs/functions/STI.html +1 -1
  64. package/docs/functions/SoftDelete.html +1 -1
  65. package/docs/functions/camelize.html +1 -1
  66. package/docs/functions/capitalize.html +1 -1
  67. package/docs/functions/closeAllDbConnections.html +1 -1
  68. package/docs/functions/compact.html +1 -1
  69. package/docs/functions/dreamDbConnections.html +1 -1
  70. package/docs/functions/dreamPath.html +1 -1
  71. package/docs/functions/generateDream.html +1 -1
  72. package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
  73. package/docs/functions/hyphenize.html +1 -1
  74. package/docs/functions/inferSerializerFromDreamClassOrViewModelClass.html +1 -1
  75. package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
  76. package/docs/functions/isEmpty.html +1 -1
  77. package/docs/functions/loadRepl.html +1 -1
  78. package/docs/functions/lookupClassByGlobalName.html +1 -1
  79. package/docs/functions/pascalize.html +1 -1
  80. package/docs/functions/pgErrorType.html +1 -1
  81. package/docs/functions/range-1.html +1 -1
  82. package/docs/functions/relativeDreamPath.html +1 -1
  83. package/docs/functions/round.html +1 -1
  84. package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
  85. package/docs/functions/sharedPathPrefix.html +1 -1
  86. package/docs/functions/snakeify.html +1 -1
  87. package/docs/functions/sort.html +1 -1
  88. package/docs/functions/sortBy.html +1 -1
  89. package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
  90. package/docs/functions/uncapitalize.html +1 -1
  91. package/docs/functions/uniq.html +1 -1
  92. package/docs/functions/untypedDb.html +1 -1
  93. package/docs/functions/validateColumn.html +1 -1
  94. package/docs/functions/validateTable.html +1 -1
  95. package/docs/interfaces/AttributeStatement.html +2 -2
  96. package/docs/interfaces/DecoratorContext.html +2 -2
  97. package/docs/interfaces/DreamAppInitOptions.html +2 -2
  98. package/docs/interfaces/DreamAppOpts.html +2 -2
  99. package/docs/interfaces/DreamSerializerAssociationStatement.html +2 -2
  100. package/docs/interfaces/EncryptOptions.html +2 -2
  101. package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
  102. package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
  103. package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
  104. package/docs/types/Camelized.html +1 -1
  105. package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
  106. package/docs/types/DateTime.html +1 -1
  107. package/docs/types/DbConnectionType.html +1 -1
  108. package/docs/types/DreamAssociationMetadata.html +1 -1
  109. package/docs/types/DreamAttributes.html +1 -1
  110. package/docs/types/DreamClassColumn.html +1 -1
  111. package/docs/types/DreamColumn.html +1 -1
  112. package/docs/types/DreamColumnNames.html +1 -1
  113. package/docs/types/DreamLogLevel.html +1 -1
  114. package/docs/types/DreamLogger.html +1 -1
  115. package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
  116. package/docs/types/DreamParamSafeAttributes.html +1 -1
  117. package/docs/types/DreamParamSafeColumnNames.html +1 -1
  118. package/docs/types/DreamSerializerKey.html +1 -1
  119. package/docs/types/DreamSerializers.html +1 -1
  120. package/docs/types/DreamTableSchema.html +1 -1
  121. package/docs/types/DreamVirtualColumns.html +1 -1
  122. package/docs/types/EncryptAlgorithm.html +1 -1
  123. package/docs/types/Hyphenized.html +1 -1
  124. package/docs/types/IdType.html +1 -1
  125. package/docs/types/OpenapiAllTypes.html +1 -1
  126. package/docs/types/OpenapiFormats.html +1 -1
  127. package/docs/types/OpenapiNumberFormats.html +1 -1
  128. package/docs/types/OpenapiPrimitiveTypes.html +1 -1
  129. package/docs/types/OpenapiSchemaArray.html +1 -1
  130. package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
  131. package/docs/types/OpenapiSchemaBase.html +1 -1
  132. package/docs/types/OpenapiSchemaBody.html +1 -1
  133. package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
  134. package/docs/types/OpenapiSchemaCommonFields.html +1 -1
  135. package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
  136. package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
  137. package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
  138. package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
  139. package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  140. package/docs/types/OpenapiSchemaInteger.html +1 -1
  141. package/docs/types/OpenapiSchemaNull.html +1 -1
  142. package/docs/types/OpenapiSchemaNumber.html +1 -1
  143. package/docs/types/OpenapiSchemaObject.html +1 -1
  144. package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
  145. package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
  146. package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
  147. package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  148. package/docs/types/OpenapiSchemaObjectBase.html +1 -1
  149. package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
  150. package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
  151. package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
  152. package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
  153. package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
  154. package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  155. package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  156. package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  157. package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  158. package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  159. package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  160. package/docs/types/OpenapiSchemaString.html +1 -1
  161. package/docs/types/OpenapiShorthandAllTypes.html +1 -1
  162. package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
  163. package/docs/types/OpenapiTypeField.html +1 -1
  164. package/docs/types/Pascalized.html +1 -1
  165. package/docs/types/PrimaryKeyType.html +1 -1
  166. package/docs/types/RoundingPrecision.html +1 -1
  167. package/docs/types/SerializableClassOrSerializerCallback.html +1 -1
  168. package/docs/types/SerializableDreamClassOrViewModelClass.html +1 -1
  169. package/docs/types/SerializableDreamOrViewModel.html +1 -1
  170. package/docs/types/SerializableTypes.html +1 -1
  171. package/docs/types/Snakeified.html +1 -1
  172. package/docs/types/Timestamp.html +1 -1
  173. package/docs/types/UpdateableAssociationProperties.html +1 -1
  174. package/docs/types/UpdateableProperties.html +1 -1
  175. package/docs/types/ValidationType.html +1 -1
  176. package/docs/types/ViewModelSerializerKey.html +1 -1
  177. package/docs/types/WhereStatementForDream.html +1 -1
  178. package/docs/types/WhereStatementForDreamClass.html +1 -1
  179. package/docs/variables/DateTime-1.html +1 -1
  180. package/docs/variables/DreamConst.html +1 -1
  181. package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
  182. package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
  183. package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
  184. package/docs/variables/ops.html +1 -1
  185. package/docs/variables/primaryKeyTypes.html +1 -1
  186. package/package.json +1 -1
@@ -18,11 +18,13 @@ const destroyDream_js_1 = require("./dream/internal/destroyDream.js");
18
18
  const destroyOptions_js_1 = require("./dream/internal/destroyOptions.js");
19
19
  const ensureSTITypeFieldIsSet_js_1 = require("./dream/internal/ensureSTITypeFieldIsSet.js");
20
20
  const extractAssociationMetadataFromAssociationName_js_1 = require("./dream/internal/extractAssociationMetadataFromAssociationName.js");
21
+ const findOrCreateBy_js_1 = require("./dream/internal/findOrCreateBy.js");
21
22
  const reload_js_1 = require("./dream/internal/reload.js");
22
23
  const runValidations_js_1 = require("./dream/internal/runValidations.js");
23
24
  const saveDream_js_1 = require("./dream/internal/saveDream.js");
24
25
  const scopeHelpers_js_1 = require("./dream/internal/scopeHelpers.js");
25
26
  const undestroyDream_js_1 = require("./dream/internal/undestroyDream.js");
27
+ const updateOrCreateBy_js_1 = require("./dream/internal/updateOrCreateBy.js");
26
28
  const LeftJoinLoadBuilder_js_1 = require("./dream/LeftJoinLoadBuilder.js");
27
29
  const LoadBuilder_js_1 = require("./dream/LoadBuilder.js");
28
30
  const Query_js_1 = require("./dream/Query.js");
@@ -36,6 +38,7 @@ const NonLoadedAssociation_js_1 = require("./errors/associations/NonLoadedAssoci
36
38
  const CannotCallUndestroyOnANonSoftDeleteModel_js_1 = require("./errors/CannotCallUndestroyOnANonSoftDeleteModel.js");
37
39
  const ConstructorOnlyForInternalUse_js_1 = require("./errors/ConstructorOnlyForInternalUse.js");
38
40
  const CreateOrFindByFailedToCreateAndFind_js_1 = require("./errors/CreateOrFindByFailedToCreateAndFind.js");
41
+ const CreateOrUpdateByFailedToCreateAndUpdate_js_1 = require("./errors/CreateOrUpdateByFailedToCreateAndUpdate.js");
39
42
  const GlobalNameNotSet_js_1 = require("./errors/dream-app/GlobalNameNotSet.js");
40
43
  const DreamMissingRequiredOverride_js_1 = require("./errors/DreamMissingRequiredOverride.js");
41
44
  const MissingSerializersDefinition_js_1 = require("./errors/MissingSerializersDefinition.js");
@@ -656,11 +659,12 @@ class Dream {
656
659
  * ```
657
660
  *
658
661
  * @param attributes - attributes or belongs to associations you wish to set on this model before persisting
662
+ * @param opts.skipHooks - if true, will skip applying model hooks. Defaults to false
659
663
  * @returns A newly persisted dream instance
660
664
  */
661
- static async create(attributes) {
665
+ static async create(attributes, { skipHooks } = {}) {
662
666
  const dreamModel = this.new(attributes);
663
- await dreamModel.save();
667
+ await dreamModel.save(skipHooks ? { skipHooks } : undefined);
664
668
  return dreamModel;
665
669
  }
666
670
  /**
@@ -701,6 +705,62 @@ class Dream {
701
705
  throw err;
702
706
  }
703
707
  }
708
+ /**
709
+ * Attempt to update the model with the given attributes.
710
+ * If no record is found, then a new record is created.
711
+ * All lifecycle hooks are run for whichever action is taken.
712
+ *
713
+ * ```ts
714
+ * const user = await User.updateOrCreateBy({ email }, { with: { name: 'Alice' })
715
+ * ```
716
+ *
717
+ * @param attributes - The base attributes for finding which record to update, also used when creating
718
+ * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
719
+ * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false
720
+ * @returns A dream instance
721
+ */
722
+ static async updateOrCreateBy(attributes, extraOpts = {}) {
723
+ return await (0, updateOrCreateBy_js_1.default)(this, null, attributes, extraOpts);
724
+ }
725
+ /**
726
+ * Attempt to create the model with the given attributes.
727
+ * If creation fails due to uniqueness constraint, then find and update
728
+ * the existing model. All lifecycle hooks are run for whichever
729
+ * action is taken.
730
+ *
731
+ * This is useful in situations where we want to avoid
732
+ * a race condition creating duplicate records.
733
+ *
734
+ * IMPORTANT: A unique index/uniqueness constraint must exist on
735
+ * at least one of the provided attributes
736
+ *
737
+ * @param attributes - The base attributes for finding which record to update, also used when creating
738
+ * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
739
+ * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false
740
+ * @returns A dream instance
741
+ */
742
+ static async createOrUpdateBy(attributes, extraOpts = {}) {
743
+ const { skipHooks } = extraOpts;
744
+ try {
745
+ return await this.create({
746
+ ...attributes,
747
+ ...(extraOpts?.with || {}),
748
+ }, skipHooks ? { skipHooks } : undefined);
749
+ }
750
+ catch (err) {
751
+ if ((0, errors_js_1.pgErrorType)(err) === 'UNIQUE_CONSTRAINT_VIOLATION') {
752
+ const existingRecord = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
753
+ if (!existingRecord)
754
+ throw new CreateOrUpdateByFailedToCreateAndUpdate_js_1.default(this);
755
+ const { with: attrs } = extraOpts;
756
+ if (existingRecord) {
757
+ existingRecord.assignAttributes(attrs ?? {});
758
+ return await (0, saveDream_js_1.default)(existingRecord, null, skipHooks ? { skipHooks } : undefined);
759
+ }
760
+ }
761
+ throw err;
762
+ }
763
+ }
704
764
  /**
705
765
  * Returns a new query instance with the distinct query applied.
706
766
  * If no columnName is provided, then distinct will apply to the
@@ -852,15 +912,7 @@ class Dream {
852
912
  * @returns A dream instance
853
913
  */
854
914
  static async findOrCreateBy(attributes, extraOpts = {}) {
855
- const existingRecord = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
856
- if (existingRecord)
857
- return existingRecord;
858
- const dreamModel = this.new({
859
- ...attributes,
860
- ...(extraOpts?.createWith || {}),
861
- });
862
- await dreamModel.save();
863
- return dreamModel;
915
+ return await (0, findOrCreateBy_js_1.default)(this, null, attributes, extraOpts);
864
916
  }
865
917
  /**
866
918
  * Returns true if a record exists for the given
@@ -1254,7 +1306,7 @@ class Dream {
1254
1306
  * @returns A Query scoped to this model with the transaction applied
1255
1307
  */
1256
1308
  static txn(txn) {
1257
- return new DreamClassTransactionBuilder_js_1.default(this.prototype, txn);
1309
+ return new DreamClassTransactionBuilder_js_1.default(this, txn);
1258
1310
  }
1259
1311
  /**
1260
1312
  * Builds a new DreamTransaction instance, provides
@@ -1,19 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const findOrCreateBy_js_1 = require("./internal/findOrCreateBy.js");
3
4
  const saveDream_js_1 = require("./internal/saveDream.js");
5
+ const updateOrCreateBy_js_1 = require("./internal/updateOrCreateBy.js");
4
6
  const Query_js_1 = require("./Query.js");
5
7
  class DreamClassTransactionBuilder {
6
- dreamInstance;
8
+ dreamClass;
7
9
  dreamTransaction;
10
+ dreamInstance;
8
11
  /**
9
12
  * Constructs a new DreamClassTransactionBuilder.
10
13
  *
11
14
  * @param dreamInstance - The Dream instance to build the transaction for
12
15
  * @param txn - The DreamTransaction instance
13
16
  */
14
- constructor(dreamInstance, dreamTransaction) {
15
- this.dreamInstance = dreamInstance;
17
+ constructor(dreamClass, dreamTransaction) {
18
+ this.dreamClass = dreamClass;
16
19
  this.dreamTransaction = dreamTransaction;
20
+ this.dreamInstance = dreamClass.prototype;
17
21
  }
18
22
  /**
19
23
  * Retrieves an array containing all records corresponding to
@@ -122,9 +126,9 @@ class DreamClassTransactionBuilder {
122
126
  * @param attributes - Attributes or belongs to associations you wish to set on this model before persisting
123
127
  * @returns A newly persisted dream instance
124
128
  */
125
- async create(attributes) {
126
- const dream = this.dreamInstance.constructor.new(attributes);
127
- return (0, saveDream_js_1.default)(dream, this.dreamTransaction);
129
+ async create(attributes, { skipHooks } = {}) {
130
+ const dream = this.dreamClass.new(attributes);
131
+ return (0, saveDream_js_1.default)(dream, this.dreamTransaction, skipHooks ? { skipHooks } : undefined);
128
132
  }
129
133
  /**
130
134
  * Finds a record for the corresponding model with the
@@ -196,6 +200,42 @@ class DreamClassTransactionBuilder {
196
200
  async findOrFailBy(whereStatement) {
197
201
  return await this.queryInstance().findOrFailBy(whereStatement);
198
202
  }
203
+ /**
204
+ * Attempt to find the model with the given attributes.
205
+ * If no record is found, then a new record is created.
206
+ *
207
+ * ```ts
208
+ * await ApplicationModel.transaction(async txn => {
209
+ * await User.txn(txn).findOrCreateBy({ email }, { createWith: params })
210
+ * })
211
+ * ```
212
+ *
213
+ * @param attributes - The base attributes for finding, but also the attributes to use when creating
214
+ * @param extraOpts.createWith - additional attributes to persist when creating, but not used for finding
215
+ * @returns A dream instance
216
+ */
217
+ async findOrCreateBy(attributes, extraOpts = {}) {
218
+ return await (0, findOrCreateBy_js_1.default)(this.dreamClass, this.dreamTransaction, attributes, extraOpts);
219
+ }
220
+ /**
221
+ * Attempt to update the model with the given attributes.
222
+ * If no record is found, then a new record is created.
223
+ * All lifecycle hooks are run for whichever action is taken.
224
+ *
225
+ * ```ts
226
+ * await ApplicationModel.transaction(async txn => {
227
+ * await User.txn(txn).updateOrCreateBy({ email }, { with: { name: 'Alice' })
228
+ * })
229
+ * ```
230
+ *
231
+ * @param attributes - The base attributes for finding, but also the attributes to use when creating
232
+ * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
233
+ * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false
234
+ * @returns A dream instance
235
+ */
236
+ async updateOrCreateBy(attributes, extraOpts = {}) {
237
+ return await (0, updateOrCreateBy_js_1.default)(this.dreamClass, this.dreamTransaction, attributes, extraOpts);
238
+ }
199
239
  /**
200
240
  * Finds all records for the corresponding model in batches,
201
241
  * and then calls the provided callback for each found record.
@@ -26,9 +26,9 @@ class DreamInstanceTransactionBuilder {
26
26
  * @param dreamInstance - The Dream instance to build the transaction for
27
27
  * @param txn - The DreamTransaction instance
28
28
  */
29
- constructor(dreamInstance, txn) {
29
+ constructor(dreamInstance, dreamTransaction) {
30
30
  this.dreamInstance = dreamInstance;
31
- this.dreamTransaction = txn;
31
+ this.dreamTransaction = dreamTransaction;
32
32
  }
33
33
  /**
34
34
  * Loads the requested associations upon execution
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const UnexpectedUndefined_js_1 = require("../errors/UnexpectedUndefined.js");
4
4
  class LeftJoinLoadBuilder {
5
- dream;
6
5
  dreamTransaction;
6
+ dream;
7
7
  query;
8
8
  /**
9
9
  * An intermediate class on the way to executing a load
@@ -15,7 +15,8 @@ class LeftJoinLoadBuilder {
15
15
  * await user.load('settings').execute()
16
16
  * ```
17
17
  */
18
- constructor(dream, txn) {
18
+ constructor(dream, dreamTransaction) {
19
+ this.dreamTransaction = dreamTransaction;
19
20
  this.dream = dream['clone']();
20
21
  // Load queries start from the table corresponding to an instance
21
22
  // of a Dream. However, the Dream may have default scopes that would
@@ -24,7 +25,6 @@ class LeftJoinLoadBuilder {
24
25
  // to other associations (thus the use of `removeAllDefaultScopesExceptOnAssociations`
25
26
  // instead of `removeAllDefaultScopes`).
26
27
  this.query = this.dream.query()['removeAllDefaultScopesExceptOnAssociations']();
27
- this.dreamTransaction = txn;
28
28
  }
29
29
  passthrough(passthroughWhereStatement) {
30
30
  this.query = this.query.passthrough(passthroughWhereStatement);
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  class LoadBuilder {
4
- dream;
5
4
  dreamTransaction;
5
+ dream;
6
6
  query;
7
7
  /**
8
8
  * An intermediate class on the way to executing a load
@@ -14,7 +14,8 @@ class LoadBuilder {
14
14
  * await user.load('settings').execute()
15
15
  * ```
16
16
  */
17
- constructor(dream, txn) {
17
+ constructor(dream, dreamTransaction) {
18
+ this.dreamTransaction = dreamTransaction;
18
19
  this.dream = dream['clone']();
19
20
  // Load queries start from the table corresponding to an instance
20
21
  // of a Dream. However, the Dream may have default scopes that would
@@ -23,7 +24,6 @@ class LoadBuilder {
23
24
  // to other associations (thus the use of `removeAllDefaultScopesExceptOnAssociations`
24
25
  // instead of `removeAllDefaultScopes`).
25
26
  this.query = this.dream.query()['removeAllDefaultScopesExceptOnAssociations']();
26
- this.dreamTransaction = txn;
27
27
  }
28
28
  passthrough(passthroughWhereStatement) {
29
29
  this.query = this.query.passthrough(passthroughWhereStatement);
@@ -33,12 +33,7 @@ async function createAssociation(dream, txn = null, associationName, opts = {})
33
33
  if (hasAssociation.polymorphic) {
34
34
  modifiedOpts[hasAssociation.foreignKeyTypeField()] = dream['stiBaseClassOrOwnClassName'];
35
35
  }
36
- if (txn) {
37
- hasresult = await associationClass.txn(txn).create(modifiedOpts);
38
- }
39
- else {
40
- hasresult = await associationClass.create(modifiedOpts);
41
- }
36
+ hasresult = await associationClass.txn(txn).create(modifiedOpts);
42
37
  return hasresult;
43
38
  case 'BelongsTo':
44
39
  belongstoFn = async (txn) => {
@@ -40,7 +40,7 @@ async function destroyDreamWithTransaction(dream, txn, options) {
40
40
  }
41
41
  await maybeDestroyDream(dream, txn, reallyDestroy);
42
42
  if (!skipHooks) {
43
- await (0, runHooksFor_js_1.default)('afterDestroy', dream, true, null, txn || undefined);
43
+ await (0, runHooksFor_js_1.default)('afterDestroy', dream, true, null, txn);
44
44
  await (0, runHooksFor_js_1.default)('afterDestroyCommit', dream, true, null, txn);
45
45
  }
46
46
  if (shouldSoftDelete(dream, reallyDestroy)) {
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = findOrCreateBy;
4
+ async function findOrCreateBy(dreamClass, txn = null, attributes, extraOpts = {}) {
5
+ const existingRecord = await dreamClass
6
+ .txn(txn)
7
+ .findBy(dreamClass['extractAttributesFromUpdateableProperties'](attributes));
8
+ if (existingRecord)
9
+ return existingRecord;
10
+ const dreamModel = dreamClass.new({
11
+ ...attributes,
12
+ ...(extraOpts?.createWith || {}),
13
+ });
14
+ await dreamModel.txn(txn).save();
15
+ return dreamModel;
16
+ }
@@ -39,7 +39,7 @@ async function runHooksFor(key, dream, alreadyPersisted, beforeSaveChanges, txn)
39
39
  if (txn)
40
40
  txn.addCommitHook(hook, dream);
41
41
  else
42
- await runHook(hook, dream);
42
+ await runHook(hook, dream, null);
43
43
  }
44
44
  break;
45
45
  case 'afterSave':
@@ -54,7 +54,7 @@ async function runHooksFor(key, dream, alreadyPersisted, beforeSaveChanges, txn)
54
54
  if (txn)
55
55
  txn.addCommitHook(hook, dream);
56
56
  else
57
- await runHook(hook, dream);
57
+ await runHook(hook, dream, null);
58
58
  }
59
59
  break;
60
60
  case 'afterUpdate':
@@ -67,7 +67,7 @@ async function runHooksFor(key, dream, alreadyPersisted, beforeSaveChanges, txn)
67
67
  if (txn)
68
68
  txn.addCommitHook(hook, dream);
69
69
  else
70
- await runHook(hook, dream);
70
+ await runHook(hook, dream, null);
71
71
  }
72
72
  break;
73
73
  default:
@@ -83,7 +83,7 @@ async function runHooksFor(key, dream, alreadyPersisted, beforeSaveChanges, txn)
83
83
  if (txn)
84
84
  txn.addCommitHook(hook, dream);
85
85
  else
86
- await runHook(hook, dream);
86
+ await runHook(hook, dream, null);
87
87
  break;
88
88
  default:
89
89
  await runHook(hook, dream, txn);
@@ -9,7 +9,7 @@ const sqlAttributes_js_1 = require("../../helpers/sqlAttributes.js");
9
9
  const executeDatabaseQuery_js_1 = require("./executeDatabaseQuery.js");
10
10
  const runHooksFor_js_1 = require("./runHooksFor.js");
11
11
  async function saveDream(dream, txn = null, { skipHooks = false } = {}) {
12
- const db = txn?.kyselyTransaction || (0, index_js_1.default)('primary');
12
+ const db = txn?.kyselyTransaction ?? (0, index_js_1.default)('primary');
13
13
  const alreadyPersisted = dream.isPersisted;
14
14
  if (!skipHooks) {
15
15
  if (alreadyPersisted)
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = updateOrCreateBy;
4
+ const saveDream_js_1 = require("./saveDream.js");
5
+ async function updateOrCreateBy(dreamClass, txn = null, attributes, extraOpts = {}) {
6
+ const existingRecord = await dreamClass
7
+ .txn(txn)
8
+ .findBy(dreamClass['extractAttributesFromUpdateableProperties'](attributes));
9
+ const { with: attrs, skipHooks } = extraOpts;
10
+ if (existingRecord) {
11
+ existingRecord.assignAttributes(attrs ?? {});
12
+ return await (0, saveDream_js_1.default)(existingRecord, txn, skipHooks ? { skipHooks } : undefined);
13
+ }
14
+ return await dreamClass.txn(txn).create({
15
+ ...attributes,
16
+ ...(extraOpts?.with || {}),
17
+ }, skipHooks ? { skipHooks } : undefined);
18
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class CreateOrUpdateByFailedToCreateAndUpdate extends Error {
4
+ dreamClass;
5
+ constructor(dreamClass) {
6
+ super();
7
+ this.dreamClass = dreamClass;
8
+ }
9
+ get message() {
10
+ return `
11
+ Failed to create instance of ${this.dreamClass.sanitizedName} and no matching model exists to update.
12
+
13
+ The likely cause is that one of the \`with\` fields violates
14
+ a uniqueness constraint.
15
+ `;
16
+ }
17
+ }
18
+ exports.default = CreateOrUpdateByFailedToCreateAndUpdate;
@@ -16,11 +16,13 @@ import destroyDream from './dream/internal/destroyDream.js';
16
16
  import { destroyOptions, reallyDestroyOptions, undestroyOptions, } from './dream/internal/destroyOptions.js';
17
17
  import ensureSTITypeFieldIsSet from './dream/internal/ensureSTITypeFieldIsSet.js';
18
18
  import extractAssociationMetadataFromAssociationName from './dream/internal/extractAssociationMetadataFromAssociationName.js';
19
+ import findOrCreateBy from './dream/internal/findOrCreateBy.js';
19
20
  import reload from './dream/internal/reload.js';
20
21
  import runValidations from './dream/internal/runValidations.js';
21
22
  import saveDream from './dream/internal/saveDream.js';
22
23
  import { DEFAULT_BYPASS_ALL_DEFAULT_SCOPES, DEFAULT_DEFAULT_SCOPES_TO_BYPASS, DEFAULT_SKIP_HOOKS, } from './dream/internal/scopeHelpers.js';
23
24
  import undestroyDream from './dream/internal/undestroyDream.js';
25
+ import updateOrCreateBy from './dream/internal/updateOrCreateBy.js';
24
26
  import LeftJoinLoadBuilder from './dream/LeftJoinLoadBuilder.js';
25
27
  import LoadBuilder from './dream/LoadBuilder.js';
26
28
  import Query from './dream/Query.js';
@@ -34,6 +36,7 @@ import NonLoadedAssociation from './errors/associations/NonLoadedAssociation.js'
34
36
  import CannotCallUndestroyOnANonSoftDeleteModel from './errors/CannotCallUndestroyOnANonSoftDeleteModel.js';
35
37
  import ConstructorOnlyForInternalUse from './errors/ConstructorOnlyForInternalUse.js';
36
38
  import CreateOrFindByFailedToCreateAndFind from './errors/CreateOrFindByFailedToCreateAndFind.js';
39
+ import CreateOrUpdateByFailedToCreateAndUpdate from './errors/CreateOrUpdateByFailedToCreateAndUpdate.js';
37
40
  import GlobalNameNotSet from './errors/dream-app/GlobalNameNotSet.js';
38
41
  import DreamMissingRequiredOverride from './errors/DreamMissingRequiredOverride.js';
39
42
  import MissingSerializer from './errors/MissingSerializersDefinition.js';
@@ -654,11 +657,12 @@ export default class Dream {
654
657
  * ```
655
658
  *
656
659
  * @param attributes - attributes or belongs to associations you wish to set on this model before persisting
660
+ * @param opts.skipHooks - if true, will skip applying model hooks. Defaults to false
657
661
  * @returns A newly persisted dream instance
658
662
  */
659
- static async create(attributes) {
663
+ static async create(attributes, { skipHooks } = {}) {
660
664
  const dreamModel = this.new(attributes);
661
- await dreamModel.save();
665
+ await dreamModel.save(skipHooks ? { skipHooks } : undefined);
662
666
  return dreamModel;
663
667
  }
664
668
  /**
@@ -699,6 +703,62 @@ export default class Dream {
699
703
  throw err;
700
704
  }
701
705
  }
706
+ /**
707
+ * Attempt to update the model with the given attributes.
708
+ * If no record is found, then a new record is created.
709
+ * All lifecycle hooks are run for whichever action is taken.
710
+ *
711
+ * ```ts
712
+ * const user = await User.updateOrCreateBy({ email }, { with: { name: 'Alice' })
713
+ * ```
714
+ *
715
+ * @param attributes - The base attributes for finding which record to update, also used when creating
716
+ * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
717
+ * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false
718
+ * @returns A dream instance
719
+ */
720
+ static async updateOrCreateBy(attributes, extraOpts = {}) {
721
+ return await updateOrCreateBy(this, null, attributes, extraOpts);
722
+ }
723
+ /**
724
+ * Attempt to create the model with the given attributes.
725
+ * If creation fails due to uniqueness constraint, then find and update
726
+ * the existing model. All lifecycle hooks are run for whichever
727
+ * action is taken.
728
+ *
729
+ * This is useful in situations where we want to avoid
730
+ * a race condition creating duplicate records.
731
+ *
732
+ * IMPORTANT: A unique index/uniqueness constraint must exist on
733
+ * at least one of the provided attributes
734
+ *
735
+ * @param attributes - The base attributes for finding which record to update, also used when creating
736
+ * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
737
+ * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false
738
+ * @returns A dream instance
739
+ */
740
+ static async createOrUpdateBy(attributes, extraOpts = {}) {
741
+ const { skipHooks } = extraOpts;
742
+ try {
743
+ return await this.create({
744
+ ...attributes,
745
+ ...(extraOpts?.with || {}),
746
+ }, skipHooks ? { skipHooks } : undefined);
747
+ }
748
+ catch (err) {
749
+ if (pgErrorType(err) === 'UNIQUE_CONSTRAINT_VIOLATION') {
750
+ const existingRecord = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
751
+ if (!existingRecord)
752
+ throw new CreateOrUpdateByFailedToCreateAndUpdate(this);
753
+ const { with: attrs } = extraOpts;
754
+ if (existingRecord) {
755
+ existingRecord.assignAttributes(attrs ?? {});
756
+ return await saveDream(existingRecord, null, skipHooks ? { skipHooks } : undefined);
757
+ }
758
+ }
759
+ throw err;
760
+ }
761
+ }
702
762
  /**
703
763
  * Returns a new query instance with the distinct query applied.
704
764
  * If no columnName is provided, then distinct will apply to the
@@ -850,15 +910,7 @@ export default class Dream {
850
910
  * @returns A dream instance
851
911
  */
852
912
  static async findOrCreateBy(attributes, extraOpts = {}) {
853
- const existingRecord = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
854
- if (existingRecord)
855
- return existingRecord;
856
- const dreamModel = this.new({
857
- ...attributes,
858
- ...(extraOpts?.createWith || {}),
859
- });
860
- await dreamModel.save();
861
- return dreamModel;
913
+ return await findOrCreateBy(this, null, attributes, extraOpts);
862
914
  }
863
915
  /**
864
916
  * Returns true if a record exists for the given
@@ -1252,7 +1304,7 @@ export default class Dream {
1252
1304
  * @returns A Query scoped to this model with the transaction applied
1253
1305
  */
1254
1306
  static txn(txn) {
1255
- return new DreamClassTransactionBuilder(this.prototype, txn);
1307
+ return new DreamClassTransactionBuilder(this, txn);
1256
1308
  }
1257
1309
  /**
1258
1310
  * Builds a new DreamTransaction instance, provides
@@ -1,17 +1,21 @@
1
+ import findOrCreateBy from './internal/findOrCreateBy.js';
1
2
  import saveDream from './internal/saveDream.js';
3
+ import updateOrCreateBy from './internal/updateOrCreateBy.js';
2
4
  import Query from './Query.js';
3
5
  export default class DreamClassTransactionBuilder {
4
- dreamInstance;
6
+ dreamClass;
5
7
  dreamTransaction;
8
+ dreamInstance;
6
9
  /**
7
10
  * Constructs a new DreamClassTransactionBuilder.
8
11
  *
9
12
  * @param dreamInstance - The Dream instance to build the transaction for
10
13
  * @param txn - The DreamTransaction instance
11
14
  */
12
- constructor(dreamInstance, dreamTransaction) {
13
- this.dreamInstance = dreamInstance;
15
+ constructor(dreamClass, dreamTransaction) {
16
+ this.dreamClass = dreamClass;
14
17
  this.dreamTransaction = dreamTransaction;
18
+ this.dreamInstance = dreamClass.prototype;
15
19
  }
16
20
  /**
17
21
  * Retrieves an array containing all records corresponding to
@@ -120,9 +124,9 @@ export default class DreamClassTransactionBuilder {
120
124
  * @param attributes - Attributes or belongs to associations you wish to set on this model before persisting
121
125
  * @returns A newly persisted dream instance
122
126
  */
123
- async create(attributes) {
124
- const dream = this.dreamInstance.constructor.new(attributes);
125
- return saveDream(dream, this.dreamTransaction);
127
+ async create(attributes, { skipHooks } = {}) {
128
+ const dream = this.dreamClass.new(attributes);
129
+ return saveDream(dream, this.dreamTransaction, skipHooks ? { skipHooks } : undefined);
126
130
  }
127
131
  /**
128
132
  * Finds a record for the corresponding model with the
@@ -194,6 +198,42 @@ export default class DreamClassTransactionBuilder {
194
198
  async findOrFailBy(whereStatement) {
195
199
  return await this.queryInstance().findOrFailBy(whereStatement);
196
200
  }
201
+ /**
202
+ * Attempt to find the model with the given attributes.
203
+ * If no record is found, then a new record is created.
204
+ *
205
+ * ```ts
206
+ * await ApplicationModel.transaction(async txn => {
207
+ * await User.txn(txn).findOrCreateBy({ email }, { createWith: params })
208
+ * })
209
+ * ```
210
+ *
211
+ * @param attributes - The base attributes for finding, but also the attributes to use when creating
212
+ * @param extraOpts.createWith - additional attributes to persist when creating, but not used for finding
213
+ * @returns A dream instance
214
+ */
215
+ async findOrCreateBy(attributes, extraOpts = {}) {
216
+ return await findOrCreateBy(this.dreamClass, this.dreamTransaction, attributes, extraOpts);
217
+ }
218
+ /**
219
+ * Attempt to update the model with the given attributes.
220
+ * If no record is found, then a new record is created.
221
+ * All lifecycle hooks are run for whichever action is taken.
222
+ *
223
+ * ```ts
224
+ * await ApplicationModel.transaction(async txn => {
225
+ * await User.txn(txn).updateOrCreateBy({ email }, { with: { name: 'Alice' })
226
+ * })
227
+ * ```
228
+ *
229
+ * @param attributes - The base attributes for finding, but also the attributes to use when creating
230
+ * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
231
+ * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false
232
+ * @returns A dream instance
233
+ */
234
+ async updateOrCreateBy(attributes, extraOpts = {}) {
235
+ return await updateOrCreateBy(this.dreamClass, this.dreamTransaction, attributes, extraOpts);
236
+ }
197
237
  /**
198
238
  * Finds all records for the corresponding model in batches,
199
239
  * and then calls the provided callback for each found record.
@@ -24,9 +24,9 @@ export default class DreamInstanceTransactionBuilder {
24
24
  * @param dreamInstance - The Dream instance to build the transaction for
25
25
  * @param txn - The DreamTransaction instance
26
26
  */
27
- constructor(dreamInstance, txn) {
27
+ constructor(dreamInstance, dreamTransaction) {
28
28
  this.dreamInstance = dreamInstance;
29
- this.dreamTransaction = txn;
29
+ this.dreamTransaction = dreamTransaction;
30
30
  }
31
31
  /**
32
32
  * Loads the requested associations upon execution
@@ -1,7 +1,7 @@
1
1
  import UnexpectedUndefined from '../errors/UnexpectedUndefined.js';
2
2
  export default class LeftJoinLoadBuilder {
3
- dream;
4
3
  dreamTransaction;
4
+ dream;
5
5
  query;
6
6
  /**
7
7
  * An intermediate class on the way to executing a load
@@ -13,7 +13,8 @@ export default class LeftJoinLoadBuilder {
13
13
  * await user.load('settings').execute()
14
14
  * ```
15
15
  */
16
- constructor(dream, txn) {
16
+ constructor(dream, dreamTransaction) {
17
+ this.dreamTransaction = dreamTransaction;
17
18
  this.dream = dream['clone']();
18
19
  // Load queries start from the table corresponding to an instance
19
20
  // of a Dream. However, the Dream may have default scopes that would
@@ -22,7 +23,6 @@ export default class LeftJoinLoadBuilder {
22
23
  // to other associations (thus the use of `removeAllDefaultScopesExceptOnAssociations`
23
24
  // instead of `removeAllDefaultScopes`).
24
25
  this.query = this.dream.query()['removeAllDefaultScopesExceptOnAssociations']();
25
- this.dreamTransaction = txn;
26
26
  }
27
27
  passthrough(passthroughWhereStatement) {
28
28
  this.query = this.query.passthrough(passthroughWhereStatement);