json-rest-api 1.0.7 → 1.0.8
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/agents/library-guide-expert.md +29 -0
- package/.claude/agents/rest-api-internals-expert.md +53 -0
- package/CLAUDE.md +139 -1
- package/README.md +2 -5
- package/docTest.js +238 -0
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
- package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
- package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
- package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
- package/docs/GUIDE/index.md +58 -0
- package/docs/GUIDE.md +61 -2238
- package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
- package/docs/README.md +57 -2239
- package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
- package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
- package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
- package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
- package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
- package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
- package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
- package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
- package/docs/assets/css/style.scss +78 -5
- package/docs/index.md +2 -12
- package/helper-reorganization-plan.md +85 -0
- package/helper-usage-analysis.md +94 -0
- package/package.json +5 -4
- package/plugins/core/bulk-operations-plugin.js +2 -2
- package/plugins/core/connectors/express-plugin.js +90 -32
- package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
- package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
- package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
- package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
- package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
- package/plugins/core/lib/querying/knex-window-queries.js +328 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
- package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
- package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
- package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
- package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
- package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
- package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
- package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
- package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
- package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
- package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
- package/plugins/core/lib/writing/relationship-processor.js +252 -0
- package/plugins/core/rest-api-knex-plugin.js +183 -62
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
- package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
- package/plugins/core/rest-api-plugin-methods/common.js +605 -0
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
- package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
- package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
- package/plugins/core/rest-api-plugin-methods/get.js +187 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
- package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
- package/plugins/core/rest-api-plugin-methods/post.js +157 -0
- package/plugins/core/rest-api-plugin-methods/put.js +257 -0
- package/plugins/core/rest-api-plugin-methods/query.js +208 -0
- package/plugins/core/rest-api-plugin.js +108 -2498
- package/plugins/core/socketio-plugin.js +28 -5
- package/plugins/storage/local-storage.js +2 -2
- package/tests/computed-fields.test.js +483 -0
- package/tests/cors.test.js +4 -4
- package/tests/curl.test.js +5 -4
- package/tests/custom-idproperty-relationships.test.js +891 -0
- package/tests/field-getters.test.js +353 -0
- package/tests/field-setters.test.js +310 -0
- package/tests/fixtures/api-configs.js +800 -28
- package/tests/jwt-auth.test.js +0 -2
- package/tests/nested-includes.test.js +1 -1
- package/tests/pagination-cursor-multifield.test.js +514 -0
- package/tests/pagination-enhanced.test.js +12 -9
- package/tests/polymorphic-relationships.test.js +10 -8
- package/tests/polymorphic-simple.test.js +6 -4
- package/tests/positioning-advanced.test.js +12 -12
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +24 -24
- package/tests/positioning.test.js +3 -3
- package/tests/query-limits.test.js +24 -8
- package/tests/relationship-endpoints.test.js +4 -5
- package/tests/relationships.test.js +0 -1
- package/tests/socketio.test.js +12 -1
- package/tests/virtual-fields.test.js +13 -13
- package/PLAN_FOR_WEBSOCKETS.md +0 -398
- package/README_1_Initial_Setup.md +0 -393
- package/README_X_Hooks.md +0 -647
- package/TODO_rest-api-plugin-extras.todo +0 -23
- package/connectors_ideas/express-plugin.js +0 -120
- package/connectors_ideas/http-plugin.js +0 -138
- package/docs/examples/express-plugin.js +0 -366
- package/example.js +0 -301
- package/plugins/core/HELPER_MODULES.md +0 -285
- package/plugins/core/lib/compile-schemas.js +0 -245
- package/plugins/core/lib/database-value-normalizers.js +0 -192
- package/plugins/core/lib/default-data-helpers.js +0 -100
- package/plugins/core/lib/knex-cross-table-search.js +0 -442
- package/plugins/core/lib/knex-json-api-helpers.js +0 -84
- package/plugins/core/lib/knex-json-api-transformers.js +0 -449
- package/plugins/core/lib/knex-pagination-helpers.js +0 -193
- package/plugins/core/lib/knex-window-queries.js +0 -142
- package/plugins/core/lib/many-to-many-manipulations.js +0 -343
- package/plugins/core/lib/relationship-processor.js +0 -278
- package/plugins/core/lib/schema-helpers.js +0 -343
- package/plugins/core/lib/scope-validations.js +0 -252
- package/plugins/core/rest-api-relationships-plugin.js +0 -700
- package/plugins/core/utils/knex-query-helpers-base.js +0 -56
- /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
- /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
- /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
- /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
- /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
- /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
- /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
- /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
- /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
- /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
- /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
- /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_fields_without_defaults.js +0 -0
package/example.js
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import { Api } from 'hooked-api';
|
|
2
|
-
import { RestApiPlugin, RestApiKnexPlugin } from './index.js';
|
|
3
|
-
import knexLib from 'knex';
|
|
4
|
-
import util from 'util';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// import { setupDatabase } from './setup-database.js';
|
|
8
|
-
|
|
9
|
-
// Create a Knex instance connected to SQLite in-memory database
|
|
10
|
-
const knex = knexLib({
|
|
11
|
-
client: 'better-sqlite3',
|
|
12
|
-
connection: {
|
|
13
|
-
filename: ':memory:'
|
|
14
|
-
},
|
|
15
|
-
useNullAsDefault: true
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Create API instance
|
|
19
|
-
const api = new Api({
|
|
20
|
-
name: 'book-catalog-api',
|
|
21
|
-
version: '1.0.0'
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Install plugins
|
|
25
|
-
await api.use(RestApiPlugin);
|
|
26
|
-
await api.use(RestApiKnexPlugin, { knex });
|
|
27
|
-
|
|
28
|
-
// Define schemas for our book catalog system
|
|
29
|
-
|
|
30
|
-
// Countries table
|
|
31
|
-
await api.addResource('countries', {
|
|
32
|
-
schema: {
|
|
33
|
-
name: { type: 'string', required: true, max: 100 },
|
|
34
|
-
code: { type: 'string', max: 2, unique: true }, // ISO country code
|
|
35
|
-
},
|
|
36
|
-
relationships: {
|
|
37
|
-
publishers: { hasMany: 'publishers', foreignKey: 'country_id' },
|
|
38
|
-
books: { hasMany: 'books', foreignKey: 'country_id' }
|
|
39
|
-
},
|
|
40
|
-
returnFullRecord: {
|
|
41
|
-
post: false,
|
|
42
|
-
patch: false,
|
|
43
|
-
put: false
|
|
44
|
-
},
|
|
45
|
-
// tableName: 'stocazzo',
|
|
46
|
-
// idProperty: 'anotherId'
|
|
47
|
-
});
|
|
48
|
-
await api.resources.countries.createKnexTable()
|
|
49
|
-
|
|
50
|
-
// Publishers table
|
|
51
|
-
await api.addResource('publishers', {
|
|
52
|
-
schema: {
|
|
53
|
-
name: { type: 'string', required: true, max: 200 },
|
|
54
|
-
country_id: { type: 'number', belongsTo: 'countries', as: 'country' },
|
|
55
|
-
},
|
|
56
|
-
relationships: {
|
|
57
|
-
books: { hasMany: 'books', foreignKey: 'publisher_id' }
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
await api.resources.publishers.createKnexTable()
|
|
61
|
-
|
|
62
|
-
// Authors table
|
|
63
|
-
await api.addResource('authors', {
|
|
64
|
-
schema: {
|
|
65
|
-
name: { type: 'string', required: true, max: 200 },
|
|
66
|
-
},
|
|
67
|
-
relationships: {
|
|
68
|
-
books: { hasMany: 'books', through: 'book_authors', foreignKey: 'author_id', otherKey: 'book_id' }
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
await api.resources.authors.createKnexTable()
|
|
72
|
-
|
|
73
|
-
// Books table
|
|
74
|
-
await api.addResource('books', {
|
|
75
|
-
schema: {
|
|
76
|
-
title: { type: 'string', required: true, max: 300 },
|
|
77
|
-
country_id: { type: 'number', required: true, belongsTo: 'countries', as: 'country' },
|
|
78
|
-
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher' },
|
|
79
|
-
},
|
|
80
|
-
relationships: {
|
|
81
|
-
authors: { hasMany: 'authors', through: 'book_authors', foreignKey: 'book_id', otherKey: 'author_id' },
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
await api.resources.books.createKnexTable()
|
|
85
|
-
|
|
86
|
-
// Book-Authors pivot table (many-to-many relationship)
|
|
87
|
-
await api.addResource('book_authors', {
|
|
88
|
-
schema: {
|
|
89
|
-
book_id: { type: 'number', required: true, belongsTo: 'books', as: 'book' },
|
|
90
|
-
author_id: { type: 'number', required: true, belongsTo: 'authors', as: 'author' },
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
await api.resources.book_authors.createKnexTable()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Now you can use the API directly in your code
|
|
101
|
-
try {
|
|
102
|
-
|
|
103
|
-
// Create a country
|
|
104
|
-
const countryResult1 = await api.resources.countries.post({
|
|
105
|
-
name: 'United States',
|
|
106
|
-
code: 'US'
|
|
107
|
-
});
|
|
108
|
-
console.log('Created country:', countryResult1);
|
|
109
|
-
|
|
110
|
-
// Create another country
|
|
111
|
-
const countryResult2 = await api.resources.countries.post({
|
|
112
|
-
name: 'United Kingdom',
|
|
113
|
-
});
|
|
114
|
-
console.log('Created country:', countryResult2);
|
|
115
|
-
|
|
116
|
-
// Create publisher using the OLD way (foreign key field name)
|
|
117
|
-
const publisher1Result = await api.resources.publishers.post({
|
|
118
|
-
name: 'Penguin Random House',
|
|
119
|
-
country_id: countryResult1.id
|
|
120
|
-
});
|
|
121
|
-
console.log('Created publisher1 (using country_id):', publisher1Result);
|
|
122
|
-
|
|
123
|
-
// Create publisher using the NEW way (relationship alias)
|
|
124
|
-
const publisher1bResult = await api.resources.publishers.post({
|
|
125
|
-
name: 'HarperCollins',
|
|
126
|
-
country: countryResult2.id // Using 'country' instead of 'country_id'
|
|
127
|
-
});
|
|
128
|
-
console.log('Created publisher1b (using country alias):', publisher1bResult);
|
|
129
|
-
|
|
130
|
-
debugger
|
|
131
|
-
// Test error case: providing both country_id and country
|
|
132
|
-
try {
|
|
133
|
-
await api.resources.publishers.post({
|
|
134
|
-
name: 'Invalid Publisher',
|
|
135
|
-
country_id: countryResult1.id,
|
|
136
|
-
country: countryResult2.id // ERROR: both specified!
|
|
137
|
-
});
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.log('Expected error when both country_id and country are provided:', error.message);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Create another publisher. NOT the simplified version
|
|
143
|
-
const publisher2Result = await api.resources.publishers.post({
|
|
144
|
-
inputRecord: {
|
|
145
|
-
data: {
|
|
146
|
-
type: 'publishers',
|
|
147
|
-
attributes: {
|
|
148
|
-
name: 'Apress'
|
|
149
|
-
},
|
|
150
|
-
relationships: {
|
|
151
|
-
country: {
|
|
152
|
-
data: { type: 'countries', id: countryResult1.id }
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
simplified: false
|
|
158
|
-
});
|
|
159
|
-
console.log('Created publisher:', publisher2Result);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const fetchedPublisher = await api.resources.publishers.get({
|
|
163
|
-
id: publisher2Result.data.id,
|
|
164
|
-
queryParams: { include: ['country']},
|
|
165
|
-
simplified: false
|
|
166
|
-
})
|
|
167
|
-
console.log('Refetched publisher:', util.inspect(fetchedPublisher, { depth: 5 }));
|
|
168
|
-
|
|
169
|
-
// Create a publisher
|
|
170
|
-
const publisherResult = await api.resources.publishers.post({
|
|
171
|
-
inputRecord: {
|
|
172
|
-
data: {
|
|
173
|
-
type: 'publishers',
|
|
174
|
-
attributes: {
|
|
175
|
-
name: 'Penguin Random House'
|
|
176
|
-
},
|
|
177
|
-
relationships: {
|
|
178
|
-
country: {
|
|
179
|
-
data: { type: 'countries', id: countryResult1.id }
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// Create authors
|
|
188
|
-
const author1Result = await api.resources.authors.post({
|
|
189
|
-
inputRecord: {
|
|
190
|
-
data: {
|
|
191
|
-
type: 'authors',
|
|
192
|
-
attributes: {
|
|
193
|
-
name: 'George Orwell'
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
console.log('Created author:');
|
|
199
|
-
|
|
200
|
-
const author2Result = await api.resources.authors.post({
|
|
201
|
-
inputRecord: {
|
|
202
|
-
data: {
|
|
203
|
-
type: 'authors',
|
|
204
|
-
attributes: {
|
|
205
|
-
name: 'Aldous Huxley'
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
console.log('Created another author:');
|
|
211
|
-
|
|
212
|
-
// Create a book with relationships
|
|
213
|
-
const bookResult = await api.resources.books.post({
|
|
214
|
-
simplified: false,
|
|
215
|
-
queryParams: { include: ['authors'] },
|
|
216
|
-
inputRecord: {
|
|
217
|
-
data: {
|
|
218
|
-
type: 'books',
|
|
219
|
-
attributes: {
|
|
220
|
-
title: '1984'
|
|
221
|
-
},
|
|
222
|
-
relationships: {
|
|
223
|
-
country: {
|
|
224
|
-
data: { type: 'countries', id: countryResult1.id }
|
|
225
|
-
},
|
|
226
|
-
publisher: {
|
|
227
|
-
data: { type: 'publishers', id: publisherResult.id }
|
|
228
|
-
},
|
|
229
|
-
authors: {
|
|
230
|
-
data: [
|
|
231
|
-
{ type: 'authors', id: author1Result.id },
|
|
232
|
-
{ type: 'authors', id: author2Result.id }
|
|
233
|
-
|
|
234
|
-
]
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
console.log('Created book:', util.inspect(bookResult, { depth: 5 }))
|
|
241
|
-
|
|
242
|
-
const pivotRecords = await knex('book_authors').where('book_id', bookResult.data.id);
|
|
243
|
-
console.log('Pivot records:', pivotRecords);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// Clean up database connection
|
|
248
|
-
await knex.destroy();
|
|
249
|
-
console.log('\nExample completed successfully!');
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
/*
|
|
254
|
-
|
|
255
|
-
// Create a book with relationships
|
|
256
|
-
const bookResult = await api.resources.books.post({
|
|
257
|
-
inputRecord: {
|
|
258
|
-
data: {
|
|
259
|
-
type: 'books',
|
|
260
|
-
attributes: {
|
|
261
|
-
title: '1984'
|
|
262
|
-
},
|
|
263
|
-
relationships: {
|
|
264
|
-
country: {
|
|
265
|
-
data: { type: 'countries', id: ukResult.data.id }
|
|
266
|
-
},
|
|
267
|
-
publisher: {
|
|
268
|
-
data: { type: 'publishers', id: publisherResult.data.id }
|
|
269
|
-
},
|
|
270
|
-
authors: {
|
|
271
|
-
data: [
|
|
272
|
-
{ type: 'authors', id: author1Result.data.id }
|
|
273
|
-
]
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
console.log('Created book:', bookResult.data);
|
|
280
|
-
|
|
281
|
-
// Query books with included relationships
|
|
282
|
-
const booksWithRelationships = await api.resources.books.query({
|
|
283
|
-
include: 'authors,publisher,country'
|
|
284
|
-
});
|
|
285
|
-
console.log('\nBooks with relationships:');
|
|
286
|
-
console.log(JSON.stringify(booksWithRelationships, null, 2));
|
|
287
|
-
|
|
288
|
-
// Clean up
|
|
289
|
-
await knex.destroy();
|
|
290
|
-
console.log('\nExample completed successfully!');
|
|
291
|
-
|
|
292
|
-
*/
|
|
293
|
-
|
|
294
|
-
} catch (error) {
|
|
295
|
-
console.error('Error:', error.message, error.details);
|
|
296
|
-
console.error('Stack trace:', error.stack);
|
|
297
|
-
await knex.destroy();
|
|
298
|
-
process.exit(1);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# Diagram of helpers and their dependencies
|
|
3
|
-
|
|
4
|
-
## Refactoring Pattern
|
|
5
|
-
|
|
6
|
-
All helper functions have been refactored to use a consistent `(scope, deps)` signature:
|
|
7
|
-
|
|
8
|
-
**Before:**
|
|
9
|
-
```javascript
|
|
10
|
-
function helperFunction(param1, param2, param3, param4, param5) {
|
|
11
|
-
// Many individual parameters
|
|
12
|
-
}
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
**After:**
|
|
16
|
-
```javascript
|
|
17
|
-
function helperFunction(scope, deps) {
|
|
18
|
-
// Extract from scope (resource-specific data)
|
|
19
|
-
const { vars: { schemaInfo } } = scope;
|
|
20
|
-
|
|
21
|
-
// Extract from deps (dependencies and context)
|
|
22
|
-
const { api, context, transaction } = deps;
|
|
23
|
-
const { scopeName, queryParams } = context;
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
This pattern reduces parameters by 50-70% and provides clear separation between:
|
|
28
|
-
- **scope**: Resource configuration and schema information
|
|
29
|
-
- **deps**: Dependencies (api, knex) and request context
|
|
30
|
-
|
|
31
|
-
## Pure Utility Functions (utils/ directory)
|
|
32
|
-
|
|
33
|
-
### utils/field-utils.js
|
|
34
|
-
```
|
|
35
|
-
┌─────────────────────────────┐
|
|
36
|
-
│ getForeignKeyFields() │ - Extracts foreign key fields from schema
|
|
37
|
-
│ (schema) │ - Identifies belongsTo relationships
|
|
38
|
-
│ │ - Returns: Set(['author_id', ...])
|
|
39
|
-
└─────────────────────────────┘
|
|
40
|
-
|
|
41
|
-
┌─────────────────────────────┐
|
|
42
|
-
│ filterAttributeFields() │ - Filters out foreign keys & internals
|
|
43
|
-
│ (attributes, │ - Removes polymorphic fields
|
|
44
|
-
│ foreignKeyFields, │ - Returns: cleaned attributes object
|
|
45
|
-
│ polymorphicFields) │
|
|
46
|
-
└─────────────────────────────┘
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### utils/knex-query-helpers-base.js
|
|
50
|
-
```
|
|
51
|
-
┌─────────────────────────────┐
|
|
52
|
-
│ parseIncludeTree() │ - Parses include parameter string
|
|
53
|
-
│ (includeParam) │ - Converts "author,comments.user"
|
|
54
|
-
│ │ - Returns: nested object tree
|
|
55
|
-
└─────────────────────────────┘
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Functions with (scope, deps) Pattern (lib/ directory)
|
|
59
|
-
|
|
60
|
-
### lib/knex-json-api-helpers.js
|
|
61
|
-
```
|
|
62
|
-
┌─────────────────────────────┐
|
|
63
|
-
│ toJsonApi() │ - Converts DB record to JSON:API format
|
|
64
|
-
│ (scope, record, deps) │ - Filters out foreign keys
|
|
65
|
-
│ │ - Returns: {type, id, attributes}
|
|
66
|
-
│ deps.context: { │
|
|
67
|
-
│ scopeName, │
|
|
68
|
-
│ schemaInfo, │
|
|
69
|
-
│ polymorphicFields │
|
|
70
|
-
│ } │
|
|
71
|
-
└─────────────────────────────┘
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### lib/knex-relationship-includes.js
|
|
75
|
-
```
|
|
76
|
-
┌─────────────────────────────┐
|
|
77
|
-
│ groupByPolymorphicType() │ - Groups records by polymorphic type
|
|
78
|
-
│ (records, typeField, │ - Used for batch loading
|
|
79
|
-
│ idField) │ - Returns: {type: [ids...]}
|
|
80
|
-
└─────────────────────────────┘
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
85
|
-
│ buildIncludedResources() │
|
|
86
|
-
│ (scope, records, deps) │
|
|
87
|
-
│ │
|
|
88
|
-
│ Main entry point - orchestrates the include loading process │
|
|
89
|
-
│ │
|
|
90
|
-
│ deps: { │
|
|
91
|
-
│ includeParam, fields, included, processedPaths │
|
|
92
|
-
│ } │
|
|
93
|
-
│ │
|
|
94
|
-
│ 1. Parses includes ──────────────┐ │
|
|
95
|
-
│ ▼ │
|
|
96
|
-
│ 2. Calls processIncludes ─────► processIncludes() │
|
|
97
|
-
│ │
|
|
98
|
-
│ 3. Returns: {included: [...], recordsWithRelationships: [...]} │
|
|
99
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
100
|
-
│
|
|
101
|
-
▼
|
|
102
|
-
┌────────────────────────────────────────────────────────────────┐
|
|
103
|
-
│ processIncludes() │
|
|
104
|
-
│ (scope, records, deps) │
|
|
105
|
-
│ │
|
|
106
|
-
│ Recursive function that examines schema and calls loaders │
|
|
107
|
-
│ │
|
|
108
|
-
│ deps: { │
|
|
109
|
-
│ includeTree, included, processedPaths, currentPath, fields │
|
|
110
|
-
│ } │
|
|
111
|
-
│ │
|
|
112
|
-
│ Dispatches to: ─────────────────┬───────────────┬──────────────┤
|
|
113
|
-
└─────────────────────────────────┴───────────────┴──────────────┘
|
|
114
|
-
│ │ │
|
|
115
|
-
▼ ▼ ▼
|
|
116
|
-
┌───────────────────────────┐ ┌─────────────────────┐ ┌──────────────────────────┐
|
|
117
|
-
│ loadBelongsTo() │ │ loadHasMany() │ │ loadPolymorphicBelongsTo │
|
|
118
|
-
│ (scope, records, deps) │ │ (scope, records, │ │ (scope, records, deps) │
|
|
119
|
-
│ │ │ deps) │ │ │
|
|
120
|
-
│ deps: { │ │ │ │ deps: { │
|
|
121
|
-
│ fieldName, fieldDef, │ │ deps: { │ │ relName, relDef, │
|
|
122
|
-
│ includeName, │ │ includeName, │ │ subIncludes, included, │
|
|
123
|
-
│ subIncludes, included, │ │ relDef, │ │ processedPaths, │
|
|
124
|
-
│ processedPaths, │ │ subIncludes, │ │ currentPath, fields │
|
|
125
|
-
│ currentPath, fields │ │ included, │ │ } │
|
|
126
|
-
│ } │ │ processedPaths, │ │ │
|
|
127
|
-
│ │ │ currentPath, │ │ Loads polymorphic │
|
|
128
|
-
│ Loads many-to-one │ │ fields │ │ e.g. comment→(article │
|
|
129
|
-
│ e.g. comment→article │ │ } │ │ or video) │
|
|
130
|
-
│ │ │ │ │ │
|
|
131
|
-
│ ┌─────────────────┐ │ │ Loads one-to-many │ │ ┌──────────────────┐ │
|
|
132
|
-
│ │ Recursively │ │ │ or many-to-many │ │ │ Recursively │ │
|
|
133
|
-
│ │ calls │◄──────┼─┤ e.g. article→ │ │ │ calls │◄────┤
|
|
134
|
-
│ │ processIncludes │ │ │ comments │ │ │ processIncludes │ │
|
|
135
|
-
│ └─────────────────┘ │ │ │ │ └──────────────────┘ │
|
|
136
|
-
└───────────────────────────┘ │ ┌─────────────────┐ │ └──────────────────────────┘
|
|
137
|
-
│ │ Recursively │ │
|
|
138
|
-
│ │ calls │◄┤
|
|
139
|
-
│ │ processIncludes │ │ Also dispatches to:
|
|
140
|
-
│ └─────────────────┘ │ │
|
|
141
|
-
└─────────────────────┘ ▼
|
|
142
|
-
┌──────────────────────────┐
|
|
143
|
-
│ loadReversePolymorphic() │
|
|
144
|
-
│ (scope, records, deps) │
|
|
145
|
-
│ │
|
|
146
|
-
│ deps: { │
|
|
147
|
-
│ includeName, relDef, │
|
|
148
|
-
│ subIncludes, included, │
|
|
149
|
-
│ processedPaths, │
|
|
150
|
-
│ currentPath, fields │
|
|
151
|
-
│ } │
|
|
152
|
-
│ │
|
|
153
|
-
│ Loads via relationships │
|
|
154
|
-
│ e.g. article→comments │
|
|
155
|
-
│ (via commentable) │
|
|
156
|
-
│ │
|
|
157
|
-
│ ┌──────────────────┐ │
|
|
158
|
-
│ │ Recursively │ │
|
|
159
|
-
│ │ calls │◄────┤
|
|
160
|
-
│ │ processIncludes │ │
|
|
161
|
-
│ └──────────────────┘ │
|
|
162
|
-
└──────────────────────────┘
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### lib/simplified-helpers.js
|
|
166
|
-
```
|
|
167
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
168
|
-
│ transformSimplifiedToJsonApi() │
|
|
169
|
-
│ (scope, deps) │
|
|
170
|
-
│ │
|
|
171
|
-
│ Converts plain objects to JSON:API format │
|
|
172
|
-
│ - Handles both foreign key names and 'as' aliases │
|
|
173
|
-
│ - Converts relationships to proper JSON:API structure │
|
|
174
|
-
│ │
|
|
175
|
-
│ scope: { inputRecord } │
|
|
176
|
-
│ deps.context: { scopeName, schemaStructure, schemaRelationships }│
|
|
177
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
178
|
-
|
|
179
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
180
|
-
│ transformJsonApiToSimplified() │
|
|
181
|
-
│ (scope, deps) │
|
|
182
|
-
│ │
|
|
183
|
-
│ Converts JSON:API responses to plain objects │
|
|
184
|
-
│ - Restores foreign keys from relationships │
|
|
185
|
-
│ - Handles both single resources and collections │
|
|
186
|
-
│ │
|
|
187
|
-
│ scope: { record } │
|
|
188
|
-
│ deps.context: { schemaStructure, schemaRelationships } │
|
|
189
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### lib/many-to-many-manipulations.js
|
|
193
|
-
```
|
|
194
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
195
|
-
│ updateManyToManyRelationship() │
|
|
196
|
-
│ (scope, deps) │
|
|
197
|
-
│ │
|
|
198
|
-
│ Intelligently syncs many-to-many relationships │
|
|
199
|
-
│ - Preserves existing pivot data │
|
|
200
|
-
│ - Only creates/deletes changed relationships │
|
|
201
|
-
│ │
|
|
202
|
-
│ deps.context: { │
|
|
203
|
-
│ resourceId, relDef, relData, transaction │
|
|
204
|
-
│ } │
|
|
205
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### lib/relationship-processor.js
|
|
209
|
-
```
|
|
210
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
211
|
-
│ processRelationships() │
|
|
212
|
-
│ (scope, deps) │
|
|
213
|
-
│ │
|
|
214
|
-
│ Extracts relationship data from JSON:API payloads │
|
|
215
|
-
│ - Converts belongsTo to foreign keys │
|
|
216
|
-
│ - Handles polymorphic relationships │
|
|
217
|
-
│ - Prepares many-to-many operations │
|
|
218
|
-
│ │
|
|
219
|
-
│ deps.context: { inputRecord } │
|
|
220
|
-
│ Returns: { belongsToUpdates, manyToManyRelationships } │
|
|
221
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
## Module: lib/knex-process-includes.js
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
228
|
-
│ processIncludes() │
|
|
229
|
-
│ (records, scopeName, queryParams, transaction, dependencies) │
|
|
230
|
-
│ │
|
|
231
|
-
│ High-level orchestrator that: │
|
|
232
|
-
│ 1. Extracts scopes, log, knex from dependencies │
|
|
233
|
-
│ 2. Uses transaction if provided, otherwise uses knex │
|
|
234
|
-
│ 3. Calls buildIncludedResources with all parameters │
|
|
235
|
-
│ 4. Returns just the included array │
|
|
236
|
-
│ │
|
|
237
|
-
│ Called by: REST API plugin dataGet/dataQuery methods │
|
|
238
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Data Flow:
|
|
242
|
-
```
|
|
243
|
-
REST API Plugin
|
|
244
|
-
│
|
|
245
|
-
├─► processIncludes (lib/knex-process-includes.js)
|
|
246
|
-
│ │
|
|
247
|
-
│ └─► buildIncludedResources (lib/knex-relationship-includes.js)
|
|
248
|
-
│ │
|
|
249
|
-
│ ├─► Uses pure utilities:
|
|
250
|
-
│ │ - parseIncludeTree (utils/knex-query-helpers-base.js)
|
|
251
|
-
│ │ - getForeignKeyFields (utils/field-utils.js)
|
|
252
|
-
│ │
|
|
253
|
-
│ └─► processIncludes (recursive orchestrator)
|
|
254
|
-
│ │
|
|
255
|
-
│ ├─► loadBelongsTo
|
|
256
|
-
│ ├─► loadHasMany
|
|
257
|
-
│ ├─► loadPolymorphicBelongsTo
|
|
258
|
-
│ └─► loadReversePolymorphic
|
|
259
|
-
│ │
|
|
260
|
-
│ └─► All loaders use:
|
|
261
|
-
│ - toJsonApi (lib/knex-json-api-helpers.js)
|
|
262
|
-
│ - groupByPolymorphicType (lib/knex-relationship-includes.js)
|
|
263
|
-
│
|
|
264
|
-
└─► Returns: Array of included resources in JSON:API format
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
## File Organization:
|
|
268
|
-
|
|
269
|
-
### Pure Utilities (utils/ directory)
|
|
270
|
-
- `field-utils.js` - Schema field inspection utilities
|
|
271
|
-
- `knex-query-helpers-base.js` - Query parsing utilities
|
|
272
|
-
- `knex-constants.js` - Shared constants
|
|
273
|
-
- `connectors-query-parser.js` - Connector query parsing
|
|
274
|
-
|
|
275
|
-
### Scoped Functions (lib/ directory)
|
|
276
|
-
- `knex-json-api-helpers.js` - JSON:API transformation with (scope, deps)
|
|
277
|
-
- `knex-relationship-includes.js` - Relationship loading with (scope, deps)
|
|
278
|
-
- `knex-process-includes.js` - Top-level orchestrator
|
|
279
|
-
- `knex-field-helpers.js` - Field selection and filtering with (scope, deps)
|
|
280
|
-
- `knex-window-queries.js` - Window query builders
|
|
281
|
-
- `simplified-helpers.js` - Simplified/JSON:API transformations with (scope, deps)
|
|
282
|
-
- `many-to-many-manipulations.js` - Many-to-many relationship updates with (scope, deps)
|
|
283
|
-
- `relationship-processor.js` - Relationship processing with (scope, deps)
|
|
284
|
-
- `compile-schemas.js` - Schema compilation with (scope, deps)
|
|
285
|
-
- `default-data-helpers.js` - Default data helper creation
|