json-rest-api 1.0.10 → 1.0.11
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 +5 -9
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { requirePackage } from 'hooked-api';
|
|
2
|
-
import { createSchema
|
|
3
|
-
import {
|
|
2
|
+
import { createSchema } from 'json-rest-schema';
|
|
3
|
+
import { createKnexTable, addKnexFields, alterKnexFields } from './lib/dbTablesOperations.js';
|
|
4
4
|
import { buildFieldSelection, isNonDatabaseField } from './lib/querying-writing/knex-field-helpers.js';
|
|
5
5
|
import { getForeignKeyFields } from './lib/querying-writing/field-utils.js';
|
|
6
6
|
import { buildQuerySelection } from './lib/querying/knex-query-helpers-base.js';
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
buildCursorMeta,
|
|
25
25
|
parseCursor
|
|
26
26
|
} from './lib/querying/knex-pagination-helpers.js';
|
|
27
|
+
import { getUrlPrefix } from './lib/querying/url-helpers.js';
|
|
28
|
+
import { createStorageAdapter } from './lib/storage/storage-adapter.js';
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Strips non-database fields (computed and virtual) from attributes before database operations
|
|
@@ -55,7 +57,6 @@ export const RestApiKnexPlugin = {
|
|
|
55
57
|
dependencies: ['rest-api'],
|
|
56
58
|
|
|
57
59
|
async install({ helpers, vars, pluginOptions, api, log, scopes, addHook, addScopeMethod }) {
|
|
58
|
-
|
|
59
60
|
// Try to import knex dynamically
|
|
60
61
|
let knexLib;
|
|
61
62
|
try {
|
|
@@ -75,6 +76,48 @@ export const RestApiKnexPlugin = {
|
|
|
75
76
|
helpers: {}
|
|
76
77
|
};
|
|
77
78
|
|
|
79
|
+
const storageAdapters = new Map();
|
|
80
|
+
|
|
81
|
+
const getScopeStorageAdapter = (scopeName) => {
|
|
82
|
+
if (!scopeName) return null;
|
|
83
|
+
const resource = api.resources?.[scopeName] || scopes?.[scopeName];
|
|
84
|
+
const schemaInfo = resource?.vars?.schemaInfo;
|
|
85
|
+
if (!schemaInfo) return null;
|
|
86
|
+
|
|
87
|
+
const cached = storageAdapters.get(scopeName);
|
|
88
|
+
if (cached && cached.schemaInfo === schemaInfo) {
|
|
89
|
+
return cached.adapter;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const adapter = createStorageAdapter({ knex, schemaInfo });
|
|
93
|
+
storageAdapters.set(scopeName, { adapter, schemaInfo });
|
|
94
|
+
if (resource?.vars) {
|
|
95
|
+
resource.vars.storageAdapter = adapter;
|
|
96
|
+
}
|
|
97
|
+
return adapter;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
api.knex.helpers.getStorageAdapter = getScopeStorageAdapter;
|
|
101
|
+
helpers.getStorageAdapter = getScopeStorageAdapter;
|
|
102
|
+
|
|
103
|
+
const createSelectTranslator = (adapter) => {
|
|
104
|
+
if (!adapter) return null;
|
|
105
|
+
return (field, alias) => {
|
|
106
|
+
if (field === '*') {
|
|
107
|
+
return alias ? `${alias}.*` : '*';
|
|
108
|
+
}
|
|
109
|
+
const translated = adapter.translateColumn(field);
|
|
110
|
+
const result = translated || field;
|
|
111
|
+
if (!alias) {
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
if (result.includes('.')) {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
return `${alias}.${result}`;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
78
121
|
// Check database capabilities
|
|
79
122
|
const hasWindowFunctions = await supportsWindowFunctions(knex);
|
|
80
123
|
const dbInfo = await getDatabaseInfo(knex);
|
|
@@ -91,8 +134,7 @@ export const RestApiKnexPlugin = {
|
|
|
91
134
|
windowFunctions: hasWindowFunctions
|
|
92
135
|
});
|
|
93
136
|
|
|
94
|
-
//
|
|
95
|
-
const crossTableSearchHelpers = createCrossTableSearchHelpers(scopes, log);
|
|
137
|
+
// Cross-table search functions are now imported directly and used with full signatures
|
|
96
138
|
|
|
97
139
|
/* ╔═════════════════════════════════════════════════════════════════════╗
|
|
98
140
|
* ║ MAIN QUERY FILTERING HOOK ║
|
|
@@ -102,7 +144,7 @@ export const RestApiKnexPlugin = {
|
|
|
102
144
|
|
|
103
145
|
// Register the three separate filter hooks
|
|
104
146
|
// Dependencies object for the hooks
|
|
105
|
-
const polymorphicFiltersHookParams = { log, scopes, knex,
|
|
147
|
+
const polymorphicFiltersHookParams = { log, scopes, knex, getStorageAdapter: getScopeStorageAdapter };
|
|
106
148
|
|
|
107
149
|
// Register in specific order: polymorphic → cross-table → basic
|
|
108
150
|
// This ensures proper field qualification when JOINs are present
|
|
@@ -121,32 +163,61 @@ export const RestApiKnexPlugin = {
|
|
|
121
163
|
addHook('knexQueryFiltering', 'basicFiltersHook', {},
|
|
122
164
|
async (hookParams) => basicFiltersHook(hookParams, polymorphicFiltersHookParams)
|
|
123
165
|
);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
166
|
+
|
|
167
|
+
// 3. Basic filters (processes simple main table filters)
|
|
168
|
+
addHook('release', 'releaseHook', {},
|
|
169
|
+
async ({ api }) => api.knex.instance.destroy()
|
|
170
|
+
);
|
|
171
|
+
|
|
127
172
|
|
|
128
173
|
// Helper scope method to get all schema-related information
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
174
|
+
addScopeMethod('createKnexTable', async ({ vars, scope, scopeName, scopeOptions, runHooks }) => {
|
|
175
|
+
// Create a filtered schema that excludes virtual fields
|
|
176
|
+
const schemaStructure = vars.schemaInfo.schemaStructure || {};
|
|
177
|
+
const filteredSchema = {};
|
|
178
|
+
|
|
179
|
+
// Copy only non-virtual fields to the filtered schema
|
|
180
|
+
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
181
|
+
if (!fieldDef.virtual) {
|
|
182
|
+
filteredSchema[fieldName] = fieldDef;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Create schema object from filtered fields
|
|
187
|
+
const tableSchemaInstance = createSchema(filteredSchema);
|
|
188
|
+
|
|
189
|
+
await createKnexTable(api.knex.instance, vars.schemaInfo, tableSchemaInstance, scopeOptions)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// Helper scope method to alter existing fields in a table
|
|
194
|
+
addScopeMethod('alterKnexFields', async ({ vars, scope, scopeName, scopeOptions, runHooks, params }) => {
|
|
195
|
+
// Validate required parameters
|
|
196
|
+
if (!params.fields || typeof params.fields !== 'object') {
|
|
197
|
+
throw new Error('fields parameter is required for alterKnexFields');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await alterKnexFields(
|
|
201
|
+
api.knex.instance,
|
|
202
|
+
vars.schemaInfo.tableName,
|
|
203
|
+
params.fields,
|
|
204
|
+
params.options // Pass through any additional options
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
146
208
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
209
|
+
// Helper scope method to add a field to an existing table
|
|
210
|
+
addScopeMethod('addKnexFields', async ({ vars, scope, scopeName, scopeOptions, runHooks, params }) => {
|
|
211
|
+
|
|
212
|
+
// Create schema object from filtered fields
|
|
213
|
+
const partialTableSchema = createSchema(params.fields);
|
|
214
|
+
|
|
215
|
+
await addKnexFields(api.knex.instance, vars.schemaInfo.tableName, partialTableSchema)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
helpers.newTransaction = async () => {
|
|
219
|
+
return knex.transaction()
|
|
220
|
+
}
|
|
150
221
|
|
|
151
222
|
/* ╔═════════════════════════════════════════════════════════════════════╗
|
|
152
223
|
* ║ DATA OPERATION METHODS ║
|
|
@@ -167,16 +238,19 @@ export const RestApiKnexPlugin = {
|
|
|
167
238
|
* @returns {Promise<boolean>} True if the resource exists, false otherwise
|
|
168
239
|
*/
|
|
169
240
|
helpers.dataExists = async ({ scopeName, context }) => {
|
|
241
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
242
|
+
if (storageAdapter) {
|
|
243
|
+
context.storageAdapter = storageAdapter;
|
|
244
|
+
}
|
|
245
|
+
|
|
170
246
|
const id = context.id;
|
|
171
|
-
const scope = api.resources[scopeName];
|
|
172
247
|
|
|
173
|
-
const tableName = context.schemaInfo.tableName
|
|
174
|
-
const idProperty = context.schemaInfo.idProperty
|
|
175
|
-
const db = context.db;
|
|
248
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
249
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
250
|
+
const db = context.db || api.knex.instance;
|
|
176
251
|
|
|
177
252
|
log.debug(`[Knex] EXISTS ${tableName}/${id}`);
|
|
178
253
|
|
|
179
|
-
// Select with alias if idProperty is not 'id'
|
|
180
254
|
const selectClause = idProperty !== 'id' ? `${idProperty} as id` : 'id';
|
|
181
255
|
|
|
182
256
|
const record = await db(tableName)
|
|
@@ -197,7 +271,7 @@ export const RestApiKnexPlugin = {
|
|
|
197
271
|
* @param {Object} params.context.schemaInfo - Schema information for the resource
|
|
198
272
|
* @param {string} params.context.schemaInfo.tableName - The database table name (e.g., 'basic_books')
|
|
199
273
|
* @param {string} params.context.schemaInfo.idProperty - The primary key field name (e.g., 'id')
|
|
200
|
-
* @param {Object} params.context.schemaInfo.
|
|
274
|
+
* @param {Object} params.context.schemaInfo.schemaInstance - The full schema definition for the resource
|
|
201
275
|
* @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
202
276
|
* @param {Object} [params.context.queryParams] - Query parameters for sparse fieldsets and includes
|
|
203
277
|
* @param {Object} [params.context.queryParams.fields] - Sparse fieldset selections
|
|
@@ -207,6 +281,10 @@ export const RestApiKnexPlugin = {
|
|
|
207
281
|
* @throws {RestApiResourceError} When the resource is not found
|
|
208
282
|
*/
|
|
209
283
|
helpers.dataGet = async ({ scopeName, context, runHooks }) => {
|
|
284
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
285
|
+
if (storageAdapter) {
|
|
286
|
+
context.storageAdapter = storageAdapter;
|
|
287
|
+
}
|
|
210
288
|
const scope = api.resources[scopeName];
|
|
211
289
|
if (!scope) {
|
|
212
290
|
log.error('[DATA-GET] scope is undefined!', { scopeName, availableScopes: Object.keys(api.resources || {}) });
|
|
@@ -220,11 +298,10 @@ export const RestApiKnexPlugin = {
|
|
|
220
298
|
varKeys: scope.vars ? Object.keys(scope.vars) : []
|
|
221
299
|
});
|
|
222
300
|
}
|
|
223
|
-
const id = context.id;
|
|
224
|
-
const tableName = context.schemaInfo.tableName;
|
|
225
|
-
const idProperty = context.schemaInfo.idProperty
|
|
226
|
-
const
|
|
227
|
-
const db = context.db;
|
|
301
|
+
const id = context.id;
|
|
302
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
303
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
304
|
+
const db = context.db || api.knex.instance;
|
|
228
305
|
|
|
229
306
|
log.debug(`[Knex] GET ${tableName}/${id}`);
|
|
230
307
|
|
|
@@ -246,7 +323,14 @@ export const RestApiKnexPlugin = {
|
|
|
246
323
|
let query = db(tableName).where(idProperty, id);
|
|
247
324
|
|
|
248
325
|
// Apply field selection
|
|
249
|
-
|
|
326
|
+
const selectTranslator = createSelectTranslator(storageAdapter);
|
|
327
|
+
query = buildQuerySelection(
|
|
328
|
+
query,
|
|
329
|
+
tableName,
|
|
330
|
+
fieldSelectionInfo.fieldsToSelect,
|
|
331
|
+
false,
|
|
332
|
+
selectTranslator ? { translateColumn: selectTranslator } : undefined,
|
|
333
|
+
);
|
|
250
334
|
|
|
251
335
|
const record = await query.first();
|
|
252
336
|
|
|
@@ -291,11 +375,15 @@ export const RestApiKnexPlugin = {
|
|
|
291
375
|
* @returns {Promise<Object|null>} JSON:API formatted resource with belongsTo relationships, or null if not found
|
|
292
376
|
*/
|
|
293
377
|
helpers.dataGetMinimal = async ({ scopeName, context }) => {
|
|
378
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
379
|
+
if (storageAdapter) {
|
|
380
|
+
context.storageAdapter = storageAdapter;
|
|
381
|
+
}
|
|
294
382
|
const scope = api.resources[scopeName];
|
|
295
383
|
const id = context.id;
|
|
296
|
-
const tableName = context.schemaInfo.tableName;
|
|
297
|
-
const idProperty = context.schemaInfo.idProperty;
|
|
298
|
-
const db = context.db;
|
|
384
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
385
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
386
|
+
const db = context.db || api.knex.instance;
|
|
299
387
|
|
|
300
388
|
log.debug(`[Knex] GET_MINIMAL ${tableName}/${id}`);
|
|
301
389
|
|
|
@@ -327,8 +415,8 @@ export const RestApiKnexPlugin = {
|
|
|
327
415
|
* @param {Object} params.context - The context object containing request-specific data
|
|
328
416
|
* @param {Object} params.context.schemaInfo - Schema information for the resource
|
|
329
417
|
* @param {string} params.context.schemaInfo.tableName - The database table name (e.g., 'basic_books')
|
|
330
|
-
* @param {Object} params.context.schemaInfo.
|
|
331
|
-
* @param {Object} params.context.schemaInfo.
|
|
418
|
+
* @param {Object} params.context.schemaInfo.schemaInstance - The full schema definition for the resource
|
|
419
|
+
* @param {Object} params.context.schemaInfo.searchSchemaInstance - Search schema for filtering capabilities
|
|
332
420
|
* @param {string} params.context.schemaInfo.idProperty - The primary key field name (e.g., 'id')
|
|
333
421
|
* @param {Object} params.context.queryParams - Query parameters object
|
|
334
422
|
* @param {Object} [params.context.queryParams.filters] - Filter conditions
|
|
@@ -347,17 +435,19 @@ export const RestApiKnexPlugin = {
|
|
|
347
435
|
* @param {Function} params.runHooks - Function to run hooks (e.g., 'knexQueryFiltering')
|
|
348
436
|
* @returns {Promise<Object>} JSON:API formatted response with data array, optional included resources, and pagination meta/links
|
|
349
437
|
*/
|
|
350
|
-
helpers.dataQuery = async ({ scopeName, context, runHooks }) => {
|
|
438
|
+
helpers.dataQuery = async ({ scopeName, context, runHooks }) => {
|
|
439
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
440
|
+
if (storageAdapter) {
|
|
441
|
+
context.storageAdapter = storageAdapter;
|
|
442
|
+
}
|
|
351
443
|
const scope = api.resources[scopeName];
|
|
352
|
-
const tableName = context.schemaInfo.tableName;
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
log.trace('[DATA-QUERY] Starting dataQuery', { scopeName, hasSearchSchema: !!searchSchema });
|
|
444
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
445
|
+
const schemaInfo = context.schemaInfo;
|
|
446
|
+
const queryParams = context.queryParams;
|
|
447
|
+
const db = context.db || api.knex.instance;
|
|
448
|
+
const sortableFields = context.sortableFields;
|
|
449
|
+
|
|
450
|
+
log.trace('[DATA-QUERY] Starting dataQuery', { scopeName });
|
|
361
451
|
log.debug(`[Knex] QUERY ${tableName}`, queryParams);
|
|
362
452
|
|
|
363
453
|
// Build field selection for sparse fieldsets
|
|
@@ -376,7 +466,14 @@ export const RestApiKnexPlugin = {
|
|
|
376
466
|
// Start building query with table prefix (for JOIN support)
|
|
377
467
|
let query = db(tableName);
|
|
378
468
|
|
|
379
|
-
|
|
469
|
+
const selectTranslator = createSelectTranslator(storageAdapter);
|
|
470
|
+
query = buildQuerySelection(
|
|
471
|
+
query,
|
|
472
|
+
tableName,
|
|
473
|
+
fieldSelectionInfo.fieldsToSelect,
|
|
474
|
+
true,
|
|
475
|
+
selectTranslator ? { translateColumn: selectTranslator } : undefined,
|
|
476
|
+
);
|
|
380
477
|
|
|
381
478
|
/* ═══════════════════════════════════════════════════════════════════
|
|
382
479
|
* FILTERING HOOKS
|
|
@@ -394,10 +491,20 @@ export const RestApiKnexPlugin = {
|
|
|
394
491
|
|
|
395
492
|
log.trace('[DATA-QUERY] Storing query data in context before calling runHooks');
|
|
396
493
|
|
|
494
|
+
// YOU ARE HERE: pass 'sesrchSchemaInstance' in the context below
|
|
495
|
+
|
|
397
496
|
// Store the query data in context where hooks can access it
|
|
398
497
|
// This is the proper way to share data between methods and hooks
|
|
399
498
|
if (context) {
|
|
400
|
-
context.knexQuery = {
|
|
499
|
+
context.knexQuery = {
|
|
500
|
+
query,
|
|
501
|
+
filters: queryParams.filters,
|
|
502
|
+
schemaInfo,
|
|
503
|
+
scopeName,
|
|
504
|
+
tableName,
|
|
505
|
+
db,
|
|
506
|
+
adapter: storageAdapter,
|
|
507
|
+
};
|
|
401
508
|
|
|
402
509
|
log.trace('[DATA-QUERY] Stored data in context', { hasStoredData: !!context.knexQuery, filters: queryParams.filters });
|
|
403
510
|
}
|
|
@@ -423,7 +530,19 @@ export const RestApiKnexPlugin = {
|
|
|
423
530
|
return; // Skip non-sortable fields
|
|
424
531
|
}
|
|
425
532
|
|
|
426
|
-
|
|
533
|
+
// Map relationship names to actual database columns for sorting
|
|
534
|
+
// Check if this is a relationship field that needs to be mapped
|
|
535
|
+
let dbField = field;
|
|
536
|
+
const searchField = schemaInfo.searchSchemaStructure?.[field];
|
|
537
|
+
if (searchField?.actualField) {
|
|
538
|
+
// This is a relationship field, use the actual database column
|
|
539
|
+
dbField = searchField.actualField;
|
|
540
|
+
}
|
|
541
|
+
const translatedField = storageAdapter?.translateColumn
|
|
542
|
+
? storageAdapter.translateColumn(dbField)
|
|
543
|
+
: dbField;
|
|
544
|
+
|
|
545
|
+
query.orderBy(translatedField, desc ? 'desc' : 'asc');
|
|
427
546
|
});
|
|
428
547
|
}
|
|
429
548
|
|
|
@@ -672,7 +791,14 @@ export const RestApiKnexPlugin = {
|
|
|
672
791
|
if (queryParams.filters && Object.keys(queryParams.filters).length > 0) {
|
|
673
792
|
// Store query data in context for hooks
|
|
674
793
|
if (context) {
|
|
675
|
-
context.knexQuery = {
|
|
794
|
+
context.knexQuery = {
|
|
795
|
+
query: countQuery,
|
|
796
|
+
filters: queryParams.filters,
|
|
797
|
+
scopeName,
|
|
798
|
+
tableName,
|
|
799
|
+
schemaInfo,
|
|
800
|
+
db
|
|
801
|
+
};
|
|
676
802
|
}
|
|
677
803
|
|
|
678
804
|
// Run the same filtering hooks
|
|
@@ -700,7 +826,7 @@ export const RestApiKnexPlugin = {
|
|
|
700
826
|
}
|
|
701
827
|
|
|
702
828
|
// Generate links
|
|
703
|
-
const urlPrefix =
|
|
829
|
+
const urlPrefix = getUrlPrefix(context, scope);
|
|
704
830
|
context.returnMeta.paginationLinks = generatePaginationLinks(
|
|
705
831
|
urlPrefix,
|
|
706
832
|
scopeName,
|
|
@@ -732,7 +858,7 @@ export const RestApiKnexPlugin = {
|
|
|
732
858
|
}
|
|
733
859
|
|
|
734
860
|
context.returnMeta.paginationMeta = buildCursorMeta(records, pageSize, hasMore, sortFields);
|
|
735
|
-
const urlPrefix =
|
|
861
|
+
const urlPrefix = getUrlPrefix(context, scope);
|
|
736
862
|
context.returnMeta.paginationLinks = generateCursorPaginationLinks(
|
|
737
863
|
urlPrefix,
|
|
738
864
|
scopeName,
|
|
@@ -770,7 +896,7 @@ export const RestApiKnexPlugin = {
|
|
|
770
896
|
* @param {Object} params.context.schemaInfo - Schema information for the resource
|
|
771
897
|
* @param {string} params.context.schemaInfo.tableName - The database table name (e.g., 'basic_books')
|
|
772
898
|
* @param {string} params.context.schemaInfo.idProperty - The primary key field name (e.g., 'id')
|
|
773
|
-
* @param {Object} params.context.schemaInfo.
|
|
899
|
+
* @param {Object} params.context.schemaInfo.schemaInstance - The full schema definition for the resource
|
|
774
900
|
* @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
775
901
|
* @param {Object} params.context.inputRecord - JSON:API formatted input record
|
|
776
902
|
* @param {Object} params.context.inputRecord.data - The resource data
|
|
@@ -778,12 +904,15 @@ export const RestApiKnexPlugin = {
|
|
|
778
904
|
* @returns {Promise<string|number>} The ID of the newly created resource
|
|
779
905
|
*/
|
|
780
906
|
helpers.dataPost = async ({ scopeName, context }) => {
|
|
907
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
908
|
+
if (storageAdapter) {
|
|
909
|
+
context.storageAdapter = storageAdapter;
|
|
910
|
+
}
|
|
781
911
|
const scope = api.resources[scopeName];
|
|
782
|
-
const tableName = context.schemaInfo.tableName;
|
|
783
|
-
const idProperty = context.schemaInfo.idProperty
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
const inputRecord = context.inputRecord
|
|
912
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
913
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
914
|
+
const db = context.db || api.knex.instance;
|
|
915
|
+
const inputRecord = context.inputRecord;
|
|
787
916
|
|
|
788
917
|
log.debug(`[Knex] POST ${tableName}`, inputRecord);
|
|
789
918
|
|
|
@@ -811,7 +940,7 @@ export const RestApiKnexPlugin = {
|
|
|
811
940
|
* @param {Object} params.context.schemaInfo - Schema information for the resource
|
|
812
941
|
* @param {string} params.context.schemaInfo.tableName - The database table name (e.g., 'basic_books')
|
|
813
942
|
* @param {string} params.context.schemaInfo.idProperty - The primary key field name (e.g., 'id')
|
|
814
|
-
* @param {Object} params.context.schemaInfo.
|
|
943
|
+
* @param {Object} params.context.schemaInfo.schemaInstance - The full schema definition for the resource
|
|
815
944
|
* @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
816
945
|
* @param {Object} params.context.inputRecord - JSON:API formatted input record
|
|
817
946
|
* @param {Object} params.context.inputRecord.data - The resource data
|
|
@@ -822,14 +951,17 @@ export const RestApiKnexPlugin = {
|
|
|
822
951
|
* @throws {RestApiResourceError} When updating and the resource is not found
|
|
823
952
|
*/
|
|
824
953
|
helpers.dataPut = async ({ scopeName, context }) => {
|
|
954
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
955
|
+
if (storageAdapter) {
|
|
956
|
+
context.storageAdapter = storageAdapter;
|
|
957
|
+
}
|
|
825
958
|
const scope = api.resources[scopeName];
|
|
826
959
|
const id = context.id;
|
|
827
|
-
const tableName = context.schemaInfo.tableName;
|
|
828
|
-
const idProperty = context.schemaInfo.idProperty
|
|
829
|
-
const
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
const isCreate = context.isCreate
|
|
960
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
961
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
962
|
+
const db = context.db || api.knex.instance;
|
|
963
|
+
const inputRecord = context.inputRecord;
|
|
964
|
+
const isCreate = context.isCreate;
|
|
833
965
|
|
|
834
966
|
log.debug(`[Knex] PUT ${tableName}/${id} (isCreate: ${context.isCreate})`);
|
|
835
967
|
|
|
@@ -895,7 +1027,7 @@ export const RestApiKnexPlugin = {
|
|
|
895
1027
|
* @param {Object} params.context.schemaInfo - Schema information for the resource
|
|
896
1028
|
* @param {string} params.context.schemaInfo.tableName - The database table name (e.g., 'basic_books')
|
|
897
1029
|
* @param {string} params.context.schemaInfo.idProperty - The primary key field name (e.g., 'id')
|
|
898
|
-
* @param {Object} params.context.schemaInfo.
|
|
1030
|
+
* @param {Object} params.context.schemaInfo.schemaInstance - The full schema definition for the resource
|
|
899
1031
|
* @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
900
1032
|
* @param {Object} params.context.inputRecord - JSON:API formatted input record with partial updates
|
|
901
1033
|
* @param {Object} params.context.inputRecord.data - The resource data
|
|
@@ -904,14 +1036,17 @@ export const RestApiKnexPlugin = {
|
|
|
904
1036
|
* @returns {Promise<void>} Resolves when the update is complete
|
|
905
1037
|
* @throws {RestApiResourceError} When the resource is not found
|
|
906
1038
|
*/
|
|
907
|
-
helpers.dataPatch = async ({ scopeName, context
|
|
1039
|
+
helpers.dataPatch = async ({ scopeName, context }) => {
|
|
1040
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
1041
|
+
if (storageAdapter) {
|
|
1042
|
+
context.storageAdapter = storageAdapter;
|
|
1043
|
+
}
|
|
908
1044
|
const scope = api.resources[scopeName];
|
|
909
1045
|
const id = context.id;
|
|
910
|
-
const tableName = context.schemaInfo.tableName;
|
|
911
|
-
const idProperty = context.schemaInfo.idProperty
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
const inputRecord = context.inputRecord
|
|
1046
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
1047
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
1048
|
+
const db = context.db || api.knex.instance;
|
|
1049
|
+
const inputRecord = context.inputRecord;
|
|
915
1050
|
|
|
916
1051
|
log.debug(`[Knex] PATCH ${tableName}/${id}`);
|
|
917
1052
|
|
|
@@ -938,7 +1073,7 @@ export const RestApiKnexPlugin = {
|
|
|
938
1073
|
|
|
939
1074
|
// Strip non-database fields (computed and virtual) before database operation
|
|
940
1075
|
const finalAttributes = stripNonDatabaseFields(mergedAttributes, context.schemaInfo);
|
|
941
|
-
|
|
1076
|
+
|
|
942
1077
|
log.debug(`[Knex] PATCH finalAttributes:`, finalAttributes);
|
|
943
1078
|
|
|
944
1079
|
// Remove the idProperty from attributes to prevent updating the primary key
|
|
@@ -969,12 +1104,16 @@ export const RestApiKnexPlugin = {
|
|
|
969
1104
|
* @throws {RestApiResourceError} When the resource is not found
|
|
970
1105
|
*/
|
|
971
1106
|
helpers.dataDelete = async ({ scopeName, context }) => {
|
|
1107
|
+
const storageAdapter = getScopeStorageAdapter(scopeName);
|
|
1108
|
+
if (storageAdapter) {
|
|
1109
|
+
context.storageAdapter = storageAdapter;
|
|
1110
|
+
}
|
|
972
1111
|
const scope = api.resources[scopeName];
|
|
973
1112
|
const id = context.id;
|
|
974
1113
|
|
|
975
|
-
const tableName = context.schemaInfo.tableName;
|
|
976
|
-
const idProperty = context.schemaInfo.idProperty
|
|
977
|
-
const db = context.db;
|
|
1114
|
+
const tableName = storageAdapter?.getTableName?.() || context.schemaInfo.tableName;
|
|
1115
|
+
const idProperty = storageAdapter?.getIdColumn?.() || context.schemaInfo.idProperty;
|
|
1116
|
+
const db = context.db || api.knex.instance;
|
|
978
1117
|
|
|
979
1118
|
log.debug(`[Knex] DELETE ${tableName}/${id}`);
|
|
980
1119
|
|
|
@@ -1004,4 +1143,4 @@ export const RestApiKnexPlugin = {
|
|
|
1004
1143
|
|
|
1005
1144
|
log.info('RestApiKnexPlugin installed - basic CRUD operations ready');
|
|
1006
1145
|
}
|
|
1007
|
-
}
|
|
1146
|
+
}
|