@rvoh/dream 2.1.4-alpha.3 → 2.2.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 (185) hide show
  1. package/dist/cjs/src/Dream.js +40 -2
  2. package/dist/cjs/src/dream/Query.js +132 -42
  3. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +7 -1
  4. package/dist/esm/src/Dream.js +40 -2
  5. package/dist/esm/src/dream/Query.js +132 -42
  6. package/dist/esm/src/helpers/cli/generateMigrationContent.js +7 -1
  7. package/dist/types/src/Dream.d.ts +40 -4
  8. package/dist/types/src/dream/Query.d.ts +45 -4
  9. package/dist/types/src/types/query.d.ts +2 -2
  10. package/dist/types/src/types/query.ts +2 -2
  11. package/docs/assets/search.js +1 -1
  12. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  13. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  14. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  15. package/docs/classes/db.QueryDriverBase.html +31 -31
  16. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  17. package/docs/classes/errors.ColumnOverflow.html +3 -3
  18. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  19. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  20. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  21. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  22. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  23. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  24. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  25. package/docs/classes/errors.NotNullViolation.html +3 -3
  26. package/docs/classes/errors.RecordNotFound.html +3 -3
  27. package/docs/classes/errors.ValidationError.html +3 -3
  28. package/docs/classes/index.CalendarDate.html +2 -2
  29. package/docs/classes/index.Decorators.html +19 -19
  30. package/docs/classes/index.Dream.html +208 -191
  31. package/docs/classes/index.DreamApp.html +5 -5
  32. package/docs/classes/index.DreamTransaction.html +2 -2
  33. package/docs/classes/index.Env.html +2 -2
  34. package/docs/classes/index.Query.html +87 -70
  35. package/docs/classes/system.CliFileWriter.html +2 -2
  36. package/docs/classes/system.DreamBin.html +2 -2
  37. package/docs/classes/system.DreamCLI.html +5 -5
  38. package/docs/classes/system.DreamImporter.html +2 -2
  39. package/docs/classes/system.DreamLogos.html +2 -2
  40. package/docs/classes/system.DreamSerializerBuilder.html +8 -8
  41. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  42. package/docs/classes/utils.Encrypt.html +2 -2
  43. package/docs/classes/utils.Range.html +2 -2
  44. package/docs/functions/db.closeAllDbConnections.html +1 -1
  45. package/docs/functions/db.dreamDbConnections.html +1 -1
  46. package/docs/functions/db.untypedDb.html +1 -1
  47. package/docs/functions/db.validateColumn.html +1 -1
  48. package/docs/functions/db.validateTable.html +1 -1
  49. package/docs/functions/errors.pgErrorType.html +1 -1
  50. package/docs/functions/index.DreamSerializer.html +1 -1
  51. package/docs/functions/index.ObjectSerializer.html +1 -1
  52. package/docs/functions/index.ReplicaSafe.html +1 -1
  53. package/docs/functions/index.STI.html +1 -1
  54. package/docs/functions/index.SoftDelete.html +1 -1
  55. package/docs/functions/utils.camelize.html +1 -1
  56. package/docs/functions/utils.capitalize.html +1 -1
  57. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  58. package/docs/functions/utils.compact.html +1 -1
  59. package/docs/functions/utils.groupBy.html +1 -1
  60. package/docs/functions/utils.hyphenize.html +1 -1
  61. package/docs/functions/utils.intersection.html +1 -1
  62. package/docs/functions/utils.isEmpty.html +1 -1
  63. package/docs/functions/utils.normalizeUnicode.html +1 -1
  64. package/docs/functions/utils.pascalize.html +1 -1
  65. package/docs/functions/utils.percent.html +1 -1
  66. package/docs/functions/utils.range-1.html +1 -1
  67. package/docs/functions/utils.round.html +1 -1
  68. package/docs/functions/utils.sanitizeString.html +1 -1
  69. package/docs/functions/utils.snakeify.html +1 -1
  70. package/docs/functions/utils.sort.html +1 -1
  71. package/docs/functions/utils.sortBy.html +1 -1
  72. package/docs/functions/utils.sortObjectByKey.html +1 -1
  73. package/docs/functions/utils.sortObjectByValue.html +1 -1
  74. package/docs/functions/utils.uncapitalize.html +1 -1
  75. package/docs/functions/utils.uniq.html +1 -1
  76. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  77. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  78. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  79. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  80. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  81. package/docs/interfaces/types.DecoratorContext.html +2 -2
  82. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  83. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  84. package/docs/interfaces/types.EncryptOptions.html +2 -2
  85. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  86. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  87. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  88. package/docs/modules/db.html +1 -1
  89. package/docs/modules/errors.html +1 -1
  90. package/docs/modules/index.html +1 -1
  91. package/docs/modules/openapi.html +1 -1
  92. package/docs/modules/system.html +1 -1
  93. package/docs/modules/types.html +1 -1
  94. package/docs/modules/utils.html +1 -1
  95. package/docs/types/index.DateTime.html +1 -1
  96. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  97. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  98. package/docs/types/openapi.OpenapiFormats.html +1 -1
  99. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  100. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  101. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  102. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  103. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  104. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  105. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  106. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  107. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  108. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  109. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  110. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  111. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +1 -1
  112. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  113. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  114. package/docs/types/openapi.OpenapiSchemaNull.html +1 -1
  115. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  116. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  117. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  118. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  119. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  120. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  121. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  122. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  123. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  124. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  125. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  126. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  127. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  128. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  129. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  130. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  131. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  132. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  133. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  134. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  135. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  136. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  137. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  138. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  139. package/docs/types/types.Camelized.html +1 -1
  140. package/docs/types/types.DbConnectionType.html +1 -1
  141. package/docs/types/types.DbTypes.html +1 -1
  142. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  143. package/docs/types/types.DreamAttributes.html +1 -1
  144. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  145. package/docs/types/types.DreamClassColumn.html +1 -1
  146. package/docs/types/types.DreamColumn.html +1 -1
  147. package/docs/types/types.DreamColumnNames.html +1 -1
  148. package/docs/types/types.DreamLogLevel.html +1 -1
  149. package/docs/types/types.DreamLogger.html +1 -1
  150. package/docs/types/types.DreamModelSerializerType.html +1 -1
  151. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  152. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  153. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  154. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  155. package/docs/types/types.DreamSerializable.html +1 -1
  156. package/docs/types/types.DreamSerializableArray.html +1 -1
  157. package/docs/types/types.DreamSerializerKey.html +1 -1
  158. package/docs/types/types.DreamSerializers.html +1 -1
  159. package/docs/types/types.DreamVirtualColumns.html +1 -1
  160. package/docs/types/types.EncryptAlgorithm.html +1 -1
  161. package/docs/types/types.HasManyStatement.html +1 -1
  162. package/docs/types/types.HasOneStatement.html +1 -1
  163. package/docs/types/types.Hyphenized.html +1 -1
  164. package/docs/types/types.Pascalized.html +1 -1
  165. package/docs/types/types.PrimaryKeyType.html +1 -1
  166. package/docs/types/types.RoundingPrecision.html +1 -1
  167. package/docs/types/types.SerializerCasing.html +1 -1
  168. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  169. package/docs/types/types.Snakeified.html +1 -1
  170. package/docs/types/types.StrictInterface.html +1 -1
  171. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  172. package/docs/types/types.UpdateableProperties.html +1 -1
  173. package/docs/types/types.ValidationType.html +1 -1
  174. package/docs/types/types.ViewModel.html +1 -1
  175. package/docs/types/types.ViewModelClass.html +1 -1
  176. package/docs/types/types.WhereStatementForDream.html +1 -1
  177. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  178. package/docs/variables/index.DateTime-1.html +1 -1
  179. package/docs/variables/index.DreamConst.html +1 -1
  180. package/docs/variables/index.ops.html +1 -1
  181. package/docs/variables/openapi.openapiPrimitiveTypes-1.html +1 -1
  182. package/docs/variables/openapi.openapiShorthandPrimitiveTypes-1.html +1 -1
  183. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  184. package/docs/variables/system.primaryKeyTypes.html +1 -1
  185. package/package.json +1 -1
@@ -926,11 +926,15 @@ export default class Dream {
926
926
  return await this.query().paginate(opts);
927
927
  }
928
928
  /**
929
- * Fast paginates the results of your query using cursor-based pagination,
929
+ * @deprecated Use {@link cursorPaginate} instead.
930
+ *
931
+ * Paginates the results of your query using cursor-based pagination,
930
932
  * accepting a pageSize and cursor argument. This method
931
933
  * provides better performance for large datasets by using cursor-based
932
934
  * pagination instead of offset-based pagination.
933
935
  *
936
+ * Default order is ascending primary key.
937
+ *
934
938
  * ```ts
935
939
  * // First page (using undefined to start from beginning)
936
940
  * const firstPage = await User.scrollPaginate({ pageSize: 100, cursor: undefined })
@@ -947,7 +951,7 @@ export default class Dream {
947
951
  * })
948
952
  * ```
949
953
  *
950
- * @param opts - Fast pagination options
954
+ * @param opts - scroll pagination options
951
955
  * @param opts.pageSize - the number of results per page
952
956
  * @param opts.cursor - identifier of where to start the next page; use undefined to start from the beginning
953
957
  * @returns A fast pagination result object containing:
@@ -957,6 +961,40 @@ export default class Dream {
957
961
  static async scrollPaginate(opts) {
958
962
  return await this.query().scrollPaginate(opts);
959
963
  }
964
+ /**
965
+ * Paginates the results of your query using cursor-based pagination,
966
+ * accepting a pageSize and cursor argument. This method
967
+ * provides better performance for large datasets by using cursor-based
968
+ * pagination instead of offset-based pagination.
969
+ *
970
+ * Default order is descending primary key.
971
+ *
972
+ * ```ts
973
+ * // First page (using undefined to start from beginning)
974
+ * const firstPage = await User.cursorPaginate({ pageSize: 100, cursor: undefined })
975
+ * firstPage.results
976
+ * // [ { User{id: 777}, User{id: 776}, ...}]
977
+ *
978
+ * firstPage.cursor
979
+ * // "100" (or null if no more pages)
980
+ *
981
+ * // Next page using cursor from previous result
982
+ * const nextPage = await User.cursorPaginate({
983
+ * pageSize: 100,
984
+ * cursor: firstPage.cursor
985
+ * })
986
+ * ```
987
+ *
988
+ * @param opts - cursor pagination options
989
+ * @param opts.pageSize - the number of results per page
990
+ * @param opts.cursor - identifier of where to start the next page; use undefined to start from the beginning
991
+ * @returns A fast pagination result object containing:
992
+ * - `cursor` - identifier for the next page, or null if no more pages
993
+ * - `results` - An array of records for the current page
994
+ */
995
+ static async cursorPaginate(opts) {
996
+ return await this.query().cursorPaginate(opts);
997
+ }
960
998
  /**
961
999
  * Forces use of a database connection (e.g. 'primary') during the query.
962
1000
  *
@@ -1225,7 +1225,7 @@ export default class Query {
1225
1225
  const pageCount = Math.ceil(recordCount / pageSize);
1226
1226
  const query = this.orderStatements.length
1227
1227
  ? this
1228
- : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1228
+ : this.order({ [this.namespacedPrimaryKey]: 'desc' });
1229
1229
  const results = await query
1230
1230
  .limit(pageSize)
1231
1231
  .offset(((page - 1) * pageSize))
@@ -1238,11 +1238,17 @@ export default class Query {
1238
1238
  };
1239
1239
  }
1240
1240
  /**
1241
- * Fast paginates the results of your query using cursor-based pagination,
1241
+ * @deprecated Use {@link cursorPaginate} instead.
1242
+ *
1243
+ * Paginates the results of your query using cursor-based pagination,
1242
1244
  * accepting a pageSize and cursor argument. This method
1243
1245
  * provides better performance for large datasets by using cursor-based
1244
1246
  * pagination instead of offset-based pagination.
1245
1247
  *
1248
+ * Default order is ascending primary key. If an order has already been
1249
+ * set on the query, and it includes the primary key (e.g.: `id: 'asc'`),
1250
+ * then the implicit primary key ordering will be omitted.
1251
+ *
1246
1252
  * ```ts
1247
1253
  * // First page (using undefined to start from beginning)
1248
1254
  * const firstPage = await User.order('email').scrollPaginate({ pageSize: 100, cursor: undefined })
@@ -1259,13 +1265,53 @@ export default class Query {
1259
1265
  * })
1260
1266
  * ```
1261
1267
  *
1262
- * @param opts - Fast pagination options
1268
+ * @param opts - scroll pagination options
1263
1269
  * @param opts.pageSize - the number of results per page (optional)
1264
1270
  * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
1265
1271
  * @returns results.cursor - identifier for the next page, or null if no more pages
1266
1272
  * @returns results.results - An array of records for the current page
1267
1273
  */
1268
1274
  async scrollPaginate(opts) {
1275
+ const orderIncludesPrimaryKey = this.orderStatements.some(orderStatement => orderStatement.column === this.dreamClass.primaryKey ||
1276
+ orderStatement.column === this.namespacedPrimaryKey);
1277
+ const query = orderIncludesPrimaryKey
1278
+ ? this
1279
+ : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1280
+ return await query.cursorPaginate(opts);
1281
+ }
1282
+ /**
1283
+ * Paginates the results of your query using cursor-based pagination,
1284
+ * accepting a pageSize and cursor argument. This method
1285
+ * provides better performance for large datasets by using cursor-based
1286
+ * pagination instead of offset-based pagination.
1287
+ *
1288
+ * Default order is descending primary key. If an order has already been
1289
+ * set on the query, and it includes the primary key (e.g.: `id: 'asc'`),
1290
+ * then the implicit primary key ordering will be omitted.
1291
+ *
1292
+ * ```ts
1293
+ * // First page (using undefined to start from beginning)
1294
+ * const firstPage = await User.order('email').cursorPaginate({ pageSize: 100, cursor: undefined })
1295
+ * firstPage.results
1296
+ * // [ { User{id: 777}, User{id: 776}, ...}]
1297
+ *
1298
+ * firstPage.cursor
1299
+ * // "100" (or null if no more pages)
1300
+ *
1301
+ * // Next page using cursor from previous result
1302
+ * const nextPage = await User.order('email').cursorPaginate({
1303
+ * pageSize: 100,
1304
+ * cursor: firstPage.cursor
1305
+ * })
1306
+ * ```
1307
+ *
1308
+ * @param opts - cursor pagination options
1309
+ * @param opts.pageSize - the number of results per page (optional)
1310
+ * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
1311
+ * @returns results.cursor - identifier for the next page, or null if no more pages
1312
+ * @returns results.results - An array of records for the current page
1313
+ */
1314
+ async cursorPaginate(opts) {
1269
1315
  const options = opts;
1270
1316
  const pageSize = Math.max(0, options.pageSize ?? 0) || DreamApp.getOrFail().paginationPageSize;
1271
1317
  if (options === null)
@@ -1280,53 +1326,97 @@ export default class Query {
1280
1326
  orderStatement.column === this.namespacedPrimaryKey);
1281
1327
  let query = orderIncludesPrimaryKey
1282
1328
  ? this
1283
- : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1329
+ : this.order({ [this.namespacedPrimaryKey]: 'desc' });
1284
1330
  if (options.cursor) {
1285
1331
  const orderStatements = query.orderStatements;
1286
- /**
1287
- * if there is only one ordering, then that is the primary key (either added above or in the original),
1288
- * so we don't need to pluck values, and all we need is the primary key that we already have in the cursor
1289
- */
1290
- const endOfPreviousPageComparisonValues = orderStatements.length === 1
1291
- ? [options.cursor]
1292
- : (await query
1332
+ if (orderStatements.length === 1) {
1333
+ /**
1334
+ * Since we add a primary key above if it wasn't already included, we know
1335
+ * that if there is only one order statement, then it must be ordering on
1336
+ * the primary key
1337
+ */
1338
+ const orderStatement = orderStatements[0];
1339
+ switch (orderStatement.direction) {
1340
+ case 'asc':
1341
+ query = query.where({
1342
+ [orderStatement.column]: ops.greaterThan(options.cursor),
1343
+ });
1344
+ break;
1345
+ case 'desc':
1346
+ query = query.where({
1347
+ [orderStatement.column]: ops.lessThan(options.cursor),
1348
+ });
1349
+ break;
1350
+ }
1351
+ }
1352
+ else {
1353
+ const endOfPreviousPageComparisonValues = (await query
1293
1354
  .removeDefaultScopeExceptOnAssociations(SOFT_DELETE_SCOPE_NAME)
1294
1355
  .where({ [this.namespacedPrimaryKey]: options.cursor })
1295
1356
  .limit(1)
1296
1357
  .order(null)
1297
- .pluck(...orderStatements.map(orderStatement => orderStatement.column)))[0] || [];
1298
- endOfPreviousPageComparisonValues.forEach((valueToCompare, index) => {
1299
- const orderStatement = orderStatements[index];
1300
- if (orderStatement.column === this.dreamClass.primaryKey ||
1301
- orderStatement.column === this.namespacedPrimaryKey) {
1302
- switch (orderStatement.direction) {
1303
- case 'asc':
1304
- query = query.where({
1305
- [orderStatement.column]: ops.greaterThan(valueToCompare),
1306
- });
1307
- break;
1308
- case 'desc':
1309
- query = query.where({
1310
- [orderStatement.column]: ops.lessThan(valueToCompare),
1311
- });
1312
- break;
1313
- }
1314
- }
1315
- else {
1316
- switch (orderStatement.direction) {
1317
- case 'asc':
1318
- query = query.where({
1319
- [orderStatement.column]: ops.greaterThanOrEqualTo(valueToCompare),
1320
- });
1321
- break;
1322
- case 'desc':
1323
- query = query.where({
1324
- [orderStatement.column]: ops.lessThanOrEqualTo(valueToCompare),
1325
- });
1326
- break;
1358
+ .pluck(...orderStatements.map(orderStatement => orderStatement.column)))[0];
1359
+ if (endOfPreviousPageComparisonValues) {
1360
+ const whereAnyMaybeEqualArray = [];
1361
+ for (let index = 0; index < endOfPreviousPageComparisonValues.length; index++) {
1362
+ /**
1363
+ * This nested loop enables us to give priority to the left-most order clause,
1364
+ * then the left-most and next left-most, etc., replicating what ordering on
1365
+ * multiple clauses does:
1366
+ *
1367
+ * const results = await Pet.query()
1368
+ * .leftJoin('user')
1369
+ * // The primary key is implicitly added if not explicitly present, so these two
1370
+ * // order clauses appear in the query with an additional primary key clause.
1371
+ * // This supports pagination when the earlier order clauses match multiple records.
1372
+ * .order({ 'pets.name': 'asc', 'user.id': 'asc' })
1373
+ * .cursorPaginate({ pageSize: 2, cursor: undefined })
1374
+ *
1375
+ * Results in:
1376
+ *
1377
+ * SELECT
1378
+ * "pets".*
1379
+ * FROM
1380
+ * "pets"
1381
+ * LEFT JOIN "users" AS "user" ON "pets"."user_id" = "user"."id"
1382
+ * AND "user"."deleted_at" IS NULL
1383
+ * WHERE ("pets"."deleted_at" IS NULL
1384
+ * AND (
1385
+ * "pets"."name" > $1
1386
+ * OR (
1387
+ * "pets"."name" = $2 AND "user"."id" > $3
1388
+ * ) OR (
1389
+ * "pets"."name" = $4 AND "user"."id" = $5 AND "pets"."id" < $6
1390
+ * )))
1391
+ * ORDER BY
1392
+ * "pets"."name" ASC nulls FIRST,
1393
+ * "user"."id" ASC nulls FIRST,
1394
+ * "pets"."id" DESC nulls LAST
1395
+ * LIMIT $7
1396
+ */
1397
+ const whereAnyMaybeEqual = {};
1398
+ whereAnyMaybeEqualArray.push(whereAnyMaybeEqual);
1399
+ for (let nestedIndex = 0; nestedIndex <= index; nestedIndex++) {
1400
+ const valueToCompare = endOfPreviousPageComparisonValues[nestedIndex];
1401
+ const orderStatement = orderStatements[nestedIndex];
1402
+ if (nestedIndex < index) {
1403
+ whereAnyMaybeEqual[orderStatement.column] = valueToCompare;
1404
+ }
1405
+ else if (nestedIndex === index) {
1406
+ switch (orderStatement.direction) {
1407
+ case 'asc':
1408
+ whereAnyMaybeEqual[orderStatement.column] = ops.greaterThan(valueToCompare);
1409
+ break;
1410
+ case 'desc':
1411
+ whereAnyMaybeEqual[orderStatement.column] = ops.lessThan(valueToCompare);
1412
+ break;
1413
+ }
1414
+ }
1415
+ }
1327
1416
  }
1417
+ query = query.whereAny(whereAnyMaybeEqualArray);
1328
1418
  }
1329
- });
1419
+ }
1330
1420
  }
1331
1421
  const results = await query.limit(pageSize).all();
1332
1422
  return {
@@ -295,12 +295,18 @@ function generateIdStr({ primaryKeyType }) {
295
295
  .defaultTo(sql\`uuidv7()\`),
296
296
  )`;
297
297
  case 'uuid4':
298
- case 'uuid':
299
298
  return `\
300
299
  .addColumn('id', 'uuid', col =>
301
300
  col
302
301
  .primaryKey()
303
302
  .defaultTo(sql\`gen_random_uuid()\`),
303
+ )`;
304
+ case 'uuid':
305
+ return `\
306
+ .addColumn('id', 'uuid', col =>
307
+ col
308
+ .primaryKey()
309
+ .defaultTo(sql\`uuid_generate_v4()\`),
304
310
  )`;
305
311
  default:
306
312
  return `.addColumn('id', '${primaryKeyType}', col => col.primaryKey())`;
@@ -926,11 +926,15 @@ export default class Dream {
926
926
  return await this.query().paginate(opts);
927
927
  }
928
928
  /**
929
- * Fast paginates the results of your query using cursor-based pagination,
929
+ * @deprecated Use {@link cursorPaginate} instead.
930
+ *
931
+ * Paginates the results of your query using cursor-based pagination,
930
932
  * accepting a pageSize and cursor argument. This method
931
933
  * provides better performance for large datasets by using cursor-based
932
934
  * pagination instead of offset-based pagination.
933
935
  *
936
+ * Default order is ascending primary key.
937
+ *
934
938
  * ```ts
935
939
  * // First page (using undefined to start from beginning)
936
940
  * const firstPage = await User.scrollPaginate({ pageSize: 100, cursor: undefined })
@@ -947,7 +951,7 @@ export default class Dream {
947
951
  * })
948
952
  * ```
949
953
  *
950
- * @param opts - Fast pagination options
954
+ * @param opts - scroll pagination options
951
955
  * @param opts.pageSize - the number of results per page
952
956
  * @param opts.cursor - identifier of where to start the next page; use undefined to start from the beginning
953
957
  * @returns A fast pagination result object containing:
@@ -957,6 +961,40 @@ export default class Dream {
957
961
  static async scrollPaginate(opts) {
958
962
  return await this.query().scrollPaginate(opts);
959
963
  }
964
+ /**
965
+ * Paginates the results of your query using cursor-based pagination,
966
+ * accepting a pageSize and cursor argument. This method
967
+ * provides better performance for large datasets by using cursor-based
968
+ * pagination instead of offset-based pagination.
969
+ *
970
+ * Default order is descending primary key.
971
+ *
972
+ * ```ts
973
+ * // First page (using undefined to start from beginning)
974
+ * const firstPage = await User.cursorPaginate({ pageSize: 100, cursor: undefined })
975
+ * firstPage.results
976
+ * // [ { User{id: 777}, User{id: 776}, ...}]
977
+ *
978
+ * firstPage.cursor
979
+ * // "100" (or null if no more pages)
980
+ *
981
+ * // Next page using cursor from previous result
982
+ * const nextPage = await User.cursorPaginate({
983
+ * pageSize: 100,
984
+ * cursor: firstPage.cursor
985
+ * })
986
+ * ```
987
+ *
988
+ * @param opts - cursor pagination options
989
+ * @param opts.pageSize - the number of results per page
990
+ * @param opts.cursor - identifier of where to start the next page; use undefined to start from the beginning
991
+ * @returns A fast pagination result object containing:
992
+ * - `cursor` - identifier for the next page, or null if no more pages
993
+ * - `results` - An array of records for the current page
994
+ */
995
+ static async cursorPaginate(opts) {
996
+ return await this.query().cursorPaginate(opts);
997
+ }
960
998
  /**
961
999
  * Forces use of a database connection (e.g. 'primary') during the query.
962
1000
  *
@@ -1225,7 +1225,7 @@ export default class Query {
1225
1225
  const pageCount = Math.ceil(recordCount / pageSize);
1226
1226
  const query = this.orderStatements.length
1227
1227
  ? this
1228
- : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1228
+ : this.order({ [this.namespacedPrimaryKey]: 'desc' });
1229
1229
  const results = await query
1230
1230
  .limit(pageSize)
1231
1231
  .offset(((page - 1) * pageSize))
@@ -1238,11 +1238,17 @@ export default class Query {
1238
1238
  };
1239
1239
  }
1240
1240
  /**
1241
- * Fast paginates the results of your query using cursor-based pagination,
1241
+ * @deprecated Use {@link cursorPaginate} instead.
1242
+ *
1243
+ * Paginates the results of your query using cursor-based pagination,
1242
1244
  * accepting a pageSize and cursor argument. This method
1243
1245
  * provides better performance for large datasets by using cursor-based
1244
1246
  * pagination instead of offset-based pagination.
1245
1247
  *
1248
+ * Default order is ascending primary key. If an order has already been
1249
+ * set on the query, and it includes the primary key (e.g.: `id: 'asc'`),
1250
+ * then the implicit primary key ordering will be omitted.
1251
+ *
1246
1252
  * ```ts
1247
1253
  * // First page (using undefined to start from beginning)
1248
1254
  * const firstPage = await User.order('email').scrollPaginate({ pageSize: 100, cursor: undefined })
@@ -1259,13 +1265,53 @@ export default class Query {
1259
1265
  * })
1260
1266
  * ```
1261
1267
  *
1262
- * @param opts - Fast pagination options
1268
+ * @param opts - scroll pagination options
1263
1269
  * @param opts.pageSize - the number of results per page (optional)
1264
1270
  * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
1265
1271
  * @returns results.cursor - identifier for the next page, or null if no more pages
1266
1272
  * @returns results.results - An array of records for the current page
1267
1273
  */
1268
1274
  async scrollPaginate(opts) {
1275
+ const orderIncludesPrimaryKey = this.orderStatements.some(orderStatement => orderStatement.column === this.dreamClass.primaryKey ||
1276
+ orderStatement.column === this.namespacedPrimaryKey);
1277
+ const query = orderIncludesPrimaryKey
1278
+ ? this
1279
+ : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1280
+ return await query.cursorPaginate(opts);
1281
+ }
1282
+ /**
1283
+ * Paginates the results of your query using cursor-based pagination,
1284
+ * accepting a pageSize and cursor argument. This method
1285
+ * provides better performance for large datasets by using cursor-based
1286
+ * pagination instead of offset-based pagination.
1287
+ *
1288
+ * Default order is descending primary key. If an order has already been
1289
+ * set on the query, and it includes the primary key (e.g.: `id: 'asc'`),
1290
+ * then the implicit primary key ordering will be omitted.
1291
+ *
1292
+ * ```ts
1293
+ * // First page (using undefined to start from beginning)
1294
+ * const firstPage = await User.order('email').cursorPaginate({ pageSize: 100, cursor: undefined })
1295
+ * firstPage.results
1296
+ * // [ { User{id: 777}, User{id: 776}, ...}]
1297
+ *
1298
+ * firstPage.cursor
1299
+ * // "100" (or null if no more pages)
1300
+ *
1301
+ * // Next page using cursor from previous result
1302
+ * const nextPage = await User.order('email').cursorPaginate({
1303
+ * pageSize: 100,
1304
+ * cursor: firstPage.cursor
1305
+ * })
1306
+ * ```
1307
+ *
1308
+ * @param opts - cursor pagination options
1309
+ * @param opts.pageSize - the number of results per page (optional)
1310
+ * @param opts.cursor - identifier of where to start the next page; undefined to start from the beginning; null when no more pages
1311
+ * @returns results.cursor - identifier for the next page, or null if no more pages
1312
+ * @returns results.results - An array of records for the current page
1313
+ */
1314
+ async cursorPaginate(opts) {
1269
1315
  const options = opts;
1270
1316
  const pageSize = Math.max(0, options.pageSize ?? 0) || DreamApp.getOrFail().paginationPageSize;
1271
1317
  if (options === null)
@@ -1280,53 +1326,97 @@ export default class Query {
1280
1326
  orderStatement.column === this.namespacedPrimaryKey);
1281
1327
  let query = orderIncludesPrimaryKey
1282
1328
  ? this
1283
- : this.order({ [this.namespacedPrimaryKey]: 'asc' });
1329
+ : this.order({ [this.namespacedPrimaryKey]: 'desc' });
1284
1330
  if (options.cursor) {
1285
1331
  const orderStatements = query.orderStatements;
1286
- /**
1287
- * if there is only one ordering, then that is the primary key (either added above or in the original),
1288
- * so we don't need to pluck values, and all we need is the primary key that we already have in the cursor
1289
- */
1290
- const endOfPreviousPageComparisonValues = orderStatements.length === 1
1291
- ? [options.cursor]
1292
- : (await query
1332
+ if (orderStatements.length === 1) {
1333
+ /**
1334
+ * Since we add a primary key above if it wasn't already included, we know
1335
+ * that if there is only one order statement, then it must be ordering on
1336
+ * the primary key
1337
+ */
1338
+ const orderStatement = orderStatements[0];
1339
+ switch (orderStatement.direction) {
1340
+ case 'asc':
1341
+ query = query.where({
1342
+ [orderStatement.column]: ops.greaterThan(options.cursor),
1343
+ });
1344
+ break;
1345
+ case 'desc':
1346
+ query = query.where({
1347
+ [orderStatement.column]: ops.lessThan(options.cursor),
1348
+ });
1349
+ break;
1350
+ }
1351
+ }
1352
+ else {
1353
+ const endOfPreviousPageComparisonValues = (await query
1293
1354
  .removeDefaultScopeExceptOnAssociations(SOFT_DELETE_SCOPE_NAME)
1294
1355
  .where({ [this.namespacedPrimaryKey]: options.cursor })
1295
1356
  .limit(1)
1296
1357
  .order(null)
1297
- .pluck(...orderStatements.map(orderStatement => orderStatement.column)))[0] || [];
1298
- endOfPreviousPageComparisonValues.forEach((valueToCompare, index) => {
1299
- const orderStatement = orderStatements[index];
1300
- if (orderStatement.column === this.dreamClass.primaryKey ||
1301
- orderStatement.column === this.namespacedPrimaryKey) {
1302
- switch (orderStatement.direction) {
1303
- case 'asc':
1304
- query = query.where({
1305
- [orderStatement.column]: ops.greaterThan(valueToCompare),
1306
- });
1307
- break;
1308
- case 'desc':
1309
- query = query.where({
1310
- [orderStatement.column]: ops.lessThan(valueToCompare),
1311
- });
1312
- break;
1313
- }
1314
- }
1315
- else {
1316
- switch (orderStatement.direction) {
1317
- case 'asc':
1318
- query = query.where({
1319
- [orderStatement.column]: ops.greaterThanOrEqualTo(valueToCompare),
1320
- });
1321
- break;
1322
- case 'desc':
1323
- query = query.where({
1324
- [orderStatement.column]: ops.lessThanOrEqualTo(valueToCompare),
1325
- });
1326
- break;
1358
+ .pluck(...orderStatements.map(orderStatement => orderStatement.column)))[0];
1359
+ if (endOfPreviousPageComparisonValues) {
1360
+ const whereAnyMaybeEqualArray = [];
1361
+ for (let index = 0; index < endOfPreviousPageComparisonValues.length; index++) {
1362
+ /**
1363
+ * This nested loop enables us to give priority to the left-most order clause,
1364
+ * then the left-most and next left-most, etc., replicating what ordering on
1365
+ * multiple clauses does:
1366
+ *
1367
+ * const results = await Pet.query()
1368
+ * .leftJoin('user')
1369
+ * // The primary key is implicitly added if not explicitly present, so these two
1370
+ * // order clauses appear in the query with an additional primary key clause.
1371
+ * // This supports pagination when the earlier order clauses match multiple records.
1372
+ * .order({ 'pets.name': 'asc', 'user.id': 'asc' })
1373
+ * .cursorPaginate({ pageSize: 2, cursor: undefined })
1374
+ *
1375
+ * Results in:
1376
+ *
1377
+ * SELECT
1378
+ * "pets".*
1379
+ * FROM
1380
+ * "pets"
1381
+ * LEFT JOIN "users" AS "user" ON "pets"."user_id" = "user"."id"
1382
+ * AND "user"."deleted_at" IS NULL
1383
+ * WHERE ("pets"."deleted_at" IS NULL
1384
+ * AND (
1385
+ * "pets"."name" > $1
1386
+ * OR (
1387
+ * "pets"."name" = $2 AND "user"."id" > $3
1388
+ * ) OR (
1389
+ * "pets"."name" = $4 AND "user"."id" = $5 AND "pets"."id" < $6
1390
+ * )))
1391
+ * ORDER BY
1392
+ * "pets"."name" ASC nulls FIRST,
1393
+ * "user"."id" ASC nulls FIRST,
1394
+ * "pets"."id" DESC nulls LAST
1395
+ * LIMIT $7
1396
+ */
1397
+ const whereAnyMaybeEqual = {};
1398
+ whereAnyMaybeEqualArray.push(whereAnyMaybeEqual);
1399
+ for (let nestedIndex = 0; nestedIndex <= index; nestedIndex++) {
1400
+ const valueToCompare = endOfPreviousPageComparisonValues[nestedIndex];
1401
+ const orderStatement = orderStatements[nestedIndex];
1402
+ if (nestedIndex < index) {
1403
+ whereAnyMaybeEqual[orderStatement.column] = valueToCompare;
1404
+ }
1405
+ else if (nestedIndex === index) {
1406
+ switch (orderStatement.direction) {
1407
+ case 'asc':
1408
+ whereAnyMaybeEqual[orderStatement.column] = ops.greaterThan(valueToCompare);
1409
+ break;
1410
+ case 'desc':
1411
+ whereAnyMaybeEqual[orderStatement.column] = ops.lessThan(valueToCompare);
1412
+ break;
1413
+ }
1414
+ }
1415
+ }
1327
1416
  }
1417
+ query = query.whereAny(whereAnyMaybeEqualArray);
1328
1418
  }
1329
- });
1419
+ }
1330
1420
  }
1331
1421
  const results = await query.limit(pageSize).all();
1332
1422
  return {
@@ -295,12 +295,18 @@ function generateIdStr({ primaryKeyType }) {
295
295
  .defaultTo(sql\`uuidv7()\`),
296
296
  )`;
297
297
  case 'uuid4':
298
- case 'uuid':
299
298
  return `\
300
299
  .addColumn('id', 'uuid', col =>
301
300
  col
302
301
  .primaryKey()
303
302
  .defaultTo(sql\`gen_random_uuid()\`),
303
+ )`;
304
+ case 'uuid':
305
+ return `\
306
+ .addColumn('id', 'uuid', col =>
307
+ col
308
+ .primaryKey()
309
+ .defaultTo(sql\`uuid_generate_v4()\`),
304
310
  )`;
305
311
  default:
306
312
  return `.addColumn('id', '${primaryKeyType}', col => col.primaryKey())`;