json-rest-api 1.0.10 → 1.0.13
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/.claude/settings.local.json +9 -1
- package/AGENTS.md +34 -0
- package/CLAUDE.md +26 -1
- package/CRUSH.md +55 -0
- package/README.md +3 -3
- package/TODO.todo +8 -0
- package/docs/API.md +17 -18
- package/docs/COMPARISON.md +18 -13
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +17 -21
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +33 -2
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +61 -7
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +58 -61
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +37 -37
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +103 -87
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +37 -34
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +45 -42
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +3 -3
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +1 -1
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -5
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +498 -755
- package/docs/GUIDE/GUIDE_6_Positioning.md +1 -3
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +4 -5
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +15 -6
- package/docs/GUIDE/GUIDE_X_Cors.md +2 -5
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +2 -3
- package/docs/GUIDE/GUIDE_X_Multihome.md +1 -4
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -2
- package/docs/GUIDE/GUIDE_X_URL_Management.md +317 -0
- package/docs/GUIDE/index.md +3 -0
- package/docs/QUICKSTART.md +12 -12
- package/docs/index.md +2 -2
- package/docs/mini-guides/file-uploads-complete.md +0 -1
- package/examples/url-override-example.js +137 -0
- package/index.js +7 -1
- package/package.json +7 -11
- package/plugins/core/bulk-operations-plugin.js +2 -0
- package/plugins/core/connectors/express-plugin.js +43 -24
- package/plugins/core/connectors/lib/request-helpers.js +2 -2
- package/plugins/core/lib/anyapi/anyapi-registry.js +821 -0
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +647 -0
- package/plugins/core/lib/anyapi/schema-utils.js +186 -0
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +113 -0
- package/plugins/core/lib/dbTablesOperations.js +305 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +64 -108
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +21 -27
- package/plugins/core/lib/querying/knex-pagination-helpers.js +36 -20
- package/plugins/core/lib/querying/knex-query-helpers-base.js +49 -14
- package/plugins/core/lib/querying/knex-query-helpers.js +378 -222
- package/plugins/core/lib/querying/knex-relationship-includes.js +262 -87
- package/plugins/core/lib/querying/url-helpers.js +119 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +36 -28
- package/plugins/core/lib/querying-writing/database-capabilities.js +10 -9
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +9 -3
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +2 -2
- package/plugins/core/lib/querying-writing/payload-validators.js +10 -16
- package/plugins/core/lib/querying-writing/schema-helpers.js +52 -60
- package/plugins/core/lib/querying-writing/scope-validations.js +78 -22
- package/plugins/core/lib/querying-writing/simplified-helpers.js +208 -61
- package/plugins/core/lib/storage/storage-adapter.js +160 -0
- package/plugins/core/lib/writing/relationship-processor.js +4 -11
- package/plugins/core/rest-api-access.js +456 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +2285 -0
- package/plugins/core/rest-api-knex-plugin.js +226 -87
- package/plugins/core/rest-api-multihome-plugin.js +89 -27
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +0 -1
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +1 -5
- package/plugins/core/rest-api-plugin-methods/common.js +46 -10
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +31 -27
- package/plugins/core/rest-api-plugin-methods/delete.js +1 -4
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +13 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +114 -56
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +4 -3
- package/plugins/core/rest-api-plugin-methods/get.js +2 -5
- package/plugins/core/rest-api-plugin-methods/patch.js +15 -16
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +21 -14
- package/plugins/core/rest-api-plugin-methods/post.js +23 -11
- package/plugins/core/rest-api-plugin-methods/put.js +18 -21
- package/plugins/core/rest-api-plugin-methods/query.js +3 -4
- package/plugins/core/rest-api-plugin-methods/release.js +13 -0
- package/plugins/core/rest-api-plugin.js +3 -4
- package/plugins/core/rest-api-positioning-plugin.js +253 -76
- package/plugins/core/socketio-plugin.js +436 -502
- package/quickTest.js +2 -2
- package/scripts/debug-anyapi-review-filter.mjs +63 -0
- package/scripts/inspect-anyapi-descriptor-country.mjs +19 -0
- package/scripts/inspect-anyapi-descriptor.mjs +19 -0
- package/test-debug.js +59 -0
- package/tests/README.md +7 -1
- package/tests/anyapi-basic.test.js +102 -0
- package/tests/anyapi-cursor-pagination.test.js +144 -0
- package/tests/computed-fields.test.js +27 -12
- package/tests/cors.test.js +5 -6
- package/tests/curl.test.js +1 -1
- package/tests/custom-idproperty-relationships.test.js +37 -29
- package/tests/field-getters.test.js +25 -7
- package/tests/fixtures/api-configs.js +576 -150
- package/tests/helpers/storage-mode.js +65 -0
- package/tests/helpers/test-utils.js +85 -3
- package/tests/include-depth-validation.test.js +35 -1
- package/tests/nested-includes.test.js +130 -103
- package/tests/pagination-cursor-multifield.test.js +17 -13
- package/tests/pagination-enhanced.test.js +19 -24
- package/tests/polymorphic-simple.test.js +9 -9
- package/tests/positioning-advanced.test.js +19 -19
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +62 -62
- package/tests/positioning.test.js +7 -7
- package/tests/queries.test.js +5 -5
- package/tests/query-limits.test.js +77 -92
- package/tests/rest-api-access.test.js +206 -0
- package/tests/searchschema-merge.test.js +182 -0
- package/tests/socketio.test.js +33 -14
- package/tests/virtual-fields.test.js +0 -1
- package/docs/DOCS_ALL.md +0 -21894
- package/plugins/core/jwt-auth-plugin.js +0 -897
- package/plugins/core/lib/jwt-auth-helpers.js +0 -130
- package/tests/jwt-auth.test.js +0 -692
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const STORAGE_MODE = process.env.JSON_REST_API_STORAGE === 'anyapi' ? 'anyapi' : 'knex';
|
|
2
|
+
const DEFAULT_TENANT = 'default';
|
|
3
|
+
|
|
4
|
+
const tableMetadata = new Map();
|
|
5
|
+
let resetMetadataFn = null;
|
|
6
|
+
const linkMappings = new Map();
|
|
7
|
+
let currentTenant = DEFAULT_TENANT;
|
|
8
|
+
|
|
9
|
+
export const storageMode = {
|
|
10
|
+
mode: STORAGE_MODE,
|
|
11
|
+
isAnyApi() {
|
|
12
|
+
return STORAGE_MODE === 'anyapi';
|
|
13
|
+
},
|
|
14
|
+
defaultTenant: DEFAULT_TENANT,
|
|
15
|
+
get currentTenant() {
|
|
16
|
+
return currentTenant;
|
|
17
|
+
},
|
|
18
|
+
registerReset(fn) {
|
|
19
|
+
resetMetadataFn = fn;
|
|
20
|
+
},
|
|
21
|
+
setCurrentTenant(tenantId) {
|
|
22
|
+
currentTenant = tenantId || DEFAULT_TENANT;
|
|
23
|
+
},
|
|
24
|
+
registerTable(tableName, resourceName, tenantId = currentTenant || DEFAULT_TENANT) {
|
|
25
|
+
if (!tableName || !resourceName) return;
|
|
26
|
+
tableMetadata.set(tableName, {
|
|
27
|
+
resource: resourceName,
|
|
28
|
+
tenantId: tenantId || DEFAULT_TENANT,
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
registerLink(
|
|
32
|
+
tableName,
|
|
33
|
+
ownerResource,
|
|
34
|
+
relationshipName,
|
|
35
|
+
relationshipKey,
|
|
36
|
+
inverseRelationshipKey,
|
|
37
|
+
tenantId = currentTenant || DEFAULT_TENANT
|
|
38
|
+
) {
|
|
39
|
+
if (!tableName || !ownerResource || !relationshipName) return;
|
|
40
|
+
linkMappings.set(tableName, {
|
|
41
|
+
ownerResource,
|
|
42
|
+
relationshipName,
|
|
43
|
+
relationshipKey,
|
|
44
|
+
inverseRelationshipKey,
|
|
45
|
+
tenantId: tenantId || DEFAULT_TENANT,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
getResourceForTable(tableName) {
|
|
49
|
+
return tableMetadata.get(tableName)?.resource;
|
|
50
|
+
},
|
|
51
|
+
getTenantForTable(tableName) {
|
|
52
|
+
return tableMetadata.get(tableName)?.tenantId || DEFAULT_TENANT;
|
|
53
|
+
},
|
|
54
|
+
getLinkInfo(tableName) {
|
|
55
|
+
return linkMappings.get(tableName);
|
|
56
|
+
},
|
|
57
|
+
clearRegistry() {
|
|
58
|
+
tableMetadata.clear();
|
|
59
|
+
linkMappings.clear();
|
|
60
|
+
currentTenant = DEFAULT_TENANT;
|
|
61
|
+
if (resetMetadataFn) {
|
|
62
|
+
resetMetadataFn();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { storageMode } from './storage-mode.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Validates that a response follows JSON:API structure
|
|
@@ -48,8 +49,54 @@ export function resourceIdentifier(type, id) {
|
|
|
48
49
|
* Cleans all records from the given tables
|
|
49
50
|
*/
|
|
50
51
|
export async function cleanTables(knex, tableNames) {
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
if (storageMode.isAnyApi()) {
|
|
53
|
+
const resourceGroups = new Map();
|
|
54
|
+
|
|
55
|
+
for (const table of tableNames) {
|
|
56
|
+
const resource = storageMode.getResourceForTable(table);
|
|
57
|
+
if (!resource) continue;
|
|
58
|
+
const tenantId = storageMode.getTenantForTable(table);
|
|
59
|
+
const key = `${tenantId}::${resource}`;
|
|
60
|
+
if (!resourceGroups.has(key)) {
|
|
61
|
+
resourceGroups.set(key, { resource, tenantId });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const { resource, tenantId } of resourceGroups.values()) {
|
|
66
|
+
await knex('any_records')
|
|
67
|
+
.where({ tenant_id: tenantId, resource })
|
|
68
|
+
.delete();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const table of tableNames) {
|
|
72
|
+
const linkInfo = storageMode.getLinkInfo(table);
|
|
73
|
+
if (linkInfo) {
|
|
74
|
+
const tenantId = linkInfo.tenantId || storageMode.defaultTenant;
|
|
75
|
+
const relationshipKey = linkInfo.relationshipKey
|
|
76
|
+
|| `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`;
|
|
77
|
+
await knex('any_links')
|
|
78
|
+
.where({ tenant_id: tenantId })
|
|
79
|
+
.andWhere((builder) => {
|
|
80
|
+
builder.where('relationship', relationshipKey)
|
|
81
|
+
.orWhere('inverse_relationship', relationshipKey);
|
|
82
|
+
if (linkInfo.inverseRelationshipKey) {
|
|
83
|
+
builder.orWhere('relationship', linkInfo.inverseRelationshipKey)
|
|
84
|
+
.orWhere('inverse_relationship', linkInfo.inverseRelationshipKey);
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.delete()
|
|
88
|
+
.catch(() => {});
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await knex(table).delete();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Ignore missing tables in anyapi mode
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
for (const table of tableNames) {
|
|
98
|
+
await knex(table).delete();
|
|
99
|
+
}
|
|
53
100
|
}
|
|
54
101
|
}
|
|
55
102
|
|
|
@@ -57,6 +104,41 @@ export async function cleanTables(knex, tableNames) {
|
|
|
57
104
|
* Counts records in a table
|
|
58
105
|
*/
|
|
59
106
|
export async function countRecords(knex, tableName) {
|
|
107
|
+
if (storageMode.isAnyApi()) {
|
|
108
|
+
const resource = storageMode.getResourceForTable(tableName);
|
|
109
|
+
const linkInfo = storageMode.getLinkInfo(tableName);
|
|
110
|
+
const tenantId = storageMode.getTenantForTable(tableName);
|
|
111
|
+
if (linkInfo) {
|
|
112
|
+
const relationshipKey = linkInfo.relationshipKey
|
|
113
|
+
|| `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`;
|
|
114
|
+
const result = await knex('any_links')
|
|
115
|
+
.where({ tenant_id: tenantId })
|
|
116
|
+
.andWhere((builder) => {
|
|
117
|
+
builder.where('relationship', relationshipKey)
|
|
118
|
+
.orWhere('inverse_relationship', relationshipKey);
|
|
119
|
+
})
|
|
120
|
+
.count('* as count')
|
|
121
|
+
.first();
|
|
122
|
+
return parseInt(result?.count ?? 0, 10);
|
|
123
|
+
}
|
|
124
|
+
if (resource) {
|
|
125
|
+
const result = await knex('any_records')
|
|
126
|
+
.where({
|
|
127
|
+
tenant_id: tenantId,
|
|
128
|
+
resource,
|
|
129
|
+
})
|
|
130
|
+
.count('* as count')
|
|
131
|
+
.first();
|
|
132
|
+
return parseInt(result.count);
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const result = await knex(tableName).count('* as count').first();
|
|
136
|
+
return parseInt(result?.count || 0);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
60
142
|
const result = await knex(tableName).count('* as count').first();
|
|
61
143
|
return parseInt(result.count);
|
|
62
144
|
}
|
|
@@ -118,4 +200,4 @@ export function assertResourceRelationship(resource, relationshipName, expectedI
|
|
|
118
200
|
expectedIdentifier,
|
|
119
201
|
`Relationship ${relationshipName} should match expected identifier`
|
|
120
202
|
);
|
|
121
|
-
}
|
|
203
|
+
}
|
|
@@ -271,6 +271,40 @@ describe('Include Depth Validation', () => {
|
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
it('should handle single-level paths correctly', async () => {
|
|
274
|
+
const country = await api.resources.countries.post({
|
|
275
|
+
inputRecord: {
|
|
276
|
+
data: {
|
|
277
|
+
type: 'countries',
|
|
278
|
+
attributes: { name: 'Single Level Country', code: `S${Date.now().toString().slice(-2)}` }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const publisher = await api.resources.publishers.post({
|
|
284
|
+
inputRecord: {
|
|
285
|
+
data: {
|
|
286
|
+
type: 'publishers',
|
|
287
|
+
attributes: { name: 'Single Level Publisher' },
|
|
288
|
+
relationships: {
|
|
289
|
+
country: { data: { type: 'countries', id: country.data.id } }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await api.resources.books.post({
|
|
296
|
+
inputRecord: {
|
|
297
|
+
data: {
|
|
298
|
+
type: 'books',
|
|
299
|
+
attributes: { title: 'Single Level Book' },
|
|
300
|
+
relationships: {
|
|
301
|
+
country: { data: { type: 'countries', id: country.data.id } },
|
|
302
|
+
publisher: { data: { type: 'publishers', id: publisher.data.id } }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
274
308
|
const result = await api.resources.books.query({
|
|
275
309
|
queryParams: { include: ['publisher'] }
|
|
276
310
|
});
|
|
@@ -294,4 +328,4 @@ describe('Include Depth Validation', () => {
|
|
|
294
328
|
);
|
|
295
329
|
});
|
|
296
330
|
});
|
|
297
|
-
});
|
|
331
|
+
});
|
|
@@ -7,6 +7,7 @@ import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.js';
|
|
|
7
7
|
import {
|
|
8
8
|
cleanTables,
|
|
9
9
|
createJsonApiDocument,
|
|
10
|
+
createRelationship,
|
|
10
11
|
validateJsonApiStructure
|
|
11
12
|
} from './helpers/test-utils.js';
|
|
12
13
|
|
|
@@ -57,8 +58,9 @@ describe('Nested Include Operations', () => {
|
|
|
57
58
|
},
|
|
58
59
|
relationships: {
|
|
59
60
|
books: {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
type: 'hasMany',
|
|
62
|
+
target: 'books',
|
|
63
|
+
foreignKey: 'publisher_id'
|
|
62
64
|
// Relationships are always includable via ?include=
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -74,7 +76,7 @@ describe('Nested Include Operations', () => {
|
|
|
74
76
|
},
|
|
75
77
|
relationships: {
|
|
76
78
|
books: {
|
|
77
|
-
|
|
79
|
+
type: 'manyToMany',
|
|
78
80
|
through: 'book_authors',
|
|
79
81
|
foreignKey: 'author_id',
|
|
80
82
|
otherKey: 'book_id',
|
|
@@ -97,15 +99,16 @@ describe('Nested Include Operations', () => {
|
|
|
97
99
|
},
|
|
98
100
|
relationships: {
|
|
99
101
|
authors: {
|
|
100
|
-
|
|
102
|
+
type: 'manyToMany',
|
|
101
103
|
through: 'book_authors',
|
|
102
104
|
foreignKey: 'book_id',
|
|
103
|
-
otherKey: 'author_id'
|
|
105
|
+
otherKey: 'author_id'
|
|
104
106
|
// Relationships are always includable via ?include=
|
|
105
107
|
},
|
|
106
108
|
reviews: {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
type: 'hasMany',
|
|
110
|
+
target: 'reviews',
|
|
111
|
+
foreignKey: 'book_id'
|
|
109
112
|
// Relationships are always includable via ?include=
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -156,77 +159,83 @@ describe('Nested Include Operations', () => {
|
|
|
156
159
|
|
|
157
160
|
const publisher = await api.resources.publishers.post({
|
|
158
161
|
inputRecord: createJsonApiDocument('publishers', {
|
|
159
|
-
name: 'Test Publisher'
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
name: 'Test Publisher'
|
|
163
|
+
}, {
|
|
164
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
165
|
+
})
|
|
162
166
|
});
|
|
163
167
|
|
|
164
168
|
// Create authors
|
|
165
169
|
const author1 = await api.resources.authors.post({
|
|
166
170
|
inputRecord: createJsonApiDocument('authors', {
|
|
167
|
-
name: 'Author 1'
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
name: 'Author 1'
|
|
172
|
+
}, {
|
|
173
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
174
|
+
})
|
|
170
175
|
});
|
|
171
176
|
|
|
172
177
|
const author2 = await api.resources.authors.post({
|
|
173
178
|
inputRecord: createJsonApiDocument('authors', {
|
|
174
|
-
name: 'Author 2'
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
name: 'Author 2'
|
|
180
|
+
}, {
|
|
181
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
182
|
+
})
|
|
177
183
|
});
|
|
178
184
|
|
|
179
185
|
// Create books
|
|
180
186
|
const book1 = await api.resources.books.post({
|
|
181
187
|
inputRecord: createJsonApiDocument('books', {
|
|
182
|
-
title: 'Book 1'
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
title: 'Book 1'
|
|
189
|
+
}, {
|
|
190
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
191
|
+
})
|
|
185
192
|
});
|
|
186
193
|
|
|
187
194
|
const book2 = await api.resources.books.post({
|
|
188
195
|
inputRecord: createJsonApiDocument('books', {
|
|
189
|
-
title: 'Book 2'
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
title: 'Book 2'
|
|
197
|
+
}, {
|
|
198
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
199
|
+
})
|
|
192
200
|
});
|
|
193
201
|
|
|
194
202
|
const book3 = await api.resources.books.post({
|
|
195
203
|
inputRecord: createJsonApiDocument('books', {
|
|
196
|
-
title: 'Book 3'
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
title: 'Book 3'
|
|
205
|
+
}, {
|
|
206
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
207
|
+
})
|
|
199
208
|
});
|
|
200
209
|
|
|
201
210
|
// Create relationships
|
|
202
211
|
// Book 1 - Author 1 only
|
|
203
212
|
await api.resources.book_authors.post({
|
|
204
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
214
|
+
book: createRelationship({ type: 'books', id: String(book1.id) }),
|
|
215
|
+
author: createRelationship({ type: 'authors', id: String(author1.id) })
|
|
216
|
+
})
|
|
208
217
|
});
|
|
209
218
|
|
|
210
219
|
// Book 2 - Both authors
|
|
211
220
|
await api.resources.book_authors.post({
|
|
212
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
221
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
222
|
+
book: createRelationship({ type: 'books', id: String(book2.id) }),
|
|
223
|
+
author: createRelationship({ type: 'authors', id: String(author1.id) })
|
|
224
|
+
})
|
|
216
225
|
});
|
|
217
226
|
await api.resources.book_authors.post({
|
|
218
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
228
|
+
book: createRelationship({ type: 'books', id: String(book2.id) }),
|
|
229
|
+
author: createRelationship({ type: 'authors', id: String(author2.id) })
|
|
230
|
+
})
|
|
222
231
|
});
|
|
223
232
|
|
|
224
233
|
// Book 3 - Author 2 only
|
|
225
234
|
await api.resources.book_authors.post({
|
|
226
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
236
|
+
book: createRelationship({ type: 'books', id: String(book3.id) }),
|
|
237
|
+
author: createRelationship({ type: 'authors', id: String(author2.id) })
|
|
238
|
+
})
|
|
230
239
|
});
|
|
231
240
|
});
|
|
232
241
|
|
|
@@ -272,20 +281,25 @@ describe('Nested Include Operations', () => {
|
|
|
272
281
|
const publisher = await api.resources.publishers.query({ simplified: false });
|
|
273
282
|
const publisherId = publisher.data[0].id;
|
|
274
283
|
|
|
284
|
+
// Get the first author
|
|
285
|
+
const authorsResult = await api.resources.authors.query({ simplified: false });
|
|
286
|
+
const authorId = authorsResult.data[0].id;
|
|
287
|
+
|
|
275
288
|
for (let i = 4; i <= 10; i++) {
|
|
276
289
|
const book = await api.resources.books.post({
|
|
277
290
|
inputRecord: createJsonApiDocument('books', {
|
|
278
|
-
title: `Book ${i}
|
|
279
|
-
|
|
280
|
-
|
|
291
|
+
title: `Book ${i}`
|
|
292
|
+
}, {
|
|
293
|
+
publisher: createRelationship({ type: 'publishers', id: publisherId })
|
|
294
|
+
})
|
|
281
295
|
});
|
|
282
296
|
|
|
283
297
|
// Associate all new books with Author 1
|
|
284
298
|
await api.resources.book_authors.post({
|
|
285
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
299
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
300
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
301
|
+
author: createRelationship({ type: 'authors', id: String(authorId) })
|
|
302
|
+
})
|
|
289
303
|
});
|
|
290
304
|
}
|
|
291
305
|
|
|
@@ -334,52 +348,57 @@ describe('Nested Include Operations', () => {
|
|
|
334
348
|
|
|
335
349
|
const publisher1 = await api.resources.publishers.post({
|
|
336
350
|
inputRecord: createJsonApiDocument('publishers', {
|
|
337
|
-
name: 'US Publisher'
|
|
338
|
-
|
|
339
|
-
|
|
351
|
+
name: 'US Publisher'
|
|
352
|
+
}, {
|
|
353
|
+
country: createRelationship({ type: 'countries', id: String(usa.id) })
|
|
354
|
+
})
|
|
340
355
|
});
|
|
341
356
|
|
|
342
357
|
const publisher2 = await api.resources.publishers.post({
|
|
343
358
|
inputRecord: createJsonApiDocument('publishers', {
|
|
344
|
-
name: 'UK Publisher'
|
|
345
|
-
|
|
346
|
-
|
|
359
|
+
name: 'UK Publisher'
|
|
360
|
+
}, {
|
|
361
|
+
country: createRelationship({ type: 'countries', id: String(uk.id) })
|
|
362
|
+
})
|
|
347
363
|
});
|
|
348
364
|
|
|
349
365
|
const author = await api.resources.authors.post({
|
|
350
366
|
inputRecord: createJsonApiDocument('authors', {
|
|
351
|
-
name: 'International Author'
|
|
352
|
-
|
|
353
|
-
|
|
367
|
+
name: 'International Author'
|
|
368
|
+
}, {
|
|
369
|
+
country: createRelationship({ type: 'countries', id: String(usa.id) })
|
|
370
|
+
})
|
|
354
371
|
});
|
|
355
372
|
|
|
356
373
|
const book1 = await api.resources.books.post({
|
|
357
374
|
inputRecord: createJsonApiDocument('books', {
|
|
358
|
-
title: 'US Book'
|
|
359
|
-
|
|
360
|
-
|
|
375
|
+
title: 'US Book'
|
|
376
|
+
}, {
|
|
377
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher1.id) })
|
|
378
|
+
})
|
|
361
379
|
});
|
|
362
380
|
|
|
363
381
|
const book2 = await api.resources.books.post({
|
|
364
382
|
inputRecord: createJsonApiDocument('books', {
|
|
365
|
-
title: 'UK Book'
|
|
366
|
-
|
|
367
|
-
|
|
383
|
+
title: 'UK Book'
|
|
384
|
+
}, {
|
|
385
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher2.id) })
|
|
386
|
+
})
|
|
368
387
|
});
|
|
369
388
|
|
|
370
389
|
// Associate both books with the author
|
|
371
390
|
await api.resources.book_authors.post({
|
|
372
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
391
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
392
|
+
book: createRelationship({ type: 'books', id: String(book1.id) }),
|
|
393
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
394
|
+
})
|
|
376
395
|
});
|
|
377
396
|
|
|
378
397
|
await api.resources.book_authors.post({
|
|
379
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
398
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
399
|
+
book: createRelationship({ type: 'books', id: String(book2.id) }),
|
|
400
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
401
|
+
})
|
|
383
402
|
});
|
|
384
403
|
});
|
|
385
404
|
|
|
@@ -445,47 +464,52 @@ describe('Nested Include Operations', () => {
|
|
|
445
464
|
|
|
446
465
|
const publisher = await api.resources.publishers.post({
|
|
447
466
|
inputRecord: createJsonApiDocument('publishers', {
|
|
448
|
-
name: 'Publisher'
|
|
449
|
-
|
|
450
|
-
|
|
467
|
+
name: 'Publisher'
|
|
468
|
+
}, {
|
|
469
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
470
|
+
})
|
|
451
471
|
});
|
|
452
472
|
|
|
453
473
|
const author = await api.resources.authors.post({
|
|
454
474
|
inputRecord: createJsonApiDocument('authors', {
|
|
455
|
-
name: 'Author'
|
|
456
|
-
|
|
457
|
-
|
|
475
|
+
name: 'Author'
|
|
476
|
+
}, {
|
|
477
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
478
|
+
})
|
|
458
479
|
});
|
|
459
480
|
|
|
460
481
|
const book = await api.resources.books.post({
|
|
461
482
|
inputRecord: createJsonApiDocument('books', {
|
|
462
|
-
title: 'Book with Reviews'
|
|
463
|
-
|
|
464
|
-
|
|
483
|
+
title: 'Book with Reviews'
|
|
484
|
+
}, {
|
|
485
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
486
|
+
})
|
|
465
487
|
});
|
|
466
488
|
|
|
467
489
|
await api.resources.book_authors.post({
|
|
468
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
490
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
491
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
492
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
493
|
+
})
|
|
472
494
|
});
|
|
473
495
|
|
|
474
496
|
// Create reviews
|
|
475
497
|
await api.resources.reviews.post({
|
|
476
498
|
inputRecord: createJsonApiDocument('reviews', {
|
|
477
499
|
content: 'Great book!',
|
|
478
|
-
rating: 5
|
|
479
|
-
|
|
480
|
-
|
|
500
|
+
rating: 5
|
|
501
|
+
}, {
|
|
502
|
+
book: createRelationship({ type: 'books', id: String(book.id) })
|
|
503
|
+
})
|
|
481
504
|
});
|
|
482
505
|
|
|
483
506
|
await api.resources.reviews.post({
|
|
484
507
|
inputRecord: createJsonApiDocument('reviews', {
|
|
485
508
|
content: 'Good read',
|
|
486
|
-
rating: 4
|
|
487
|
-
|
|
488
|
-
|
|
509
|
+
rating: 4
|
|
510
|
+
}, {
|
|
511
|
+
book: createRelationship({ type: 'books', id: String(book.id) })
|
|
512
|
+
})
|
|
489
513
|
});
|
|
490
514
|
});
|
|
491
515
|
|
|
@@ -533,30 +557,33 @@ describe('Nested Include Operations', () => {
|
|
|
533
557
|
|
|
534
558
|
const publisher = await api.resources.publishers.post({
|
|
535
559
|
inputRecord: createJsonApiDocument('publishers', {
|
|
536
|
-
name: 'Publisher'
|
|
537
|
-
|
|
538
|
-
|
|
560
|
+
name: 'Publisher'
|
|
561
|
+
}, {
|
|
562
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
563
|
+
})
|
|
539
564
|
});
|
|
540
565
|
|
|
541
566
|
const author = await api.resources.authors.post({
|
|
542
567
|
inputRecord: createJsonApiDocument('authors', {
|
|
543
|
-
name: 'Author'
|
|
544
|
-
|
|
545
|
-
|
|
568
|
+
name: 'Author'
|
|
569
|
+
}, {
|
|
570
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
571
|
+
})
|
|
546
572
|
});
|
|
547
573
|
|
|
548
574
|
const book = await api.resources.books.post({
|
|
549
575
|
inputRecord: createJsonApiDocument('books', {
|
|
550
|
-
title: 'Book'
|
|
551
|
-
|
|
552
|
-
|
|
576
|
+
title: 'Book'
|
|
577
|
+
}, {
|
|
578
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
579
|
+
})
|
|
553
580
|
});
|
|
554
581
|
|
|
555
582
|
await api.resources.book_authors.post({
|
|
556
|
-
inputRecord: createJsonApiDocument('book_authors', {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
583
|
+
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
584
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
585
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
586
|
+
})
|
|
560
587
|
});
|
|
561
588
|
});
|
|
562
589
|
|