json-rest-api 1.0.28 → 2.0.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 (289) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +746 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +634 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +100 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
  38. package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +36 -0
  44. package/index.js +1 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +64 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -174
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +20 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +142 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1069
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -385
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +38 -69
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +39 -82
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -150
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -118
  251. package/tests/fixtures/api-configs.js +0 -2509
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-cursor-multifield.test.js +0 -643
  264. package/tests/pagination-enhanced.test.js +0 -563
  265. package/tests/polymorphic-relationships.test.js +0 -347
  266. package/tests/polymorphic-simple.test.js +0 -148
  267. package/tests/positioning-advanced.test.js +0 -692
  268. package/tests/positioning-extended.test.js +0 -915
  269. package/tests/positioning-multifilter.test.js +0 -699
  270. package/tests/positioning.test.js +0 -560
  271. package/tests/post-commit-hooks.test.js +0 -120
  272. package/tests/projected-fields.test.js +0 -319
  273. package/tests/public-exports.test.js +0 -15
  274. package/tests/put-create-parity.test.js +0 -216
  275. package/tests/queries.test.js +0 -521
  276. package/tests/query-limits.test.js +0 -584
  277. package/tests/relationship-endpoints.test.js +0 -620
  278. package/tests/relationship-include-storage-adapter.test.js +0 -56
  279. package/tests/relationships.test.js +0 -681
  280. package/tests/rest-api.test.js +0 -337
  281. package/tests/return-record-settings.test.js +0 -184
  282. package/tests/row-policy.test.js +0 -489
  283. package/tests/s3-storage.test.js +0 -38
  284. package/tests/searchschema-knex-behavior.test.js +0 -187
  285. package/tests/searchschema-merge.test.js +0 -151
  286. package/tests/socketio.test.js +0 -860
  287. package/tests/storage-mapping.test.js +0 -362
  288. package/tests/test-development-guide.md +0 -370
  289. package/tests/virtual-fields.test.js +0 -404
@@ -1,1238 +0,0 @@
1
- # 2.7 Pagination and ordering
2
-
3
- Pagination and ordering are essential features for working with large datasets. The json-rest-api library provides powerful pagination that applies not just to main resources, but also to included relationships. This means you can limit and order both parent records AND their children independently.
4
-
5
- For derived fields that must participate in `ORDER BY` and cursor pagination, see [Query Projections](GUIDE_X_Query_Projections.md). Regular computed fields are response-layer only and are not the right tool for stable SQL ordering.
6
-
7
- Let's create a comprehensive example with posts, comments, and tags to demonstrate all pagination and ordering features:
8
-
9
- ```javascript
10
- await api.addResource('posts', {
11
- schema: {
12
- title: { type: 'string', required: true, max: 255, search: true, filterOperator: 'like', indexed: true },
13
- content: { type: 'string', required: true },
14
- view_count: { type: 'number', default: 0, indexed: true },
15
- published_at: { type: 'dateTime', indexed: true },
16
- created_at: { type: 'dateTime', temporalPrecision: 3, defaultTo: () => new Date().toISOString(), indexed: true }
17
- },
18
- relationships: {
19
- comments: {
20
- type: 'hasMany',
21
- target: 'comments',
22
- foreignKey: 'post_id',
23
- include: {
24
- limit: 5, // Default limit for included comments
25
- orderBy: ['-created_at'] // Newest comments first
26
- }
27
- },
28
- tags: {
29
- type: 'manyToMany',
30
- through: 'post_tags',
31
- foreignKey: 'post_id',
32
- otherKey: 'tag_id',
33
- include: {
34
- limit: 10,
35
- orderBy: ['name'] // Alphabetical order
36
- }
37
- }
38
- },
39
- // Define which fields can be sorted in queries
40
- sortableFields: ['title', 'view_count', 'published_at', 'created_at'],
41
- // Default sort uses the same format as query sort parameters:
42
- // - String format: '-created_at' (- prefix for DESC)
43
- // - Array format: ['-created_at', 'title'] for multiple sorts
44
- defaultSort: '-created_at', // Newest first by default
45
-
46
- // Set pagination limits for this resource
47
- queryDefaultLimit: 10, // Default page size
48
- queryMaxLimit: 50 // Maximum allowed page size
49
- });
50
- await api.resources.posts.createKnexTable();
51
-
52
- // Define comments resource
53
- await api.addResource('comments', {
54
- schema: {
55
- content: { type: 'string', required: true },
56
- author_name: { type: 'string', required: true, max: 100 },
57
- created_at: { type: 'dateTime', temporalPrecision: 3, defaultTo: () => new Date().toISOString(), indexed: true },
58
- likes: { type: 'number', default: 0, indexed: true },
59
- post_id: { type: 'id', belongsTo: 'posts', as: 'post', required: true }
60
- },
61
- sortableFields: ['created_at', 'likes'],
62
- queryDefaultLimit: 20,
63
- queryMaxLimit: 100
64
- });
65
- await api.resources.comments.createKnexTable();
66
-
67
- // Define tags resource
68
- await api.addResource('tags', {
69
- schema: {
70
- name: { type: 'string', required: true, max: 50, unique: true, indexed: true },
71
- usage_count: { type: 'number', default: 0, indexed: true }
72
- },
73
- relationships: {
74
- posts: {
75
- type: 'manyToMany',
76
- through: 'post_tags',
77
- foreignKey: 'tag_id',
78
- otherKey: 'post_id'
79
- }
80
- },
81
- sortableFields: ['name', 'usage_count'],
82
- queryDefaultLimit: 30
83
- });
84
- await api.resources.tags.createKnexTable();
85
-
86
- // Define pivot table
87
- await api.addResource('post_tags', {
88
- schema: {
89
- post_id: { type: 'id', belongsTo: 'posts', as: 'post', required: true },
90
- tag_id: { type: 'id', belongsTo: 'tags', as: 'tag', required: true }
91
- }
92
- });
93
- await api.resources.post_tags.createKnexTable();
94
-
95
- ```
96
-
97
- **Basic Pagination**
98
-
99
- The API supports two types of pagination:
100
-
101
- 1. **Offset-based pagination** using `page[number]` and `page[size]` parameters
102
- 2. **Cursor-based pagination** using `page[after]` and `page[before]` parameters
103
-
104
- **How the API chooses pagination mode:**
105
-
106
- The API automatically selects the pagination mode based on your query parameters:
107
- - When you specify a page number (`page[number]=2`), you get traditional offset pagination with page counts and totals
108
- - When you only specify a page size (`page[size]=10`) without a page number, the API switches to cursor pagination for better performance
109
- - When you use cursor parameters (`page[after]` or `page[before]`), you explicitly request cursor pagination
110
-
111
- This design encourages the use of cursor pagination (which is more efficient for large datasets) while still supporting traditional page numbers when needed.
112
-
113
- **Offset-based Pagination:**
114
-
115
- ```javascript
116
- // Get first page with default size (10 posts)
117
- const page1 = await api.resources.posts.query({
118
- queryParams: {
119
- page: { number: 1, size: 5 }
120
- }
121
- });
122
- // HTTP: GET /api/posts?page[number]=1
123
- // Returns: {
124
- // data: [ /* 10 posts */ ],
125
- // meta: {
126
- // pagination: {
127
- // page: 1,
128
- // pageSize: 5,
129
- // pageCount: 3,
130
- // total: 25
131
- // }
132
- // }
133
- // }
134
-
135
- // Get second page with custom size
136
- const page2 = await api.resources.posts.query({
137
- queryParams: {
138
- page: { number: 2, size: 5 }
139
- }
140
- });
141
- // HTTP: GET /api/posts?page[number]=2&page[size]=5
142
- // Returns: {
143
- // data: [ /* 5 posts (posts 6-10) */ ],
144
- // meta: {
145
- // pagination: {
146
- // page: 2,
147
- // pageSize: 5,
148
- // pageCount: 5,
149
- // total: 25
150
- // }
151
- // }
152
- // }
153
-
154
- // Get a large page (but limited by queryMaxLimit)
155
- const largePage = await api.resources.posts.query({
156
- queryParams: {
157
- page: { number: 1, size: 100 } // Will be capped at 50 (queryMaxLimit)
158
- }
159
- });
160
- // HTTP: GET /api/posts?page[number]=1&page[size]=100
161
- // Returns: {
162
- // data: [ /* All 25 posts */ ],
163
- // meta: {
164
- // pagination: {
165
- // page: 1,
166
- // pageSize: 50, // Capped at queryMaxLimit
167
- // pageCount: 1,
168
- // total: 25
169
- // }
170
- // }
171
- // }
172
-
173
- console.log('Page 1 posts:', page1.data.length);
174
- console.log('Page 1 post data:', page1.data);
175
- console.log('Page 2 posts:', page2.data.length);
176
- console.log('Page 2 post data:', page2.data);
177
- console.log('Large page posts:', largePage.data.length);
178
- console.log('Page 1 metadata:', page1.meta);
179
- ```
180
-
181
- **Expected Output**
182
-
183
- ```text
184
- Page 1 posts: 5
185
- Page 1 post data: [
186
- {
187
- id: '25',
188
- title: 'Post 25: Odd Number Post',
189
- content: "This is the content of post number 25. It's a odd numbered post.",
190
- view_count: 823,
191
- published_at: 2025-07-28T03:57:39.711Z,
192
- created_at: 2025-07-28T03:57:39.712Z,
193
- comments_ids: [],
194
- tags_ids: [ '1', '2', '3', '4', '5' ]
195
- },
196
- {
197
- id: '24',
198
- title: 'Post 24: Even Number Post',
199
- content: "This is the content of post number 24. It's a even numbered post.",
200
- view_count: 765,
201
- published_at: 2025-07-27T03:57:39.707Z,
202
- created_at: 2025-07-28T03:57:39.708Z,
203
- comments_ids: [],
204
- tags_ids: [ '1', '2', '3', '4', '5' ]
205
- },
206
- {
207
- id: '23',
208
- title: 'Post 23: Odd Number Post',
209
- content: "This is the content of post number 23. It's a odd numbered post.",
210
- view_count: 196,
211
- published_at: 2025-07-26T03:57:39.705Z,
212
- created_at: 2025-07-28T03:57:39.706Z,
213
- comments_ids: [],
214
- tags_ids: [ '1', '2', '3' ]
215
- },
216
- {
217
- id: '22',
218
- title: 'Post 22: Even Number Post',
219
- content: "This is the content of post number 22. It's a even numbered post.",
220
- view_count: 987,
221
- published_at: 2025-07-25T03:57:39.703Z,
222
- created_at: 2025-07-28T03:57:39.703Z,
223
- comments_ids: [],
224
- tags_ids: [ '1', '2', '3' ]
225
- },
226
- {
227
- id: '21',
228
- title: 'Post 21: Odd Number Post',
229
- content: "This is the content of post number 21. It's a odd numbered post.",
230
- view_count: 28,
231
- published_at: 2025-07-24T03:57:39.701Z,
232
- created_at: 2025-07-28T03:57:39.701Z,
233
- comments_ids: [],
234
- tags_ids: [ '1', '2', '3' ]
235
- }
236
- ]
237
- Page 2 posts: 5
238
- Page 2 post data: [
239
- {
240
- id: '20',
241
- title: 'Post 20: Even Number Post',
242
- content: "This is the content of post number 20. It's a even numbered post.",
243
- view_count: 626,
244
- published_at: 2025-07-23T03:57:39.699Z,
245
- created_at: 2025-07-28T03:57:39.699Z,
246
- comments_ids: [],
247
- tags_ids: [ '1', '2', '3' ]
248
- },
249
- {
250
- id: '19',
251
- title: 'Post 19: Odd Number Post',
252
- content: "This is the content of post number 19. It's a odd numbered post.",
253
- view_count: 487,
254
- published_at: 2025-07-22T03:57:39.696Z,
255
- created_at: 2025-07-28T03:57:39.697Z,
256
- comments_ids: [],
257
- tags_ids: [ '1', '2', '3', '4' ]
258
- },
259
- {
260
- id: '18',
261
- title: 'Post 18: Even Number Post',
262
- content: "This is the content of post number 18. It's a even numbered post.",
263
- view_count: 685,
264
- published_at: 2025-07-21T03:57:39.694Z,
265
- created_at: 2025-07-28T03:57:39.695Z,
266
- comments_ids: [],
267
- tags_ids: [ '1', '2', '3' ]
268
- },
269
- {
270
- id: '17',
271
- title: 'Post 17: Odd Number Post',
272
- content: "This is the content of post number 17. It's a odd numbered post.",
273
- view_count: 402,
274
- published_at: 2025-07-20T03:57:39.690Z,
275
- created_at: 2025-07-28T03:57:39.691Z,
276
- comments_ids: [],
277
- tags_ids: [ '1', '2', '3', '4', '5' ]
278
- },
279
- {
280
- id: '16',
281
- title: 'Post 16: Even Number Post',
282
- content: "This is the content of post number 16. It's a even numbered post.",
283
- view_count: 705,
284
- published_at: 2025-07-19T03:57:39.685Z,
285
- created_at: 2025-07-28T03:57:39.686Z,
286
- comments_ids: [],
287
- tags_ids: [ '1', '2', '3', '4', '5' ]
288
- }
289
- ]
290
- Large page posts: 25
291
- Page 1 metadata: {
292
- pagination: { page: 1, pageSize: 5, pageCount: 5, total: 25, hasMore: true }
293
- }
294
- ```
295
-
296
- **Cursor-based Pagination:**
297
-
298
- Cursor-based pagination is ideal for real-time data or when users are scrolling through results. It provides stable pagination even when new records are added:
299
-
300
- ```javascript
301
- // Get first page of posts (newest first)
302
- const firstPage = await api.resources.posts.query({
303
- queryParams: {
304
- page: { size: 5 }, // Using size without number triggers cursor pagination
305
- sort: ['-created_at']
306
- }
307
- });
308
- // Returns: {
309
- // data: [ /* 5 posts */ ],
310
- // meta: {
311
- // pagination: {
312
- // pageSize: 5,
313
- // hasMore: true,
314
- // cursor: { next: 'created_at:2024-07-25T10%3A30%3A00.000Z' }
315
- // }
316
- // }
317
- // }
318
-
319
- // Get next page using the cursor from meta
320
- const nextPage = await api.resources.posts.query({
321
- queryParams: {
322
- page: {
323
- size: 5,
324
- after: firstPage.meta.pagination.cursor.next
325
- },
326
- sort: ['-created_at']
327
- }
328
- });
329
-
330
- // Get second page using the cursor from first page
331
- const secondPage = await api.resources.posts.query({
332
- queryParams: {
333
- page: {
334
- size: 5,
335
- after: firstPage.meta.pagination.cursor?.next
336
- },
337
- sort: ['-created_at']
338
- }
339
- });
340
-
341
- // For backward pagination (less common), use the 'prev' cursor
342
- // Note: This requires the implementation to provide prev cursors
343
- if (secondPage.meta.pagination.cursor?.prev) {
344
- const backToFirstPage = await api.resources.posts.query({
345
- queryParams: {
346
- page: {
347
- size: 5,
348
- before: secondPage.meta.pagination.cursor.prev
349
- },
350
- sort: ['-created_at']
351
- }
352
- });
353
- }
354
-
355
- console.log('First page:', inspect(firstPage));
356
- console.log('\nSecond page:', inspect(secondPage));
357
- ```
358
-
359
- **Expected output**
360
-
361
- ```
362
- First page: {
363
- data: [
364
- {
365
- id: '25',
366
- title: 'Post 25: Odd Number Post',
367
- content: "This is the content of post number 25. It's a odd numbered post.",
368
- view_count: 142,
369
- published_at: 2025-07-28T04:06:39.821Z,
370
- created_at: 2025-07-28T04:06:39.821Z,
371
- comments_ids: [],
372
- tags_ids: [ '1', '2', '3', '4' ]
373
- },
374
- {
375
- id: '24',
376
- title: 'Post 24: Even Number Post',
377
- content: "This is the content of post number 24. It's a even numbered post.",
378
- view_count: 497,
379
- published_at: 2025-07-27T04:06:39.818Z,
380
- created_at: 2025-07-28T04:06:39.818Z,
381
- comments_ids: [],
382
- tags_ids: [ '1', '2', '3', '4', '5' ]
383
- },
384
- {
385
- id: '23',
386
- title: 'Post 23: Odd Number Post',
387
- content: "This is the content of post number 23. It's a odd numbered post.",
388
- view_count: 167,
389
- published_at: 2025-07-26T04:06:39.815Z,
390
- created_at: 2025-07-28T04:06:39.816Z,
391
- comments_ids: [],
392
- tags_ids: [ '1', '2', '3', '4', '5' ]
393
- },
394
- {
395
- id: '22',
396
- title: 'Post 22: Even Number Post',
397
- content: "This is the content of post number 22. It's a even numbered post.",
398
- view_count: 738,
399
- published_at: 2025-07-25T04:06:39.813Z,
400
- created_at: 2025-07-28T04:06:39.813Z,
401
- comments_ids: [],
402
- tags_ids: [ '1', '2', '3' ]
403
- },
404
- {
405
- id: '21',
406
- title: 'Post 21: Odd Number Post',
407
- content: "This is the content of post number 21. It's a odd numbered post.",
408
- view_count: 41,
409
- published_at: 2025-07-24T04:06:39.810Z,
410
- created_at: 2025-07-28T04:06:39.811Z,
411
- comments_ids: [],
412
- tags_ids: [ '1', '2', '3' ]
413
- }
414
- ],
415
- meta: {
416
- pagination: {
417
- pageSize: 5,
418
- hasMore: true,
419
- cursor: { next: 'created_at:1753675599811' }
420
- }
421
- },
422
- links: {
423
- self: '/api/posts?sort=-created_at&&&page[size]=5',
424
- first: '/api/posts?sort=-created_at&&&page[size]=5',
425
- next: '/api/posts?sort=-created_at&&&page[size]=5&page[after]=created_at:1753675599811'
426
- }
427
- }
428
-
429
- Second page: {
430
- data: [
431
- {
432
- id: '20',
433
- title: 'Post 20: Even Number Post',
434
- content: "This is the content of post number 20. It's a even numbered post.",
435
- view_count: 149,
436
- published_at: 2025-07-23T04:06:39.808Z,
437
- created_at: 2025-07-28T04:06:39.809Z,
438
- comments_ids: [],
439
- tags_ids: [ '1', '2', '3', '4' ]
440
- },
441
- {
442
- id: '19',
443
- title: 'Post 19: Odd Number Post',
444
- content: "This is the content of post number 19. It's a odd numbered post.",
445
- view_count: 562,
446
- published_at: 2025-07-22T04:06:39.805Z,
447
- created_at: 2025-07-28T04:06:39.806Z,
448
- comments_ids: [],
449
- tags_ids: [ '1', '2', '3', '4', '5' ]
450
- },
451
- {
452
- id: '18',
453
- title: 'Post 18: Even Number Post',
454
- content: "This is the content of post number 18. It's a even numbered post.",
455
- view_count: 146,
456
- published_at: 2025-07-21T04:06:39.803Z,
457
- created_at: 2025-07-28T04:06:39.803Z,
458
- comments_ids: [],
459
- tags_ids: [ '1', '2', '3' ]
460
- },
461
- {
462
- id: '17',
463
- title: 'Post 17: Odd Number Post',
464
- content: "This is the content of post number 17. It's a odd numbered post.",
465
- view_count: 689,
466
- published_at: 2025-07-20T04:06:39.798Z,
467
- created_at: 2025-07-28T04:06:39.799Z,
468
- comments_ids: [],
469
- tags_ids: [ '1', '2', '3', '4' ]
470
- },
471
- {
472
- id: '16',
473
- title: 'Post 16: Even Number Post',
474
- content: "This is the content of post number 16. It's a even numbered post.",
475
- view_count: 215,
476
- published_at: 2025-07-19T04:06:39.796Z,
477
- created_at: 2025-07-28T04:06:39.797Z,
478
- comments_ids: [],
479
- tags_ids: [ '1', '2', '3' ]
480
- }
481
- ],
482
- meta: {
483
- pagination: {
484
- pageSize: 5,
485
- hasMore: true,
486
- cursor: { next: 'created_at:1753675599797' }
487
- }
488
- },
489
- links: {
490
- self: '/api/posts?sort=-created_at&&&page[size]=5&page[after]=created_at:1753675599811',
491
- first: '/api/posts?sort=-created_at&&&page[size]=5',
492
- next: '/api/posts?sort=-created_at&&&page[size]=5&page[after]=created_at:1753675599797'
493
- }
494
- }
495
- ```
496
-
497
- When sorting happens
498
-
499
- ```javascript
500
- // Multi-field cursor pagination example
501
- const multiFieldPage = await api.resources.posts.query({
502
- queryParams: {
503
- page: { size: 5 },
504
- sort: ['view_count', '-created_at'] // Sort by view_count first, then by created_at DESC
505
- }
506
- });
507
-
508
- // The cursor will contain both fields: "view_count:142,created_at:2024-07-25T10%3A30%3A00.000Z"
509
- // This ensures no records are skipped even if many posts have the same view_count
510
- const nextMultiFieldPage = await api.resources.posts.query({
511
- queryParams: {
512
- page: {
513
- size: 5,
514
- after: multiFieldPage.meta.pagination.cursor.next
515
- },
516
- sort: ['view_count', '-created_at']
517
- }
518
- });
519
-
520
- console.log('First multiField page:', inspect(multiFieldPage));
521
- console.log('Next multiField page:', inspect(nextMultiFieldPage));
522
- ```
523
-
524
- **Expected output**:
525
-
526
- ```text
527
- First multiField page: {
528
- data: [
529
- {
530
- id: '4',
531
- title: 'Post 4: Even Number Post',
532
- content: "This is the content of post number 4. It's a even numbered post.",
533
- view_count: 19,
534
- published_at: 2025-07-07T04:16:32.004Z,
535
- created_at: 2025-07-28T04:16:32.005Z,
536
- comments_ids: [
537
- '30', '31', '32',
538
- '33', '34', '35',
539
- '36', '37', '38',
540
- '39'
541
- ],
542
- tags_ids: [ '1', '2', '3' ]
543
- },
544
- {
545
- id: '13',
546
- title: 'Post 13: Odd Number Post',
547
- content: "This is the content of post number 13. It's a odd numbered post.",
548
- view_count: 25,
549
- published_at: 2025-07-16T04:16:32.041Z,
550
- created_at: 2025-07-28T04:16:32.042Z,
551
- comments_ids: [],
552
- tags_ids: [ '1', '2', '3', '4', '5' ]
553
- },
554
- {
555
- id: '19',
556
- title: 'Post 19: Odd Number Post',
557
- content: "This is the content of post number 19. It's a odd numbered post.",
558
- view_count: 28,
559
- published_at: 2025-07-22T04:16:32.066Z,
560
- created_at: 2025-07-28T04:16:32.066Z,
561
- comments_ids: [],
562
- tags_ids: [ '1', '2', '3', '4', '5' ]
563
- },
564
- {
565
- id: '7',
566
- title: 'Post 7: Odd Number Post',
567
- content: "This is the content of post number 7. It's a odd numbered post.",
568
- view_count: 41,
569
- published_at: 2025-07-10T04:16:32.018Z,
570
- created_at: 2025-07-28T04:16:32.019Z,
571
- comments_ids: [],
572
- tags_ids: [ '1', '2', '3', '4' ]
573
- },
574
- {
575
- id: '20',
576
- title: 'Post 20: Even Number Post',
577
- content: "This is the content of post number 20. It's a even numbered post.",
578
- view_count: 74,
579
- published_at: 2025-07-23T04:16:32.069Z,
580
- created_at: 2025-07-28T04:16:32.069Z,
581
- comments_ids: [],
582
- tags_ids: [ '1', '2', '3' ]
583
- }
584
- ],
585
- meta: {
586
- pagination: {
587
- pageSize: 5,
588
- hasMore: true,
589
- cursor: { next: 'view_count:74,created_at:1753676192069' }
590
- }
591
- },
592
- links: {
593
- self: '/api/posts?sort=view_count&sort=-created_at&&&page[size]=5',
594
- first: '/api/posts?sort=view_count&sort=-created_at&&&page[size]=5',
595
- next: '/api/posts?sort=view_count&sort=-created_at&&&page[size]=5&page[after]=view_count:74,created_at:1753676192069'
596
- }
597
- }
598
- Next multiField page: {
599
- data: [
600
- {
601
- id: '6',
602
- title: 'Post 6: Even Number Post',
603
- content: "This is the content of post number 6. It's a even numbered post.",
604
- view_count: 148,
605
- published_at: 2025-07-09T04:16:32.011Z,
606
- created_at: 2025-07-28T04:16:32.012Z,
607
- comments_ids: [],
608
- tags_ids: [ '1', '2', '3', '4', '5' ]
609
- },
610
- {
611
- id: '22',
612
- title: 'Post 22: Even Number Post',
613
- content: "This is the content of post number 22. It's a even numbered post.",
614
- view_count: 161,
615
- published_at: 2025-07-25T04:16:32.073Z,
616
- created_at: 2025-07-28T04:16:32.074Z,
617
- comments_ids: [],
618
- tags_ids: [ '1', '2', '3', '4', '5' ]
619
- },
620
- {
621
- id: '5',
622
- title: 'Post 5: Odd Number Post',
623
- content: "This is the content of post number 5. It's a odd numbered post.",
624
- view_count: 235,
625
- published_at: 2025-07-08T04:16:32.007Z,
626
- created_at: 2025-07-28T04:16:32.008Z,
627
- comments_ids: [
628
- '40', '41', '42',
629
- '43', '44', '45',
630
- '46', '47', '48',
631
- '49', '50', '51'
632
- ],
633
- tags_ids: [ '1', '2', '3', '4', '5' ]
634
- },
635
- {
636
- id: '9',
637
- title: 'Post 9: Odd Number Post',
638
- content: "This is the content of post number 9. It's a odd numbered post.",
639
- view_count: 249,
640
- published_at: 2025-07-12T04:16:32.024Z,
641
- created_at: 2025-07-28T04:16:32.026Z,
642
- comments_ids: [],
643
- tags_ids: [ '1', '2', '3', '4', '5' ]
644
- },
645
- {
646
- id: '17',
647
- title: 'Post 17: Odd Number Post',
648
- content: "This is the content of post number 17. It's a odd numbered post.",
649
- view_count: 290,
650
- published_at: 2025-07-20T04:16:32.055Z,
651
- created_at: 2025-07-28T04:16:32.056Z,
652
- comments_ids: [],
653
- tags_ids: [ '1', '2', '3', '4', '5' ]
654
- }
655
- ],
656
- meta: {
657
- pagination: {
658
- pageSize: 5,
659
- hasMore: true,
660
- cursor: { next: 'view_count:290,created_at:1753676192056' }
661
- }
662
- },
663
- links: {
664
- self: '/api/posts?sort=view_count&sort=-created_at&&&page[size]=5&page[after]=view_count:74,created_at:1753676192069',
665
- first: '/api/posts?sort=view_count&sort=-created_at&&&page[size]=5',
666
- next: '/api/posts?sort=view_count&sort=-created_at&&&page[size]=5&page[after]=view_count:290,created_at:1753676192056'
667
- }
668
- }
669
- ```
670
-
671
-
672
- **Cursor Pagination Notes:**
673
- - Cursors are simple strings containing the sort field values (format: `field:value,field2:value2`)
674
- - The API automatically generates appropriate WHERE clauses based on sort direction
675
- - Cursor pagination is more efficient for large datasets as it doesn't need to count all records
676
- - Works seamlessly with any sort field, not just timestamps
677
- - **Multi-field sorting is fully supported** - When sorting by multiple fields (e.g., `sort: ['category', 'name']`), the cursor includes all sort fields and correctly handles records with duplicate values in the first sort field
678
-
679
- **Pagination with Sorting**
680
-
681
- You can combine pagination with sorting. The sort order is maintained across pages:
682
-
683
- ```javascript
684
- // Get posts sorted by view count (highest first)
685
- const popularPosts = await api.resources.posts.query({
686
- queryParams: {
687
- sort: ['-view_count'], // Minus prefix means descending
688
- page: { number: 1, size: 5 }
689
- }
690
- });
691
- // HTTP: GET /api/posts?sort=-view_count&page[number]=1&page[size]=5
692
- // Returns: {
693
- // data: [
694
- // { id: '5', title: 'Post 5', view_count: 950, ... },
695
- // { id: '15', title: 'Post 15', view_count: 850, ... },
696
- // { id: '20', title: 'Post 20', view_count: 750, ... },
697
- // { id: '10', title: 'Post 10', view_count: 650, ... },
698
- // { id: '25', title: 'Post 25', view_count: 550, ... }
699
- // ],
700
- // meta: { pagination: { page: 1, pageSize: 5, pageCount: 5, total: 25 } }
701
- // }
702
-
703
- // Get posts sorted by multiple fields
704
- const multiSort = await api.resources.posts.query({
705
- queryParams: {
706
- sort: ['published_at', '-view_count'], // Oldest first, then by views
707
- page: { number: 1, size: 5 }
708
- }
709
- });
710
- // HTTP: GET /api/posts?sort=published_at,-view_count&page[number]=1&page[size]=5
711
- // Returns: {
712
- // data: [
713
- // { id: '1', title: 'Post 1', published_at: '2024-01-01T00:00:00Z', view_count: 100, ... },
714
- // { id: '2', title: 'Post 2', published_at: '2024-01-02T00:00:00Z', view_count: 200, ... },
715
- // // ... posts sorted first by date, then by views within same date
716
- // ],
717
- // meta: { pagination: { page: 1, pageSize: 5, pageCount: 5, total: 25 } }
718
- // }
719
-
720
- console.log('Popular sort:', inspect(popularPosts));
721
- console.log('Multisort:', inspect(multiSort));
722
- ```
723
-
724
- **Expected output**:
725
-
726
- ```text
727
- Popular sort: {
728
- data: [
729
- {
730
- id: '6',
731
- title: 'Post 6: Even Number Post',
732
- content: "This is the content of post number 6. It's a even numbered post.",
733
- view_count: 910,
734
- published_at: 2025-07-09T04:21:13.403Z,
735
- created_at: 2025-07-28T04:21:13.404Z,
736
- comments_ids: [],
737
- tags_ids: [ '1', '2', '3' ]
738
- },
739
- {
740
- id: '24',
741
- title: 'Post 24: Even Number Post',
742
- content: "This is the content of post number 24. It's a even numbered post.",
743
- view_count: 871,
744
- published_at: 2025-07-27T04:21:13.454Z,
745
- created_at: 2025-07-28T04:21:13.454Z,
746
- comments_ids: [],
747
- tags_ids: [ '1', '2', '3', '4' ]
748
- },
749
- {
750
- id: '18',
751
- title: 'Post 18: Even Number Post',
752
- content: "This is the content of post number 18. It's a even numbered post.",
753
- view_count: 870,
754
- published_at: 2025-07-21T04:21:13.437Z,
755
- created_at: 2025-07-28T04:21:13.438Z,
756
- comments_ids: [],
757
- tags_ids: [ '1', '2', '3', '4', '5' ]
758
- },
759
- {
760
- id: '8',
761
- title: 'Post 8: Even Number Post',
762
- content: "This is the content of post number 8. It's a even numbered post.",
763
- view_count: 848,
764
- published_at: 2025-07-11T04:21:13.410Z,
765
- created_at: 2025-07-28T04:21:13.410Z,
766
- comments_ids: [],
767
- tags_ids: [ '1', '2', '3' ]
768
- },
769
- {
770
- id: '22',
771
- title: 'Post 22: Even Number Post',
772
- content: "This is the content of post number 22. It's a even numbered post.",
773
- view_count: 838,
774
- published_at: 2025-07-25T04:21:13.449Z,
775
- created_at: 2025-07-28T04:21:13.450Z,
776
- comments_ids: [],
777
- tags_ids: [ '1', '2', '3', '4' ]
778
- }
779
- ],
780
- meta: {
781
- pagination: { page: 1, pageSize: 5, pageCount: 5, total: 25, hasMore: true }
782
- },
783
- links: {
784
- self: '/api/posts?sort=-view_count&&&page[number]=1&page[size]=5',
785
- first: '/api/posts?sort=-view_count&&&page[number]=1&page[size]=5',
786
- last: '/api/posts?sort=-view_count&&&page[number]=5&page[size]=5',
787
- next: '/api/posts?sort=-view_count&&&page[number]=2&page[size]=5'
788
- }
789
- }
790
- Multisort: {
791
- data: [
792
- {
793
- id: '1',
794
- title: 'Post 1: Odd Number Post',
795
- content: "This is the content of post number 1. It's a odd numbered post.",
796
- view_count: 468,
797
- published_at: 2025-07-04T04:21:13.385Z,
798
- created_at: 2025-07-28T04:21:13.386Z,
799
- comments_ids: [
800
- '1', '2', '3', '4',
801
- '5', '6', '7', '8',
802
- '9', '10', '11', '12'
803
- ],
804
- tags_ids: [ '1', '2', '3' ]
805
- },
806
- {
807
- id: '2',
808
- title: 'Post 2: Even Number Post',
809
- content: "This is the content of post number 2. It's a even numbered post.",
810
- view_count: 652,
811
- published_at: 2025-07-05T04:21:13.389Z,
812
- created_at: 2025-07-28T04:21:13.390Z,
813
- comments_ids: [
814
- '13', '14', '15',
815
- '16', '17', '18',
816
- '19', '20', '21',
817
- '22', '23', '24'
818
- ],
819
- tags_ids: [ '1', '2', '3', '4' ]
820
- },
821
- {
822
- id: '3',
823
- title: 'Post 3: Odd Number Post',
824
- content: "This is the content of post number 3. It's a odd numbered post.",
825
- view_count: 198,
826
- published_at: 2025-07-06T04:21:13.393Z,
827
- created_at: 2025-07-28T04:21:13.394Z,
828
- comments_ids: [
829
- '25', '26', '27',
830
- '28', '29', '30',
831
- '31', '32', '33',
832
- '34', '35', '36'
833
- ],
834
- tags_ids: [ '1', '2', '3', '4', '5' ]
835
- },
836
- {
837
- id: '4',
838
- title: 'Post 4: Even Number Post',
839
- content: "This is the content of post number 4. It's a even numbered post.",
840
- view_count: 606,
841
- published_at: 2025-07-07T04:21:13.396Z,
842
- created_at: 2025-07-28T04:21:13.397Z,
843
- comments_ids: [
844
- '37', '38', '39',
845
- '40', '41', '42',
846
- '43', '44', '45',
847
- '46'
848
- ],
849
- tags_ids: [ '1', '2', '3', '4', '5' ]
850
- },
851
- {
852
- id: '5',
853
- title: 'Post 5: Odd Number Post',
854
- content: "This is the content of post number 5. It's a odd numbered post.",
855
- view_count: 142,
856
- published_at: 2025-07-08T04:21:13.400Z,
857
- created_at: 2025-07-28T04:21:13.401Z,
858
- comments_ids: [
859
- '47', '48', '49',
860
- '50', '51', '52',
861
- '53', '54', '55',
862
- '56'
863
- ],
864
- tags_ids: [ '1', '2', '3', '4', '5' ]
865
- }
866
- ],
867
- meta: {
868
- pagination: { page: 1, pageSize: 5, pageCount: 5, total: 25, hasMore: true }
869
- },
870
- links: {
871
- self: '/api/posts?sort=published_at&sort=-view_count&&&page[number]=1&page[size]=5',
872
- first: '/api/posts?sort=published_at&sort=-view_count&&&page[number]=1&page[size]=5',
873
- last: '/api/posts?sort=published_at&sort=-view_count&&&page[number]=5&page[size]=5',
874
- next: '/api/posts?sort=published_at&sort=-view_count&&&page[number]=2&page[size]=5'
875
- }
876
- }
877
- ```
878
-
879
- **Limits and Ordering for Included Relationships**
880
-
881
- When you include relationships in your query, they use the default `limit` and `orderBy` values defined in the relationship schema. These defaults are applied automatically and cannot be overridden in the query:
882
-
883
- ```javascript
884
- // The relationship was defined with default limits:
885
- // relationships: {
886
- // comments: {
887
- // type: 'hasMany',
888
- // target: 'comments',
889
- // foreignKey: 'post_id',
890
- // include: {
891
- // limit: 5, // Always return max 5 comments
892
- // orderBy: ['-created_at'] // Always order by newest first
893
- // }
894
- // }
895
- // }
896
-
897
- // When you include comments, these defaults are automatically applied:
898
- const postsWithComments = await api.resources.posts.query({
899
- queryParams: {
900
- include: ['comments'], // Just specify which relationships to include
901
- page: { number: 1, size: 3 } // This controls posts pagination only
902
- }
903
- });
904
- // HTTP: GET /api/posts?include=comments&page[number]=1&page[size]=3
905
- // Returns: {
906
- // data: [
907
- // { id: '1', title: 'Post 1', ...,
908
- // comments: [ /* Up to 5 comments, ordered by newest first */ ]
909
- // },
910
- // { id: '2', title: 'Post 2', ...,
911
- // comments: [ /* Up to 5 comments, newest first */ ]
912
- // },
913
- // { id: '3', title: 'Post 3', ...,
914
- // comments: [ /* Up to 5 comments, newest first */ ]
915
- // }
916
- // ],
917
- // meta: { pagination: { page: 1, pageSize: 3, pageCount: 9, total: 25 } }
918
- // }
919
-
920
- // Display the results
921
- console.log('Posts with comments:', inspect(postsWithComments));
922
-
923
- // Get posts with both comments and tags, using their configured limits
924
- const postsWithAll = await api.resources.posts.query({
925
- queryParams: {
926
- include: ['comments', 'tags'],
927
- page: { number: 1, size: 2 }
928
- }
929
- });
930
-
931
- console.log('\nPosts with all relationships:', inspect(postsWithAll));
932
- ```
933
-
934
- **Expected Output**
935
-
936
- ```text
937
- Posts with comments: {
938
- data: [
939
- {
940
- id: '25',
941
- title: 'Post 25: Odd Number Post',
942
- content: "This is the content of post number 25. It's a odd numbered post.",
943
- view_count: 483,
944
- published_at: 2025-07-28T04:50:14.529Z,
945
- created_at: 2025-07-28T04:50:14.530Z,
946
- comments_ids: [],
947
- tags_ids: [ '1', '2', '3', '4' ]
948
- },
949
- {
950
- id: '24',
951
- title: 'Post 24: Even Number Post',
952
- content: "This is the content of post number 24. It's a even numbered post.",
953
- view_count: 719,
954
- published_at: 2025-07-27T04:50:14.526Z,
955
- created_at: 2025-07-28T04:50:14.527Z,
956
- comments_ids: [],
957
- tags_ids: [ '1', '2', '3', '4' ]
958
- },
959
- {
960
- id: '23',
961
- title: 'Post 23: Odd Number Post',
962
- content: "This is the content of post number 23. It's a odd numbered post.",
963
- view_count: 167,
964
- published_at: 2025-07-26T04:50:14.523Z,
965
- created_at: 2025-07-28T04:50:14.524Z,
966
- comments_ids: [],
967
- tags_ids: [ '1', '2', '3', '4', '5' ]
968
- }
969
- ],
970
- meta: {
971
- pagination: { page: 1, pageSize: 3, pageCount: 9, total: 25, hasMore: true }
972
- },
973
- links: {
974
- self: '/api/posts?include=comments&&sort=-created_at&page[number]=1&page[size]=3',
975
- first: '/api/posts?include=comments&&sort=-created_at&page[number]=1&page[size]=3',
976
- last: '/api/posts?include=comments&&sort=-created_at&page[number]=9&page[size]=3',
977
- next: '/api/posts?include=comments&&sort=-created_at&page[number]=2&page[size]=3'
978
- }
979
- }
980
-
981
- Posts with all relationships: {
982
- data: [
983
- {
984
- id: '25',
985
- title: 'Post 25: Odd Number Post',
986
- content: "This is the content of post number 25. It's a odd numbered post.",
987
- view_count: 483,
988
- published_at: 2025-07-28T04:50:14.529Z,
989
- created_at: 2025-07-28T04:50:14.530Z,
990
- comments_ids: [],
991
- tags_ids: [ '1', '2', '3', '4' ],
992
- tags: [
993
- { id: '1', name: 'tag-01', usage_count: 74, posts_ids: [] },
994
- { id: '2', name: 'tag-02', usage_count: 36, posts_ids: [] },
995
- { id: '3', name: 'tag-03', usage_count: 59, posts_ids: [] },
996
- { id: '4', name: 'tag-04', usage_count: 63, posts_ids: [] }
997
- ]
998
- },
999
- {
1000
- id: '24',
1001
- title: 'Post 24: Even Number Post',
1002
- content: "This is the content of post number 24. It's a even numbered post.",
1003
- view_count: 719,
1004
- published_at: 2025-07-27T04:50:14.526Z,
1005
- created_at: 2025-07-28T04:50:14.527Z,
1006
- comments_ids: [],
1007
- tags_ids: [ '1', '2', '3', '4' ],
1008
- tags: [
1009
- { id: '1', name: 'tag-01', usage_count: 74, posts_ids: [] },
1010
- { id: '2', name: 'tag-02', usage_count: 36, posts_ids: [] },
1011
- { id: '3', name: 'tag-03', usage_count: 59, posts_ids: [] },
1012
- { id: '4', name: 'tag-04', usage_count: 63, posts_ids: [] }
1013
- ]
1014
- }
1015
- ],
1016
- meta: {
1017
- pagination: { page: 1, pageSize: 2, pageCount: 13, total: 25, hasMore: true }
1018
- },
1019
- links: {
1020
- self: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=1&page[size]=2',
1021
- first: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=1&page[size]=2',
1022
- last: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=13&page[size]=2',
1023
- next: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=2&page[size]=2'
1024
- }
1025
- }
1026
- ```
1027
-
1028
- **Pagination Links**
1029
-
1030
- The API automatically generates pagination links following JSON:API specification:
1031
-
1032
- ```javascript
1033
- // Get a page with full pagination info
1034
- const pagedResult = await api.resources.posts.query({
1035
- queryParams: {
1036
- page: { number: 2, size: 5 },
1037
- sort: ['-published_at']
1038
- },
1039
- simplified: false // Use JSON:API format to see links
1040
- });
1041
- // HTTP: GET /api/posts?page[number]=2&page[size]=5&sort=-published_at
1042
- // Returns (JSON:API): {
1043
- // data: [ /* 5 posts */ ],
1044
- // links: {
1045
- // first: '/api/posts?page[number]=1&page[size]=5&sort=-published_at',
1046
- // prev: '/api/posts?page[number]=1&page[size]=5&sort=-published_at',
1047
- // next: '/api/posts?page[number]=3&page[size]=5&sort=-published_at',
1048
- // last: '/api/posts?page[number]=5&page[size]=5&sort=-published_at'
1049
- // },
1050
- // meta: {
1051
- // pagination: {
1052
- // page: 2,
1053
- // pageSize: 5,
1054
- // pageCount: 5,
1055
- // total: 25
1056
- // }
1057
- // }
1058
- // }
1059
-
1060
- console.log('Pagination links:', JSON.stringify(pagedResult.links, null, 2));
1061
- console.log('Pagination meta:', JSON.stringify(pagedResult.meta, null, 2));
1062
- ```
1063
-
1064
- **Expected Output**
1065
-
1066
- ```text
1067
- Pagination links: {
1068
- "self": "/api/posts?sort=-published_at&&&page[number]=2&page[size]=5",
1069
- "first": "/api/posts?sort=-published_at&&&page[number]=1&page[size]=5",
1070
- "last": "/api/posts?sort=-published_at&&&page[number]=5&page[size]=5",
1071
- "prev": "/api/posts?sort=-published_at&&&page[number]=1&page[size]=5",
1072
- "next": "/api/posts?sort=-published_at&&&page[number]=3&page[size]=5"
1073
- }
1074
- Pagination meta: {
1075
- "pagination": {
1076
- "page": 2,
1077
- "pageSize": 5,
1078
- "pageCount": 5,
1079
- "total": 25,
1080
- "hasMore": true
1081
- }
1082
- }
1083
- ```
1084
-
1085
- **Combining Everything: Filters, Sorting, Pagination, and Includes**
1086
-
1087
- ```javascript
1088
- // Complex query combining all features
1089
- const complexQuery = await api.resources.posts.query({
1090
- queryParams: {
1091
- filters: {
1092
- title: 'Even' // Only posts with "Even" in title
1093
- },
1094
- sort: ['-view_count', 'published_at'], // Most viewed first, then oldest
1095
- page: { number: 1, size: 3 },
1096
- include: ['comments', 'tags']
1097
- }
1098
- });
1099
- // HTTP: GET /api/posts?filter[title]=Even&sort=-view_count,published_at&page[number]=1&page[size]=3&include=comments,tags
1100
- // Returns: {
1101
- // data: [
1102
- // { id: '20', title: 'Even Post 20', view_count: 750,
1103
- // comments: [ /* Up to 5 newest */ ],
1104
- // tags: [ /* Up to 10, alphabetical */ ]
1105
- // },
1106
- // { id: '10', title: 'Even Post 10', view_count: 650,
1107
- // comments: [ /* Up to 5 newest */ ],
1108
- // tags: [ /* Up to 10, alphabetical */ ]
1109
- // },
1110
- // { id: '2', title: 'Even Post 2', view_count: 200,
1111
- // comments: [ /* Up to 5 newest */ ],
1112
- // tags: [ /* Up to 10, alphabetical */ ]
1113
- // }
1114
- // ],
1115
- // meta: {
1116
- // pagination: {
1117
- // page: 1,
1118
- // pageSize: 3,
1119
- // pageCount: 4, // 12 "Even" posts total
1120
- // total: 12
1121
- // }
1122
- // }
1123
- // });
1124
-
1125
- console.log('\nComplex query results:', inspect(complexQuery));
1126
- ```
1127
-
1128
- **Expected results:**
1129
-
1130
- ```text
1131
- Complex query results: {
1132
- data: [
1133
- {
1134
- id: '14',
1135
- title: 'Post 14: Even Number Post',
1136
- content: "This is the content of post number 14. It's a even numbered post.",
1137
- view_count: 968,
1138
- published_at: 2025-07-17T05:03:41.480Z,
1139
- created_at: 2025-07-28T05:03:41.480Z,
1140
- comments_ids: [],
1141
- tags_ids: [ '1', '2', '3' ],
1142
- tags: [
1143
- { id: '1', name: 'tag-01', usage_count: 43, posts_ids: [] },
1144
- { id: '2', name: 'tag-02', usage_count: 80, posts_ids: [] },
1145
- { id: '3', name: 'tag-03', usage_count: 6, posts_ids: [] }
1146
- ]
1147
- },
1148
- {
1149
- id: '2',
1150
- title: 'Post 2: Even Number Post',
1151
- content: "This is the content of post number 2. It's a even numbered post.",
1152
- view_count: 844,
1153
- published_at: 2025-07-05T05:03:41.439Z,
1154
- created_at: 2025-07-28T05:03:41.440Z,
1155
- comments_ids: [ '19', '18', '17', '16', '15' ],
1156
- comments: [
1157
- {
1158
- id: '19',
1159
- content: 'Comment 10 on post 2',
1160
- author_name: 'User 3',
1161
- created_at: 2025-07-28T05:03:41.542Z,
1162
- likes: 27
1163
- },
1164
- {
1165
- id: '18',
1166
- content: 'Comment 9 on post 2',
1167
- author_name: 'User 2',
1168
- created_at: 2025-07-28T04:03:41.541Z,
1169
- likes: 27
1170
- },
1171
- {
1172
- id: '17',
1173
- content: 'Comment 8 on post 2',
1174
- author_name: 'User 7',
1175
- created_at: 2025-07-28T03:03:41.539Z,
1176
- likes: 31
1177
- },
1178
- {
1179
- id: '16',
1180
- content: 'Comment 7 on post 2',
1181
- author_name: 'User 10',
1182
- created_at: 2025-07-28T02:03:41.538Z,
1183
- likes: 27
1184
- },
1185
- {
1186
- id: '15',
1187
- content: 'Comment 6 on post 2',
1188
- author_name: 'User 9',
1189
- created_at: 2025-07-28T01:03:41.535Z,
1190
- likes: 46
1191
- }
1192
- ],
1193
- tags_ids: [ '1', '2', '3', '4' ],
1194
- tags: [
1195
- { id: '1', name: 'tag-01', usage_count: 43, posts_ids: [] },
1196
- { id: '2', name: 'tag-02', usage_count: 80, posts_ids: [] },
1197
- { id: '3', name: 'tag-03', usage_count: 6, posts_ids: [] },
1198
- { id: '4', name: 'tag-04', usage_count: 92, posts_ids: [] }
1199
- ]
1200
- },
1201
- {
1202
- id: '18',
1203
- title: 'Post 18: Even Number Post',
1204
- content: "This is the content of post number 18. It's a even numbered post.",
1205
- view_count: 623,
1206
- published_at: 2025-07-21T05:03:41.492Z,
1207
- created_at: 2025-07-28T05:03:41.492Z,
1208
- comments_ids: [],
1209
- tags_ids: [ '1', '2', '3', '4' ],
1210
- tags: [
1211
- { id: '1', name: 'tag-01', usage_count: 43, posts_ids: [] },
1212
- { id: '2', name: 'tag-02', usage_count: 80, posts_ids: [] },
1213
- { id: '3', name: 'tag-03', usage_count: 6, posts_ids: [] },
1214
- { id: '4', name: 'tag-04', usage_count: 92, posts_ids: [] }
1215
- ]
1216
- }
1217
- ],
1218
- meta: {
1219
- pagination: { page: 1, pageSize: 3, pageCount: 4, total: 12, hasMore: true }
1220
- },
1221
- links: {
1222
- self: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=1&page[size]=3',
1223
- first: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=1&page[size]=3',
1224
- last: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=4&page[size]=3',
1225
- next: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=2&page[size]=3'
1226
- }
1227
- }
1228
- ```
1229
-
1230
- The pagination and ordering system is designed to be efficient and predictable:
1231
- - Parent records are paginated and sorted according to the main query
1232
- - Child records are independently paginated and sorted according to their relationship configuration
1233
- - All limits respect the configured maximums to prevent performance issues
1234
- - The system automatically uses SQL window functions for efficient pagination of included records when supported by the database
1235
-
1236
- ---
1237
-
1238
- [Previous: 2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md) | [Back to Guide](./README.md)