json-rest-api 1.0.13 → 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.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +13 -0
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +9 -5
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- 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
|
-
|
|
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
|
+
})
|