@rvoh/dream 1.10.0 → 1.11.0-beta.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 (202) hide show
  1. package/dist/cjs/src/Dream.js +43 -42
  2. package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +0 -32
  3. package/dist/cjs/src/dream/DreamInstanceTransactionBuilder.js +23 -0
  4. package/dist/cjs/src/dream/Query.js +75 -4
  5. package/dist/cjs/src/helpers/cli/SchemaBuilder.js +10 -12
  6. package/dist/esm/src/Dream.js +43 -42
  7. package/dist/esm/src/dream/DreamClassTransactionBuilder.js +0 -32
  8. package/dist/esm/src/dream/DreamInstanceTransactionBuilder.js +23 -0
  9. package/dist/esm/src/dream/Query.js +75 -4
  10. package/dist/esm/src/helpers/cli/SchemaBuilder.js +10 -12
  11. package/dist/types/src/Dream.d.ts +115 -22
  12. package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +1 -31
  13. package/dist/types/src/dream/DreamInstanceTransactionBuilder.d.ts +113 -7
  14. package/dist/types/src/dream/Query.d.ts +32 -1
  15. package/dist/types/src/helpers/cli/SchemaBuilder.d.ts +1 -1
  16. package/dist/types/src/types/associations/shared.d.ts +1 -0
  17. package/dist/types/src/types/associations/shared.ts +8 -0
  18. package/dist/types/src/types/dream.d.ts +11 -3
  19. package/dist/types/src/types/dream.ts +60 -3
  20. package/dist/types/src/types/query.d.ts +20 -0
  21. package/dist/types/src/types/query.ts +24 -0
  22. package/dist/types/src/types/variadic.d.ts +1 -2
  23. package/dist/types/src/types/variadic.ts +2 -18
  24. package/docs/assets/search.js +1 -1
  25. package/docs/classes/Benchmark.html +2 -2
  26. package/docs/classes/CalendarDate.html +2 -2
  27. package/docs/classes/CheckConstraintViolation.html +3 -3
  28. package/docs/classes/CliFileWriter.html +2 -2
  29. package/docs/classes/ColumnOverflow.html +3 -3
  30. package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
  31. package/docs/classes/DataIncompatibleWithDatabaseField.html +3 -3
  32. package/docs/classes/DataTypeColumnTypeMismatch.html +3 -3
  33. package/docs/classes/Decorators.html +19 -19
  34. package/docs/classes/Dream.html +244 -227
  35. package/docs/classes/DreamApp.html +4 -4
  36. package/docs/classes/DreamBin.html +2 -2
  37. package/docs/classes/DreamCLI.html +4 -4
  38. package/docs/classes/DreamImporter.html +2 -2
  39. package/docs/classes/DreamLogos.html +2 -2
  40. package/docs/classes/DreamMigrationHelpers.html +9 -9
  41. package/docs/classes/DreamSerializerBuilder.html +8 -8
  42. package/docs/classes/DreamTransaction.html +2 -2
  43. package/docs/classes/Encrypt.html +2 -2
  44. package/docs/classes/Env.html +2 -2
  45. package/docs/classes/GlobalNameNotSet.html +3 -3
  46. package/docs/classes/KyselyQueryDriver.html +30 -30
  47. package/docs/classes/MissingSerializersDefinition.html +3 -3
  48. package/docs/classes/NonLoadedAssociation.html +3 -3
  49. package/docs/classes/NotNullViolation.html +3 -3
  50. package/docs/classes/ObjectSerializerBuilder.html +8 -8
  51. package/docs/classes/PostgresQueryDriver.html +31 -31
  52. package/docs/classes/Query.html +75 -65
  53. package/docs/classes/QueryDriverBase.html +29 -29
  54. package/docs/classes/Range.html +2 -2
  55. package/docs/classes/RecordNotFound.html +3 -3
  56. package/docs/classes/ValidationError.html +3 -3
  57. package/docs/functions/DreamSerializer.html +1 -1
  58. package/docs/functions/ObjectSerializer.html +1 -1
  59. package/docs/functions/ReplicaSafe.html +1 -1
  60. package/docs/functions/STI.html +1 -1
  61. package/docs/functions/SoftDelete.html +1 -1
  62. package/docs/functions/camelize.html +1 -1
  63. package/docs/functions/capitalize.html +1 -1
  64. package/docs/functions/cloneDeepSafe.html +1 -1
  65. package/docs/functions/closeAllDbConnections.html +1 -1
  66. package/docs/functions/compact.html +1 -1
  67. package/docs/functions/dreamDbConnections.html +1 -1
  68. package/docs/functions/dreamPath.html +1 -1
  69. package/docs/functions/expandStiClasses.html +1 -1
  70. package/docs/functions/generateDream.html +1 -1
  71. package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
  72. package/docs/functions/groupBy.html +1 -1
  73. package/docs/functions/hyphenize.html +1 -1
  74. package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
  75. package/docs/functions/inferSerializersFromDreamClassOrViewModelClass.html +1 -1
  76. package/docs/functions/intersection.html +1 -1
  77. package/docs/functions/isDreamSerializer.html +1 -1
  78. package/docs/functions/isEmpty.html +1 -1
  79. package/docs/functions/loadRepl.html +1 -1
  80. package/docs/functions/lookupClassByGlobalName.html +1 -1
  81. package/docs/functions/normalizeUnicode.html +1 -1
  82. package/docs/functions/pascalize.html +1 -1
  83. package/docs/functions/percent.html +1 -1
  84. package/docs/functions/pgErrorType.html +1 -1
  85. package/docs/functions/range-1.html +1 -1
  86. package/docs/functions/relativeDreamPath.html +1 -1
  87. package/docs/functions/round.html +1 -1
  88. package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
  89. package/docs/functions/sharedPathPrefix.html +1 -1
  90. package/docs/functions/snakeify.html +1 -1
  91. package/docs/functions/sort.html +1 -1
  92. package/docs/functions/sortBy.html +1 -1
  93. package/docs/functions/sortObjectByKey.html +1 -1
  94. package/docs/functions/sortObjectByValue.html +1 -1
  95. package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
  96. package/docs/functions/uncapitalize.html +1 -1
  97. package/docs/functions/uniq.html +1 -1
  98. package/docs/functions/untypedDb.html +1 -1
  99. package/docs/functions/validateColumn.html +1 -1
  100. package/docs/functions/validateTable.html +1 -1
  101. package/docs/interfaces/BelongsToStatement.html +2 -2
  102. package/docs/interfaces/DecoratorContext.html +2 -2
  103. package/docs/interfaces/DreamAppInitOptions.html +2 -2
  104. package/docs/interfaces/DreamAppOpts.html +2 -2
  105. package/docs/interfaces/EncryptOptions.html +2 -2
  106. package/docs/interfaces/InternalAnyTypedSerializerRendersMany.html +2 -2
  107. package/docs/interfaces/InternalAnyTypedSerializerRendersOne.html +2 -2
  108. package/docs/interfaces/OpenapiDescription.html +2 -2
  109. package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
  110. package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
  111. package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
  112. package/docs/interfaces/SerializerRendererOpts.html +2 -2
  113. package/docs/types/Camelized.html +1 -1
  114. package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
  115. package/docs/types/DateTime.html +1 -1
  116. package/docs/types/DbConnectionType.html +1 -1
  117. package/docs/types/DbTypes.html +1 -1
  118. package/docs/types/DreamAppAllowedPackageManagersEnum.html +1 -1
  119. package/docs/types/DreamAssociationMetadata.html +1 -1
  120. package/docs/types/DreamAttributes.html +1 -1
  121. package/docs/types/DreamClassAssociationAndStatement.html +1 -1
  122. package/docs/types/DreamClassColumn.html +1 -1
  123. package/docs/types/DreamColumn.html +1 -1
  124. package/docs/types/DreamColumnNames.html +1 -1
  125. package/docs/types/DreamLogLevel.html +1 -1
  126. package/docs/types/DreamLogger.html +1 -1
  127. package/docs/types/DreamModelSerializerType.html +1 -1
  128. package/docs/types/DreamOrViewModelClassSerializerKey.html +1 -1
  129. package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
  130. package/docs/types/DreamParamSafeAttributes.html +1 -1
  131. package/docs/types/DreamParamSafeColumnNames.html +1 -1
  132. package/docs/types/DreamSerializable.html +1 -1
  133. package/docs/types/DreamSerializableArray.html +1 -1
  134. package/docs/types/DreamSerializerKey.html +1 -1
  135. package/docs/types/DreamSerializers.html +1 -1
  136. package/docs/types/DreamVirtualColumns.html +1 -1
  137. package/docs/types/EncryptAlgorithm.html +1 -1
  138. package/docs/types/HasManyStatement.html +1 -1
  139. package/docs/types/HasOneStatement.html +1 -1
  140. package/docs/types/Hyphenized.html +1 -1
  141. package/docs/types/OpenapiAllTypes.html +1 -1
  142. package/docs/types/OpenapiFormats.html +1 -1
  143. package/docs/types/OpenapiNumberFormats.html +1 -1
  144. package/docs/types/OpenapiPrimitiveBaseTypes.html +1 -1
  145. package/docs/types/OpenapiPrimitiveTypes.html +1 -1
  146. package/docs/types/OpenapiSchemaArray.html +1 -1
  147. package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
  148. package/docs/types/OpenapiSchemaBase.html +1 -1
  149. package/docs/types/OpenapiSchemaBody.html +1 -1
  150. package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
  151. package/docs/types/OpenapiSchemaCommonFields.html +1 -1
  152. package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
  153. package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
  154. package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
  155. package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
  156. package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  157. package/docs/types/OpenapiSchemaInteger.html +1 -1
  158. package/docs/types/OpenapiSchemaNull.html +1 -1
  159. package/docs/types/OpenapiSchemaNumber.html +1 -1
  160. package/docs/types/OpenapiSchemaObject.html +1 -1
  161. package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
  162. package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
  163. package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
  164. package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  165. package/docs/types/OpenapiSchemaObjectBase.html +1 -1
  166. package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
  167. package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
  168. package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
  169. package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
  170. package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
  171. package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  172. package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  173. package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  174. package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  175. package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  176. package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  177. package/docs/types/OpenapiSchemaString.html +1 -1
  178. package/docs/types/OpenapiShorthandAllTypes.html +1 -1
  179. package/docs/types/OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  180. package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
  181. package/docs/types/OpenapiTypeField.html +1 -1
  182. package/docs/types/Pascalized.html +1 -1
  183. package/docs/types/RoundingPrecision.html +1 -1
  184. package/docs/types/SerializerCasing.html +1 -1
  185. package/docs/types/SimpleObjectSerializerType.html +1 -1
  186. package/docs/types/Snakeified.html +1 -1
  187. package/docs/types/UpdateableAssociationProperties.html +1 -1
  188. package/docs/types/UpdateableProperties.html +1 -1
  189. package/docs/types/ValidationType.html +1 -1
  190. package/docs/types/ViewModel.html +1 -1
  191. package/docs/types/ViewModelClass.html +1 -1
  192. package/docs/types/WhereStatementForDream.html +1 -1
  193. package/docs/types/WhereStatementForDreamClass.html +1 -1
  194. package/docs/variables/DateTime-1.html +1 -1
  195. package/docs/variables/DreamAppAllowedPackageManagersEnumValues.html +1 -1
  196. package/docs/variables/DreamConst.html +1 -1
  197. package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
  198. package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
  199. package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
  200. package/docs/variables/ops.html +1 -1
  201. package/docs/variables/primaryKeyTypes.html +1 -1
  202. package/package.json +3 -3
@@ -909,6 +909,37 @@ class Dream {
909
909
  static async paginate(opts) {
910
910
  return await this.query().paginate(opts);
911
911
  }
912
+ /**
913
+ * Fast paginates the results of your query using cursor-based pagination,
914
+ * accepting a pageSize and cursor argument. This method
915
+ * provides better performance for large datasets by using cursor-based
916
+ * pagination instead of offset-based pagination.
917
+ *
918
+ * ```ts
919
+ * // First page
920
+ * const firstPage = await User.order('email').fastPaginate({ pageSize: 100 })
921
+ * firstPage.results
922
+ * // [ { User{id: 1}, User{id: 2}, ...}]
923
+ *
924
+ * firstPage.cursor
925
+ * // "100" (or null if no more pages)
926
+ *
927
+ * // Next page using cursor from previous result
928
+ * const nextPage = await User.order('email').fastPaginate({
929
+ * pageSize: 100,
930
+ * cursor: firstPage.cursor
931
+ * })
932
+ * ```
933
+ *
934
+ * @param opts - Fast pagination options
935
+ * @param opts.pageSize - the number of results per page (optional)
936
+ * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
937
+ * @returns results.cursor - identifier for the next page, or null if no more pages
938
+ * @returns results.results - An array of records for the current page
939
+ */
940
+ static async fastPaginate(opts) {
941
+ return await this.query().fastPaginate(opts);
942
+ }
912
943
  /**
913
944
  * Forces use of a database connection (e.g. 'primary') during the query.
914
945
  *
@@ -3221,61 +3252,31 @@ class Dream {
3221
3252
  load(...args) {
3222
3253
  return new LoadBuilder_js_1.default(this).load(...args);
3223
3254
  }
3224
- /**
3225
- * If the association is already loaded on the instance, it returns the loaded value
3226
- * immediately without making a database query. If the association is not loaded,
3227
- * it performs a database query to fetch the association.
3228
- *
3229
- * If a query is performed, the association is set on the model so that future calls to
3230
- * access this association can access the already loaded value.
3231
- *
3232
- * ```ts
3233
- * // return the already loaded user if it exists or fetch it from the database otherwise
3234
- * const user = await post.associationOrFail('user')
3235
- * ```
3236
- *
3237
- * @param associationName - The name of the BelongsTo or HasOne association
3238
- * @returns The associated model instance or null
3239
- */
3240
- async association(associationName) {
3255
+ async association(associationName, options) {
3241
3256
  if (!this.loaded(associationName)) {
3242
3257
  const association = this.getAssociationMetadata(associationName);
3258
+ const associationQueryOptions = options?.required && { and: options.required };
3259
+ let scope = this.associationQuery(associationName, associationQueryOptions);
3260
+ if (options?.passthrough)
3261
+ scope = scope.passthrough(options.passthrough);
3243
3262
  if (association?.type === 'HasMany') {
3244
- this[associationName] = (await this.associationQuery(associationName).all());
3263
+ this[associationName] = (await scope.all());
3245
3264
  }
3246
3265
  else {
3247
- this[associationName] = (await this.associationQuery(associationName).first());
3266
+ this[associationName] = (await scope.first());
3248
3267
  }
3249
3268
  }
3250
3269
  return this[associationName];
3251
3270
  }
3252
- /**
3253
- * If the association is already loaded on the instance, it returns the loaded value
3254
- * immediately without making a database query. If the association is not loaded,
3255
- * it performs a database query to fetch the association.
3256
- *
3257
- * Unlike `association`, this method throws an exception if no associated
3258
- * record is found, guaranteeing a non-null result.
3259
- *
3260
- * If a query is performed, the association is set on the model so that future calls to
3261
- * access this association can access the already loaded value.
3262
- *
3263
- * ```ts
3264
- * // return the already loaded user if it exists or fetch it from the database otherwise,
3265
- * // throwing RecordNotFound either way if the associated model does not exist
3266
- * const user = await post.associationOrFail('user')
3267
- * ```
3268
- *
3269
- * @param associationName - The name of the BelongsTo or HasOne association
3270
- * @returns The associated model instance (never null)
3271
- * @throws RecordNotFound if no associated record exists
3272
- */
3273
- async associationOrFail(associationName) {
3274
- const response = await this.association(associationName);
3271
+ async associationOrFail(associationName, options) {
3272
+ const response = await this.association(associationName, options);
3275
3273
  if (this[associationName] === null)
3276
3274
  throw new RecordNotFound_js_1.default(this['sanitizedConstructorName']);
3277
3275
  return response;
3278
3276
  }
3277
+ ///////////////////
3278
+ // end:associationOrFail
3279
+ ///////////////////
3279
3280
  /**
3280
3281
  * Recursively loads all Dream associations referenced by `rendersOne` and `rendersMany`
3281
3282
  * in a DreamSerializer. This traverses the entire content tree of serializers to automatically
@@ -38,38 +38,6 @@ class DreamClassTransactionBuilder {
38
38
  async all(options = {}) {
39
39
  return this.queryInstance().all(options);
40
40
  }
41
- /**
42
- * Paginates the results of your query, accepting a pageSize and page argument,
43
- * which it uses to segment your query into pages, leveraging limit and offset
44
- * to deliver your query to you in pages.
45
- *
46
- * ```ts
47
- * await ApplicationModel.transaction(async txn => {
48
- * const paginated = await User.txn(txn).paginate({ pageSize: 100, page: 2 })
49
- * paginated.results
50
- * // [ { User{id: 101}, User{id: 102}, ...}]
51
- *
52
- * paginated.recordCount
53
- * // 350
54
- *
55
- * paginated.pageCount
56
- * // 4
57
- *
58
- * paginated.currentPage
59
- * // 2
60
- * })
61
- * ```
62
- *
63
- * @param opts.page - the page number that you want to fetch results for
64
- * @param opts.pageSize - the number of results per page (optional)
65
- * @returns results.recordCount - A number representing the total number of records matching your query
66
- * @returns results.pageCount - The number of pages needed to encapsulate all the matching records
67
- * @returns results.currentPage - The current page (same as what is provided in the paginate args)
68
- * @returns results.results - An array of records matching the current record
69
- */
70
- async paginate(opts) {
71
- return await this.queryInstance().paginate(opts);
72
- }
73
41
  /**
74
42
  * Retrieves the number of records corresponding
75
43
  * to this model.
@@ -4,6 +4,7 @@ const CannotAssociationQueryOnUnpersistedDream_js_1 = require("../errors/associa
4
4
  const CannotCreateAssociationOnUnpersistedDream_js_1 = require("../errors/associations/CannotCreateAssociationOnUnpersistedDream.js");
5
5
  const CannotDestroyAssociationOnUnpersistedDream_js_1 = require("../errors/associations/CannotDestroyAssociationOnUnpersistedDream.js");
6
6
  const CannotUpdateAssociationOnUnpersistedDream_js_1 = require("../errors/associations/CannotUpdateAssociationOnUnpersistedDream.js");
7
+ const RecordNotFound_js_1 = require("../errors/RecordNotFound.js");
7
8
  const associationQuery_js_1 = require("./internal/associations/associationQuery.js");
8
9
  const associationUpdateQuery_js_1 = require("./internal/associations/associationUpdateQuery.js");
9
10
  const createAssociation_js_1 = require("./internal/associations/createAssociation.js");
@@ -398,6 +399,28 @@ class DreamInstanceTransactionBuilder {
398
399
  async save({ skipHooks } = {}) {
399
400
  await (0, saveDream_js_1.default)(this.dreamInstance, this.dreamTransaction, skipHooks ? { skipHooks } : undefined);
400
401
  }
402
+ async association(associationName, options) {
403
+ if (!this.dreamInstance.loaded(associationName)) {
404
+ const association = this.dreamInstance['getAssociationMetadata'](associationName);
405
+ const associationQueryOptions = options?.required && { and: options.required };
406
+ let scope = this.associationQuery(associationName, associationQueryOptions);
407
+ if (options?.passthrough)
408
+ scope = scope.passthrough(options.passthrough);
409
+ if (association?.type === 'HasMany') {
410
+ this.dreamInstance[associationName] = (await scope.all());
411
+ }
412
+ else {
413
+ this.dreamInstance[associationName] = (await scope.first());
414
+ }
415
+ }
416
+ return this.dreamInstance[associationName];
417
+ }
418
+ async associationOrFail(associationName, options) {
419
+ const response = await this.association(associationName, options);
420
+ if (this.dreamInstance[associationName] === null)
421
+ throw new RecordNotFound_js_1.default(this.dreamInstance['sanitizedConstructorName']);
422
+ return response;
423
+ }
401
424
  /**
402
425
  * Returns a Query instance for the specified
403
426
  * association on the current instance.
@@ -809,7 +809,8 @@ class Query {
809
809
  * @returns A cloned Query with the passthrough data
810
810
  */
811
811
  passthrough(passthroughOnStatement) {
812
- return this.clone({ passthroughOnStatement: passthroughOnStatement });
812
+ const baseSelectQuery = this.baseSelectQuery && this.baseSelectQuery.clone({ passthroughOnStatement: passthroughOnStatement });
813
+ return this.clone({ passthroughOnStatement: passthroughOnStatement, baseSelectQuery });
813
814
  }
814
815
  /**
815
816
  * Applies a where statement to the Query instance
@@ -1209,20 +1210,21 @@ class Query {
1209
1210
  * @returns results.results - An array of records matching the current record
1210
1211
  */
1211
1212
  async paginate(opts) {
1213
+ const options = opts;
1212
1214
  if (this.limitStatement)
1213
1215
  throw new CannotPaginateWithLimit_js_1.default();
1214
1216
  if (this.offsetStatement)
1215
1217
  throw new CannotPaginateWithOffset_js_1.default();
1216
- const page = (0, computedPaginatePage_js_1.default)(opts.page);
1218
+ const page = (0, computedPaginatePage_js_1.default)(options.page);
1217
1219
  const recordCount = await this.count();
1218
- const pageSize = opts.pageSize || index_js_1.default.getOrFail().paginationPageSize;
1220
+ const pageSize = Math.max(0, options.pageSize ?? 0) || index_js_1.default.getOrFail().paginationPageSize;
1219
1221
  const pageCount = Math.ceil(recordCount / pageSize);
1220
1222
  const query = this.orderStatements.length
1221
1223
  ? this
1222
1224
  : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1223
1225
  const results = await query
1224
1226
  .limit(pageSize)
1225
- .offset((page - 1) * pageSize)
1227
+ .offset(((page - 1) * pageSize))
1226
1228
  .all();
1227
1229
  return {
1228
1230
  recordCount,
@@ -1231,6 +1233,75 @@ class Query {
1231
1233
  results,
1232
1234
  };
1233
1235
  }
1236
+ /**
1237
+ * Fast paginates the results of your query using cursor-based pagination,
1238
+ * accepting a pageSize and cursor argument. This method
1239
+ * provides better performance for large datasets by using cursor-based
1240
+ * pagination instead of offset-based pagination.
1241
+ *
1242
+ * ```ts
1243
+ * // First page
1244
+ * const firstPage = await User.order('email').fastPaginate({ pageSize: 100 })
1245
+ * firstPage.results
1246
+ * // [ { User{id: 1}, User{id: 2}, ...}]
1247
+ *
1248
+ * firstPage.cursor
1249
+ * // "100" (or null if no more pages)
1250
+ *
1251
+ * // Next page using cursor from previous result
1252
+ * const nextPage = await User.order('email').fastPaginate({
1253
+ * pageSize: 100,
1254
+ * cursor: firstPage.cursor
1255
+ * })
1256
+ * ```
1257
+ *
1258
+ * @param opts - Fast pagination options
1259
+ * @param opts.pageSize - the number of results per page (optional)
1260
+ * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
1261
+ * @returns results.cursor - identifier for the next page, or null if no more pages
1262
+ * @returns results.results - An array of records for the current page
1263
+ */
1264
+ async fastPaginate(opts) {
1265
+ const options = opts;
1266
+ const pageSize = Math.max(0, options.pageSize ?? 0) || index_js_1.default.getOrFail().paginationPageSize;
1267
+ if (options === null)
1268
+ return { cursor: null, results: [] };
1269
+ if (this.limitStatement)
1270
+ throw new CannotPaginateWithLimit_js_1.default();
1271
+ if (this.offsetStatement)
1272
+ throw new CannotPaginateWithOffset_js_1.default();
1273
+ const explicitOrdering = !!this.orderStatements.length;
1274
+ let query = explicitOrdering ? this : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1275
+ if (options.cursor) {
1276
+ if (explicitOrdering) {
1277
+ const lastItem = await query
1278
+ .where({ [this.dreamClass.primaryKey]: options.cursor })
1279
+ .firstOrFail();
1280
+ this.orderStatements.forEach(orderStatement => {
1281
+ const column = orderStatement.column;
1282
+ const direction = orderStatement.direction;
1283
+ switch (direction) {
1284
+ case 'asc':
1285
+ query = query.where({ [column]: index_js_2.default.greaterThan(lastItem[column]) });
1286
+ break;
1287
+ case 'desc':
1288
+ query = query.where({ [column]: index_js_2.default.lessThan(lastItem[column]) });
1289
+ break;
1290
+ }
1291
+ });
1292
+ }
1293
+ else {
1294
+ query = query.where({
1295
+ [this.dreamClass.primaryKey]: index_js_2.default.greaterThan(options.cursor),
1296
+ });
1297
+ }
1298
+ }
1299
+ const results = await query.limit(pageSize).all();
1300
+ return {
1301
+ cursor: (results.length === pageSize && results.at(-1)?.primaryKeyValue().toString()) || null,
1302
+ results,
1303
+ };
1304
+ }
1234
1305
  /**
1235
1306
  * Forces use of a database connection (e.g. 'primary') during the query.
1236
1307
  *
@@ -132,24 +132,22 @@ ${tableName}: {
132
132
  const associationMetadata = tableData.associations[associationName];
133
133
  if (associationMetadata === undefined)
134
134
  return '';
135
- const whereStatement = associationMetadata.where;
136
- const requiredOnClauses = whereStatement === null
135
+ const andStatement = associationMetadata.and;
136
+ const requiredAndClauses = andStatement === null
137
137
  ? []
138
- : Object.keys(whereStatement).filter(column => whereStatement[column] === constants_js_1.DreamConst.required);
139
- passthroughColumns =
140
- whereStatement === null
141
- ? passthroughColumns
142
- : [
143
- ...passthroughColumns,
144
- ...Object.keys(whereStatement).filter(column => whereStatement[column] === constants_js_1.DreamConst.passthrough),
145
- ];
138
+ : Object.keys(andStatement).filter(column => andStatement[column] === constants_js_1.DreamConst.required);
139
+ const passthroughAndClauses = andStatement === null
140
+ ? []
141
+ : Object.keys(andStatement).filter(column => andStatement[column] === constants_js_1.DreamConst.passthrough);
142
+ passthroughColumns = [...passthroughColumns, ...passthroughAndClauses];
146
143
  return `${associationName}: {
147
144
  type: '${associationMetadata.type}',
148
145
  foreignKey: ${associationMetadata.foreignKey ? `'${associationMetadata.foreignKey}'` : 'null'},
149
146
  foreignKeyTypeColumn: ${associationMetadata.foreignKeyTypeColumn ? `'${associationMetadata.foreignKeyTypeColumn}'` : 'null'},
150
147
  tables: ${stringifyArray(associationMetadata.tables)},
151
148
  optional: ${associationMetadata.optional},
152
- requiredOnClauses: ${requiredOnClauses.length === 0 ? 'null' : stringifyArray(requiredOnClauses)},
149
+ requiredAndClauses: ${requiredAndClauses.length === 0 ? 'null' : stringifyArray(requiredAndClauses)},
150
+ passthroughAndClauses: ${passthroughAndClauses.length === 0 ? 'null' : stringifyArray(passthroughAndClauses)},
153
151
  },`;
154
152
  })
155
153
  .join('\n ')}
@@ -276,7 +274,7 @@ may need to update the table getter in the corresponding Dream.
276
274
  ? associationMetaData?.foreignKeyTypeField?.() || null
277
275
  : null,
278
276
  optional,
279
- where,
277
+ and: where,
280
278
  };
281
279
  if (foreignKey)
282
280
  tableAssociationData[associationName]['foreignKey'] = foreignKey;
@@ -907,6 +907,37 @@ export default class Dream {
907
907
  static async paginate(opts) {
908
908
  return await this.query().paginate(opts);
909
909
  }
910
+ /**
911
+ * Fast paginates the results of your query using cursor-based pagination,
912
+ * accepting a pageSize and cursor argument. This method
913
+ * provides better performance for large datasets by using cursor-based
914
+ * pagination instead of offset-based pagination.
915
+ *
916
+ * ```ts
917
+ * // First page
918
+ * const firstPage = await User.order('email').fastPaginate({ pageSize: 100 })
919
+ * firstPage.results
920
+ * // [ { User{id: 1}, User{id: 2}, ...}]
921
+ *
922
+ * firstPage.cursor
923
+ * // "100" (or null if no more pages)
924
+ *
925
+ * // Next page using cursor from previous result
926
+ * const nextPage = await User.order('email').fastPaginate({
927
+ * pageSize: 100,
928
+ * cursor: firstPage.cursor
929
+ * })
930
+ * ```
931
+ *
932
+ * @param opts - Fast pagination options
933
+ * @param opts.pageSize - the number of results per page (optional)
934
+ * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
935
+ * @returns results.cursor - identifier for the next page, or null if no more pages
936
+ * @returns results.results - An array of records for the current page
937
+ */
938
+ static async fastPaginate(opts) {
939
+ return await this.query().fastPaginate(opts);
940
+ }
910
941
  /**
911
942
  * Forces use of a database connection (e.g. 'primary') during the query.
912
943
  *
@@ -3219,61 +3250,31 @@ export default class Dream {
3219
3250
  load(...args) {
3220
3251
  return new LoadBuilder(this).load(...args);
3221
3252
  }
3222
- /**
3223
- * If the association is already loaded on the instance, it returns the loaded value
3224
- * immediately without making a database query. If the association is not loaded,
3225
- * it performs a database query to fetch the association.
3226
- *
3227
- * If a query is performed, the association is set on the model so that future calls to
3228
- * access this association can access the already loaded value.
3229
- *
3230
- * ```ts
3231
- * // return the already loaded user if it exists or fetch it from the database otherwise
3232
- * const user = await post.associationOrFail('user')
3233
- * ```
3234
- *
3235
- * @param associationName - The name of the BelongsTo or HasOne association
3236
- * @returns The associated model instance or null
3237
- */
3238
- async association(associationName) {
3253
+ async association(associationName, options) {
3239
3254
  if (!this.loaded(associationName)) {
3240
3255
  const association = this.getAssociationMetadata(associationName);
3256
+ const associationQueryOptions = options?.required && { and: options.required };
3257
+ let scope = this.associationQuery(associationName, associationQueryOptions);
3258
+ if (options?.passthrough)
3259
+ scope = scope.passthrough(options.passthrough);
3241
3260
  if (association?.type === 'HasMany') {
3242
- this[associationName] = (await this.associationQuery(associationName).all());
3261
+ this[associationName] = (await scope.all());
3243
3262
  }
3244
3263
  else {
3245
- this[associationName] = (await this.associationQuery(associationName).first());
3264
+ this[associationName] = (await scope.first());
3246
3265
  }
3247
3266
  }
3248
3267
  return this[associationName];
3249
3268
  }
3250
- /**
3251
- * If the association is already loaded on the instance, it returns the loaded value
3252
- * immediately without making a database query. If the association is not loaded,
3253
- * it performs a database query to fetch the association.
3254
- *
3255
- * Unlike `association`, this method throws an exception if no associated
3256
- * record is found, guaranteeing a non-null result.
3257
- *
3258
- * If a query is performed, the association is set on the model so that future calls to
3259
- * access this association can access the already loaded value.
3260
- *
3261
- * ```ts
3262
- * // return the already loaded user if it exists or fetch it from the database otherwise,
3263
- * // throwing RecordNotFound either way if the associated model does not exist
3264
- * const user = await post.associationOrFail('user')
3265
- * ```
3266
- *
3267
- * @param associationName - The name of the BelongsTo or HasOne association
3268
- * @returns The associated model instance (never null)
3269
- * @throws RecordNotFound if no associated record exists
3270
- */
3271
- async associationOrFail(associationName) {
3272
- const response = await this.association(associationName);
3269
+ async associationOrFail(associationName, options) {
3270
+ const response = await this.association(associationName, options);
3273
3271
  if (this[associationName] === null)
3274
3272
  throw new RecordNotFound(this['sanitizedConstructorName']);
3275
3273
  return response;
3276
3274
  }
3275
+ ///////////////////
3276
+ // end:associationOrFail
3277
+ ///////////////////
3277
3278
  /**
3278
3279
  * Recursively loads all Dream associations referenced by `rendersOne` and `rendersMany`
3279
3280
  * in a DreamSerializer. This traverses the entire content tree of serializers to automatically
@@ -36,38 +36,6 @@ export default class DreamClassTransactionBuilder {
36
36
  async all(options = {}) {
37
37
  return this.queryInstance().all(options);
38
38
  }
39
- /**
40
- * Paginates the results of your query, accepting a pageSize and page argument,
41
- * which it uses to segment your query into pages, leveraging limit and offset
42
- * to deliver your query to you in pages.
43
- *
44
- * ```ts
45
- * await ApplicationModel.transaction(async txn => {
46
- * const paginated = await User.txn(txn).paginate({ pageSize: 100, page: 2 })
47
- * paginated.results
48
- * // [ { User{id: 101}, User{id: 102}, ...}]
49
- *
50
- * paginated.recordCount
51
- * // 350
52
- *
53
- * paginated.pageCount
54
- * // 4
55
- *
56
- * paginated.currentPage
57
- * // 2
58
- * })
59
- * ```
60
- *
61
- * @param opts.page - the page number that you want to fetch results for
62
- * @param opts.pageSize - the number of results per page (optional)
63
- * @returns results.recordCount - A number representing the total number of records matching your query
64
- * @returns results.pageCount - The number of pages needed to encapsulate all the matching records
65
- * @returns results.currentPage - The current page (same as what is provided in the paginate args)
66
- * @returns results.results - An array of records matching the current record
67
- */
68
- async paginate(opts) {
69
- return await this.queryInstance().paginate(opts);
70
- }
71
39
  /**
72
40
  * Retrieves the number of records corresponding
73
41
  * to this model.
@@ -2,6 +2,7 @@ import CannotAssociationQueryOnUnpersistedDream from '../errors/associations/Can
2
2
  import CannotCreateAssociationOnUnpersistedDream from '../errors/associations/CannotCreateAssociationOnUnpersistedDream.js';
3
3
  import CannotDestroyAssociationOnUnpersistedDream from '../errors/associations/CannotDestroyAssociationOnUnpersistedDream.js';
4
4
  import CannotUpdateAssociationOnUnpersistedDream from '../errors/associations/CannotUpdateAssociationOnUnpersistedDream.js';
5
+ import RecordNotFound from '../errors/RecordNotFound.js';
5
6
  import associationQuery from './internal/associations/associationQuery.js';
6
7
  import associationUpdateQuery from './internal/associations/associationUpdateQuery.js';
7
8
  import createAssociation from './internal/associations/createAssociation.js';
@@ -396,6 +397,28 @@ export default class DreamInstanceTransactionBuilder {
396
397
  async save({ skipHooks } = {}) {
397
398
  await saveDream(this.dreamInstance, this.dreamTransaction, skipHooks ? { skipHooks } : undefined);
398
399
  }
400
+ async association(associationName, options) {
401
+ if (!this.dreamInstance.loaded(associationName)) {
402
+ const association = this.dreamInstance['getAssociationMetadata'](associationName);
403
+ const associationQueryOptions = options?.required && { and: options.required };
404
+ let scope = this.associationQuery(associationName, associationQueryOptions);
405
+ if (options?.passthrough)
406
+ scope = scope.passthrough(options.passthrough);
407
+ if (association?.type === 'HasMany') {
408
+ this.dreamInstance[associationName] = (await scope.all());
409
+ }
410
+ else {
411
+ this.dreamInstance[associationName] = (await scope.first());
412
+ }
413
+ }
414
+ return this.dreamInstance[associationName];
415
+ }
416
+ async associationOrFail(associationName, options) {
417
+ const response = await this.association(associationName, options);
418
+ if (this.dreamInstance[associationName] === null)
419
+ throw new RecordNotFound(this.dreamInstance['sanitizedConstructorName']);
420
+ return response;
421
+ }
399
422
  /**
400
423
  * Returns a Query instance for the specified
401
424
  * association on the current instance.
@@ -807,7 +807,8 @@ export default class Query {
807
807
  * @returns A cloned Query with the passthrough data
808
808
  */
809
809
  passthrough(passthroughOnStatement) {
810
- return this.clone({ passthroughOnStatement: passthroughOnStatement });
810
+ const baseSelectQuery = this.baseSelectQuery && this.baseSelectQuery.clone({ passthroughOnStatement: passthroughOnStatement });
811
+ return this.clone({ passthroughOnStatement: passthroughOnStatement, baseSelectQuery });
811
812
  }
812
813
  /**
813
814
  * Applies a where statement to the Query instance
@@ -1207,20 +1208,21 @@ export default class Query {
1207
1208
  * @returns results.results - An array of records matching the current record
1208
1209
  */
1209
1210
  async paginate(opts) {
1211
+ const options = opts;
1210
1212
  if (this.limitStatement)
1211
1213
  throw new CannotPaginateWithLimit();
1212
1214
  if (this.offsetStatement)
1213
1215
  throw new CannotPaginateWithOffset();
1214
- const page = computedPaginatePage(opts.page);
1216
+ const page = computedPaginatePage(options.page);
1215
1217
  const recordCount = await this.count();
1216
- const pageSize = opts.pageSize || DreamApp.getOrFail().paginationPageSize;
1218
+ const pageSize = Math.max(0, options.pageSize ?? 0) || DreamApp.getOrFail().paginationPageSize;
1217
1219
  const pageCount = Math.ceil(recordCount / pageSize);
1218
1220
  const query = this.orderStatements.length
1219
1221
  ? this
1220
1222
  : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1221
1223
  const results = await query
1222
1224
  .limit(pageSize)
1223
- .offset((page - 1) * pageSize)
1225
+ .offset(((page - 1) * pageSize))
1224
1226
  .all();
1225
1227
  return {
1226
1228
  recordCount,
@@ -1229,6 +1231,75 @@ export default class Query {
1229
1231
  results,
1230
1232
  };
1231
1233
  }
1234
+ /**
1235
+ * Fast paginates the results of your query using cursor-based pagination,
1236
+ * accepting a pageSize and cursor argument. This method
1237
+ * provides better performance for large datasets by using cursor-based
1238
+ * pagination instead of offset-based pagination.
1239
+ *
1240
+ * ```ts
1241
+ * // First page
1242
+ * const firstPage = await User.order('email').fastPaginate({ pageSize: 100 })
1243
+ * firstPage.results
1244
+ * // [ { User{id: 1}, User{id: 2}, ...}]
1245
+ *
1246
+ * firstPage.cursor
1247
+ * // "100" (or null if no more pages)
1248
+ *
1249
+ * // Next page using cursor from previous result
1250
+ * const nextPage = await User.order('email').fastPaginate({
1251
+ * pageSize: 100,
1252
+ * cursor: firstPage.cursor
1253
+ * })
1254
+ * ```
1255
+ *
1256
+ * @param opts - Fast pagination options
1257
+ * @param opts.pageSize - the number of results per page (optional)
1258
+ * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
1259
+ * @returns results.cursor - identifier for the next page, or null if no more pages
1260
+ * @returns results.results - An array of records for the current page
1261
+ */
1262
+ async fastPaginate(opts) {
1263
+ const options = opts;
1264
+ const pageSize = Math.max(0, options.pageSize ?? 0) || DreamApp.getOrFail().paginationPageSize;
1265
+ if (options === null)
1266
+ return { cursor: null, results: [] };
1267
+ if (this.limitStatement)
1268
+ throw new CannotPaginateWithLimit();
1269
+ if (this.offsetStatement)
1270
+ throw new CannotPaginateWithOffset();
1271
+ const explicitOrdering = !!this.orderStatements.length;
1272
+ let query = explicitOrdering ? this : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1273
+ if (options.cursor) {
1274
+ if (explicitOrdering) {
1275
+ const lastItem = await query
1276
+ .where({ [this.dreamClass.primaryKey]: options.cursor })
1277
+ .firstOrFail();
1278
+ this.orderStatements.forEach(orderStatement => {
1279
+ const column = orderStatement.column;
1280
+ const direction = orderStatement.direction;
1281
+ switch (direction) {
1282
+ case 'asc':
1283
+ query = query.where({ [column]: ops.greaterThan(lastItem[column]) });
1284
+ break;
1285
+ case 'desc':
1286
+ query = query.where({ [column]: ops.lessThan(lastItem[column]) });
1287
+ break;
1288
+ }
1289
+ });
1290
+ }
1291
+ else {
1292
+ query = query.where({
1293
+ [this.dreamClass.primaryKey]: ops.greaterThan(options.cursor),
1294
+ });
1295
+ }
1296
+ }
1297
+ const results = await query.limit(pageSize).all();
1298
+ return {
1299
+ cursor: (results.length === pageSize && results.at(-1)?.primaryKeyValue().toString()) || null,
1300
+ results,
1301
+ };
1302
+ }
1232
1303
  /**
1233
1304
  * Forces use of a database connection (e.g. 'primary') during the query.
1234
1305
  *