json-rest-api 1.0.14 → 1.0.15

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 (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,17 +1,17 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import {
5
- validateJsonApiStructure,
6
- cleanTables,
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import {
5
+ validateJsonApiStructure,
6
+ cleanTables,
7
7
  createJsonApiDocument,
8
8
  assertResourceAttributes
9
- } from './helpers/test-utils.js';
10
- import { createBasicApi } from './fixtures/api-configs.js';
9
+ } from './helpers/test-utils.js'
10
+ import { createBasicApi } from './fixtures/api-configs.js'
11
11
 
12
12
  /**
13
13
  * TEST TEMPLATE - BEST PRACTICES LEARNED
14
- *
14
+ *
15
15
  * CRITICAL RULES:
16
16
  * 1. Create API instance ONLY ONCE in before() - NOT in beforeEach()
17
17
  * 2. Use cleanTables() in beforeEach() to reset data between tests
@@ -19,9 +19,9 @@ import { createBasicApi } from './fixtures/api-configs.js';
19
19
  * 4. Clean up any intervals/timers in after() hook
20
20
  * 5. SQLite returns numeric IDs - JSON:API expects string IDs
21
21
  * 6. For auth tests, context.auth must be set via JWT token or passed manually
22
- *
22
+ *
23
23
  * NOTICE TO CLAUDE/AI ASSISTANTS:
24
- *
24
+ *
25
25
  * API CONFIGURATION RULES - EXTREMELY IMPORTANT:
26
26
  * 1. NEVER add resources with api.addResource() in test files!
27
27
  * 2. ALL resources MUST be defined in api-configs.js
@@ -38,13 +38,13 @@ import { createBasicApi } from './fixtures/api-configs.js';
38
38
  * }
39
39
  * 5. Example structure in api-configs.js:
40
40
  * - createBasicApi() - has countries, publishers, authors, books
41
- * - createAuthApi() - might have users, roles, permissions
41
+ * - createAuthApi() - might have users, roles, permissions
42
42
  * - createYourCustomApi() - whatever resources you need
43
- *
43
+ *
44
44
  * RESOURCE CREATION RULE (REPEAT FOR EMPHASIS):
45
45
  * Resources are ONLY created in api-configs.js, NEVER in test files!
46
46
  * If you need new resources, create a new function in api-configs.js!
47
- *
47
+ *
48
48
  * DATA ACCESS RULES - EXTREMELY IMPORTANT:
49
49
  * 1. NEVER query the database directly with knex('table').select()
50
50
  * 2. ALWAYS use API methods to check data:
@@ -57,10 +57,10 @@ import { createBasicApi } from './fixtures/api-configs.js';
57
57
  * - Return data in the expected JSON:API format
58
58
  * - Include computed fields and enriched attributes
59
59
  * 4. Direct DB queries will give you raw data that doesn't match what the API returns!
60
- *
60
+ *
61
61
  * BAD EXAMPLE:
62
62
  * const record = await knex('basic_countries').where('id', 1).first(); // NO!
63
- *
63
+ *
64
64
  * GOOD EXAMPLE:
65
65
  * const result = await api.resources.countries.get({ id: 1 });
66
66
  * const record = result.data; // YES!
@@ -73,10 +73,10 @@ const knex = knexLib({
73
73
  filename: ':memory:'
74
74
  },
75
75
  useNullAsDefault: true
76
- });
76
+ })
77
77
 
78
78
  // API instance that persists across ALL tests
79
- let api;
79
+ let api
80
80
 
81
81
  describe('Your Feature Name (Template Example)', () => {
82
82
  // IMPORTANT: before() runs ONCE for the entire test suite
@@ -84,18 +84,18 @@ describe('Your Feature Name (Template Example)', () => {
84
84
  // Create API instance ONCE - this is reused for all tests
85
85
  // NOTE: If you need different resources than what createBasicApi provides,
86
86
  // create a new function in api-configs.js!
87
- api = await createBasicApi(knex);
88
-
87
+ api = await createBasicApi(knex)
88
+
89
89
  // Install your plugin if testing a specific plugin
90
90
  // await api.use(YourPlugin, {
91
91
  // option1: 'value1',
92
92
  // option2: 'value2'
93
93
  // });
94
-
94
+
95
95
  // NO api.addResource() HERE! Resources are defined in api-configs.js!
96
96
  // createBasicApi already created tables for: countries, publishers, authors, books
97
- });
98
-
97
+ })
98
+
99
99
  // IMPORTANT: after() cleans up resources
100
100
  after(async () => {
101
101
  // Clean up any intervals/timers your plugin might have created
@@ -103,76 +103,76 @@ describe('Your Feature Name (Template Example)', () => {
103
103
  // if (api.vars.yourPluginCleanupJob) {
104
104
  // clearInterval(api.vars.yourPluginCleanupJob);
105
105
  // }
106
-
106
+
107
107
  // Always destroy knex connection to allow tests to exit
108
- await knex.destroy();
109
- });
110
-
108
+ await knex.destroy()
109
+ })
110
+
111
111
  // IMPORTANT: beforeEach() cleans data but does NOT recreate API
112
112
  beforeEach(async () => {
113
113
  // Clean all tables - list ALL tables your tests use
114
114
  await cleanTables(knex, [
115
115
  'basic_countries', // From createBasicApi
116
116
  'basic_publishers', // From createBasicApi
117
- 'basic_authors', // From createBasicApi
117
+ 'basic_authors', // From createBasicApi
118
118
  'basic_books', // From createBasicApi
119
119
  'basic_book_authors' // From createBasicApi (junction table)
120
120
  // Add any additional tables used by your tests here
121
- ]);
122
- });
123
-
121
+ ])
122
+ })
123
+
124
124
  // ONE SIMPLE EXAMPLE TEST
125
125
  it('should create and retrieve a country', async () => {
126
126
  // Create a country using the API
127
127
  const doc = createJsonApiDocument('countries', {
128
128
  name: 'Test Country',
129
129
  code: 'TC'
130
- });
131
-
130
+ })
131
+
132
132
  // Create the country
133
133
  // IMPORTANT: Context is SECOND parameter, not inside first parameter!
134
134
  const createResult = await api.resources.countries.post({
135
135
  inputRecord: doc,
136
136
  simplified: false
137
- }, { /* optional context goes here */ });
138
-
137
+ }, { /* optional context goes here */ })
138
+
139
139
  // Validate the creation response
140
- validateJsonApiStructure(createResult);
141
- assert.equal(createResult.data.type, 'countries');
140
+ validateJsonApiStructure(createResult)
141
+ assert.equal(createResult.data.type, 'countries')
142
142
  assertResourceAttributes(createResult.data, {
143
143
  name: 'Test Country',
144
144
  code: 'TC'
145
- });
146
-
145
+ })
146
+
147
147
  // Retrieve the country we just created
148
148
  const getResult = await api.resources.countries.get({
149
149
  id: createResult.data.id,
150
150
  simplified: false
151
- });
152
-
151
+ })
152
+
153
153
  // Validate the retrieved country
154
- validateJsonApiStructure(getResult);
155
- assert.equal(getResult.data.id, createResult.data.id);
156
- assert.equal(getResult.data.attributes.name, 'Test Country');
157
- assert.equal(getResult.data.attributes.code, 'TC');
158
- });
159
- });
154
+ validateJsonApiStructure(getResult)
155
+ assert.equal(getResult.data.id, createResult.data.id)
156
+ assert.equal(getResult.data.attributes.name, 'Test Country')
157
+ assert.equal(getResult.data.attributes.code, 'TC')
158
+ })
159
+ })
160
160
 
161
161
  /**
162
162
  * COMMON PITFALLS TO AVOID:
163
- *
163
+ *
164
164
  * 1. DON'T create API in beforeEach - it's expensive and unnecessary
165
165
  * 2. DON'T forget to clean tables between tests
166
166
  * 3. DON'T pass context inside params object - it's a separate parameter
167
167
  * 4. DON'T assume IDs are strings - SQLite returns numbers
168
168
  * 5. DON'T forget to clean up intervals/timers
169
169
  * 6. DON'T skip error cases - test both success and failure paths
170
- *
170
+ *
171
171
  * DEBUGGING TIPS:
172
- *
172
+ *
173
173
  * 1. Use console.log liberally when debugging, but remove before committing
174
174
  * 2. Check if your auth context is properly structured
175
175
  * 3. Validate that required fields are present in your test data
176
176
  * 4. Remember that HTTP routes have /api/ prefix by default
177
177
  * 5. Use debugger statements to pause execution and inspect state
178
- */
178
+ */
@@ -1,27 +1,27 @@
1
- import { describe, it, before, beforeEach, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { Api } from 'hooked-api';
1
+ import { describe, it, before, beforeEach, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { Api } from 'hooked-api'
5
5
 
6
- import { RestApiPlugin, RestApiAnyapiKnexPlugin } from '../index.js';
7
- import { ensureAnyApiSchema } from '../plugins/core/lib/anyapi/schema-utils.js';
6
+ import { RestApiPlugin, RestApiAnyapiKnexPlugin } from '../index.js'
7
+ import { ensureAnyApiSchema } from '../plugins/core/lib/anyapi/schema-utils.js'
8
8
 
9
9
  const knex = knexLib({
10
10
  client: 'better-sqlite3',
11
11
  connection: { filename: ':memory:' },
12
12
  useNullAsDefault: true,
13
- });
13
+ })
14
14
 
15
- let api;
15
+ let api
16
16
 
17
17
  describe('AnyAPI Knex Plugin - Basic Attributes', () => {
18
18
  before(async () => {
19
19
  try {
20
- await ensureAnyApiSchema(knex);
20
+ await ensureAnyApiSchema(knex)
21
21
 
22
- api = new Api({ name: 'anyapi-test', log: { level: 'warn' } });
23
- await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false });
24
- await api.use(RestApiAnyapiKnexPlugin, { knex });
22
+ api = new Api({ name: 'anyapi-test', log: { level: 'warn' } })
23
+ await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
24
+ await api.use(RestApiAnyapiKnexPlugin, { knex })
25
25
 
26
26
  await api.addResource('countries', {
27
27
  schema: {
@@ -29,25 +29,25 @@ describe('AnyAPI Knex Plugin - Basic Attributes', () => {
29
29
  name: { type: 'string', required: true },
30
30
  code: { type: 'string', required: true },
31
31
  },
32
- });
32
+ })
33
33
 
34
34
  if (api.resources.countries?.createKnexTable) {
35
- await api.resources.countries.createKnexTable();
35
+ await api.resources.countries.createKnexTable()
36
36
  }
37
37
  } catch (error) {
38
- console.error('Setup failed', error);
39
- throw error;
38
+ console.error('Setup failed', error)
39
+ throw error
40
40
  }
41
- });
41
+ })
42
42
 
43
43
  after(async () => {
44
- await api.release();
45
- await knex.destroy();
46
- });
44
+ await api.release()
45
+ await knex.destroy()
46
+ })
47
47
 
48
48
  beforeEach(async () => {
49
- await knex('any_records').truncate();
50
- });
49
+ await knex('any_records').truncate()
50
+ })
51
51
 
52
52
  it('creates and retrieves records', async () => {
53
53
  const result = await api.resources.countries.post({
@@ -62,24 +62,24 @@ describe('AnyAPI Knex Plugin - Basic Attributes', () => {
62
62
  },
63
63
  simplified: false,
64
64
  }).catch((error) => {
65
- console.error('POST failed', error);
66
- throw error;
67
- });
65
+ console.error('POST failed', error)
66
+ throw error
67
+ })
68
68
 
69
- assert.ok(result.data.id, 'should return new id');
70
- assert.equal(result.data.attributes.name, 'Australia');
69
+ assert.ok(result.data.id, 'should return new id')
70
+ assert.equal(result.data.attributes.name, 'Australia')
71
71
 
72
- const stored = await knex('any_records').first();
73
- assert.equal(stored.string_1, 'Australia');
74
- assert.equal(stored.string_2, 'AU');
72
+ const stored = await knex('any_records').first()
73
+ assert.equal(stored.string_1, 'Australia')
74
+ assert.equal(stored.string_2, 'AU')
75
75
 
76
76
  const fetched = await api.resources.countries.get({ id: result.data.id, simplified: false })
77
77
  .catch((error) => {
78
- console.error('GET failed', error);
79
- throw error;
80
- });
81
- assert.equal(fetched.data.attributes.name, 'Australia');
82
- });
78
+ console.error('GET failed', error)
79
+ throw error
80
+ })
81
+ assert.equal(fetched.data.attributes.name, 'Australia')
82
+ })
83
83
 
84
84
  it('lists records', async () => {
85
85
  await knex('any_records').insert([{
@@ -92,11 +92,11 @@ describe('AnyAPI Knex Plugin - Basic Attributes', () => {
92
92
  resource: 'countries',
93
93
  string_1: 'Brazil',
94
94
  string_2: 'BR',
95
- }]);
95
+ }])
96
96
 
97
- const result = await api.resources.countries.query({ simplified: false });
98
- assert.equal(result.data.length, 2);
99
- const names = result.data.map((entry) => entry.attributes.name).sort();
100
- assert.deepEqual(names, ['Brazil', 'Canada']);
101
- });
102
- });
97
+ const result = await api.resources.countries.query({ simplified: false })
98
+ assert.equal(result.data.length, 2)
99
+ const names = result.data.map((entry) => entry.attributes.name).sort()
100
+ assert.deepEqual(names, ['Brazil', 'Canada'])
101
+ })
102
+ })
@@ -1,58 +1,58 @@
1
- import { describe, it, before, beforeEach, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { createPaginationApi } from './fixtures/api-configs.js';
1
+ import { describe, it, before, beforeEach, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { createPaginationApi } from './fixtures/api-configs.js'
5
5
  import {
6
6
  cleanTables,
7
7
  createJsonApiDocument,
8
8
  createRelationship,
9
9
  resourceIdentifier,
10
- } from './helpers/test-utils.js';
11
- import { storageMode } from './helpers/storage-mode.js';
10
+ } from './helpers/test-utils.js'
11
+ import { storageMode } from './helpers/storage-mode.js'
12
12
 
13
- const isAnyApi = storageMode.isAnyApi();
13
+ const isAnyApi = storageMode.isAnyApi()
14
14
 
15
- const maybeDescribe = isAnyApi ? describe : describe.skip;
15
+ const maybeDescribe = isAnyApi ? describe : describe.skip
16
16
 
17
17
  maybeDescribe('AnyAPI Cursor Pagination', () => {
18
18
  const knex = knexLib({
19
19
  client: 'better-sqlite3',
20
20
  connection: { filename: ':memory:' },
21
21
  useNullAsDefault: true,
22
- });
22
+ })
23
23
 
24
- let api;
24
+ let api
25
25
 
26
26
  before(async () => {
27
- api = await createPaginationApi(knex);
28
- });
27
+ api = await createPaginationApi(knex)
28
+ })
29
29
 
30
30
  after(async () => {
31
- await knex.destroy();
32
- });
31
+ await knex.destroy()
32
+ })
33
33
 
34
34
  beforeEach(async () => {
35
35
  await cleanTables(knex, [
36
36
  'pagination_countries',
37
37
  'pagination_publishers',
38
38
  'pagination_books',
39
- ]);
39
+ ])
40
40
 
41
41
  const country = await api.resources.countries.post({
42
42
  inputRecord: createJsonApiDocument('countries', { name: 'Cursorland', code: 'CL' }),
43
43
  simplified: false,
44
- });
44
+ })
45
45
 
46
46
  const publisher = await api.resources.publishers.post({
47
47
  inputRecord: createJsonApiDocument(
48
48
  'publishers',
49
49
  { name: 'Cursor Press' },
50
- { country: createRelationship(resourceIdentifier('countries', country.data.id)) },
50
+ { country: createRelationship(resourceIdentifier('countries', country.data.id)) }
51
51
  ),
52
52
  simplified: false,
53
- });
53
+ })
54
54
 
55
- const titles = ['Book A', 'Book B', 'Book C', 'Book D', 'Book E', 'Book F'];
55
+ const titles = ['Book A', 'Book B', 'Book C', 'Book D', 'Book E', 'Book F']
56
56
  for (const title of titles) {
57
57
  await api.resources.books.post({
58
58
  inputRecord: createJsonApiDocument(
@@ -61,12 +61,12 @@ maybeDescribe('AnyAPI Cursor Pagination', () => {
61
61
  {
62
62
  country: createRelationship(resourceIdentifier('countries', country.data.id)),
63
63
  publisher: createRelationship(resourceIdentifier('publishers', publisher.data.id)),
64
- },
64
+ }
65
65
  ),
66
66
  simplified: false,
67
- });
67
+ })
68
68
  }
69
- });
69
+ })
70
70
 
71
71
  it('returns cursor metadata and links when requesting the first page', async () => {
72
72
  const result = await api.resources.books.query({
@@ -74,12 +74,12 @@ maybeDescribe('AnyAPI Cursor Pagination', () => {
74
74
  page: { size: 2 },
75
75
  },
76
76
  simplified: false,
77
- });
77
+ })
78
78
 
79
- assert.equal(result.data.length, 2);
80
- assert(result.meta?.pagination?.cursor?.next, 'Should include next cursor');
81
- assert(result.links?.next?.includes('page[after]='), 'Next link should include cursor parameter');
82
- });
79
+ assert.equal(result.data.length, 2)
80
+ assert(result.meta?.pagination?.cursor?.next, 'Should include next cursor')
81
+ assert(result.links?.next?.includes('page[after]='), 'Next link should include cursor parameter')
82
+ })
83
83
 
84
84
  it('paginates forward using page[after]', async () => {
85
85
  const firstPage = await api.resources.books.query({
@@ -87,21 +87,21 @@ maybeDescribe('AnyAPI Cursor Pagination', () => {
87
87
  page: { size: 2 },
88
88
  },
89
89
  simplified: false,
90
- });
90
+ })
91
91
 
92
- const cursor = firstPage.meta.pagination.cursor.next;
92
+ const cursor = firstPage.meta.pagination.cursor.next
93
93
  const secondPage = await api.resources.books.query({
94
94
  queryParams: {
95
95
  page: { size: 2, after: cursor },
96
96
  },
97
97
  simplified: false,
98
- });
98
+ })
99
99
 
100
- assert.equal(secondPage.data.length, 2);
101
- const firstIds = firstPage.data.map((book) => book.id);
102
- const secondIds = secondPage.data.map((book) => book.id);
103
- assert(!secondIds.some((id) => firstIds.includes(id)), 'Second page should not repeat first page records');
104
- });
100
+ assert.equal(secondPage.data.length, 2)
101
+ const firstIds = firstPage.data.map((book) => book.id)
102
+ const secondIds = secondPage.data.map((book) => book.id)
103
+ assert(!secondIds.some((id) => firstIds.includes(id)), 'Second page should not repeat first page records')
104
+ })
105
105
 
106
106
  it('paginates backward using page[before]', async () => {
107
107
  const firstPage = await api.resources.books.query({
@@ -109,7 +109,7 @@ maybeDescribe('AnyAPI Cursor Pagination', () => {
109
109
  page: { size: 2 },
110
110
  },
111
111
  simplified: false,
112
- });
112
+ })
113
113
 
114
114
  const secondPage = await api.resources.books.query({
115
115
  queryParams: {
@@ -119,11 +119,11 @@ maybeDescribe('AnyAPI Cursor Pagination', () => {
119
119
  },
120
120
  },
121
121
  simplified: false,
122
- });
122
+ })
123
123
 
124
124
  const beforeCursor =
125
- secondPage.meta.pagination.cursor?.next
126
- || `id:${encodeURIComponent(secondPage.data[0].id)}`;
125
+ secondPage.meta.pagination.cursor?.next ||
126
+ `id:${encodeURIComponent(secondPage.data[0].id)}`
127
127
 
128
128
  const previousPage = await api.resources.books.query({
129
129
  queryParams: {
@@ -133,12 +133,12 @@ maybeDescribe('AnyAPI Cursor Pagination', () => {
133
133
  },
134
134
  },
135
135
  simplified: false,
136
- });
137
-
138
- const secondIds = secondPage.data.map((book) => book.id);
139
- const previousIds = previousPage.data.map((book) => book.id);
140
- assert(!previousIds.some((id) => secondIds.includes(id)), 'Page[before] should return different records');
141
- const firstIds = firstPage.data.map((book) => book.id);
142
- assert(previousIds.every((id) => firstIds.includes(id)), 'Page[before] should return earlier records');
143
- });
144
- });
136
+ })
137
+
138
+ const secondIds = secondPage.data.map((book) => book.id)
139
+ const previousIds = previousPage.data.map((book) => book.id)
140
+ assert(!previousIds.some((id) => secondIds.includes(id)), 'Page[before] should return different records')
141
+ const firstIds = firstPage.data.map((book) => book.id)
142
+ assert(previousIds.every((id) => firstIds.includes(id)), 'Page[before] should return earlier records')
143
+ })
144
+ })