json-rest-api 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +13 -0
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +9 -5
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,46 +1,46 @@
1
1
  /**
2
2
  * Normalizes a date value to appropriate format based on field type
3
- *
3
+ *
4
4
  * @param {*} value - The date value from the database
5
5
  * @param {string} type - The field type ('date', 'dateTime', or 'time')
6
6
  * @returns {Date|string|null} Date object for date/dateTime, string for time, or null
7
- *
7
+ *
8
8
  * @example
9
9
  * // Input: Already a Date object
10
10
  * normalizeDateValue(new Date('2024-01-15T10:30:00Z'), 'dateTime');
11
11
  * // Output: Date object (unchanged)
12
- *
12
+ *
13
13
  * @example
14
14
  * // Input: MySQL datetime string without timezone
15
15
  * normalizeDateValue('2024-01-15 10:30:00', 'dateTime');
16
16
  * // Output: Date object parsed as UTC (2024-01-15T10:30:00Z)
17
- *
17
+ *
18
18
  * @example
19
19
  * // Input: Date-only string
20
20
  * normalizeDateValue('2024-01-15', 'date');
21
21
  * // Output: Date object at UTC midnight (2024-01-15T00:00:00Z)
22
- *
22
+ *
23
23
  * @example
24
24
  * // Input: Time field (always returns string)
25
25
  * normalizeDateValue('14:30:45', 'time');
26
26
  * // Output: "14:30:45" (string)
27
- *
27
+ *
28
28
  * @example
29
29
  * // Input: Date object for time field
30
30
  * normalizeDateValue(new Date('2024-01-15T14:30:45Z'), 'time');
31
31
  * // Output: "14:30:45" (extracts time portion)
32
- *
32
+ *
33
33
  * @description
34
34
  * Used by:
35
35
  * - normalizeAttributes to process individual field values
36
36
  * - Applied to all date/time fields fetched from database
37
- *
37
+ *
38
38
  * Purpose:
39
39
  * - Handles database-specific date formats (especially MySQL)
40
40
  * - Prevents timezone shifts for date-only fields
41
41
  * - Ensures consistent Date objects across databases
42
42
  * - Keeps time fields as HH:MM:SS strings
43
- *
43
+ *
44
44
  * Data flow:
45
45
  * 1. Checks for null/undefined (returns null)
46
46
  * 2. For time fields: ensures HH:MM:SS string format
@@ -48,85 +48,85 @@
48
48
  * 4. Handles MySQL datetime format (no T separator)
49
49
  * 5. Forces UTC interpretation to prevent timezone issues
50
50
  */
51
- function normalizeDateValue(value, type) {
51
+ function normalizeDateValue (value, type) {
52
52
  // Handle null/undefined
53
53
  if (value === null || value === undefined) {
54
- return null;
54
+ return null
55
55
  }
56
56
 
57
57
  // Handle time type specially - always return as string
58
58
  if (type === 'time') {
59
59
  // If it's already a properly formatted time string, return as-is
60
60
  if (typeof value === 'string' && /^\d{2}:\d{2}:\d{2}/.test(value)) {
61
- return value;
61
+ return value
62
62
  }
63
63
  // If we got a Date object for a time field, extract the time portion
64
64
  if (value instanceof Date) {
65
- return value.toISOString().slice(11, 19); // Extract HH:MM:SS
65
+ return value.toISOString().slice(11, 19) // Extract HH:MM:SS
66
66
  }
67
67
  // Try to parse and extract time
68
68
  if (typeof value === 'string' || typeof value === 'number') {
69
- const d = new Date(value);
69
+ const d = new Date(value)
70
70
  if (!isNaN(d.getTime())) {
71
- return d.toISOString().slice(11, 19);
71
+ return d.toISOString().slice(11, 19)
72
72
  }
73
73
  }
74
- return null;
74
+ return null
75
75
  }
76
76
 
77
77
  // For date and dateTime: Already a Date object? Return as-is
78
78
  if (value instanceof Date) {
79
- return value;
79
+ return value
80
80
  }
81
-
81
+
82
82
  // Handle string values
83
83
  if (typeof value === 'string') {
84
84
  // Detect MySQL datetime format: 'YYYY-MM-DD HH:MM:SS'
85
85
  // These have no T separator and no timezone indicator
86
- const isMySQLDateTime = type === 'dateTime' &&
86
+ const isMySQLDateTime = type === 'dateTime' &&
87
87
  /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(value) &&
88
- !value.includes('T') &&
88
+ !value.includes('T') &&
89
89
  !value.includes('Z') &&
90
90
  !value.includes('+') &&
91
- !value.includes('-', 10); // Don't match date separators
92
-
91
+ !value.includes('-', 10) // Don't match date separators
92
+
93
93
  if (isMySQLDateTime) {
94
94
  // Convert to ISO format and force UTC interpretation
95
95
  // '2024-01-15 10:30:00' becomes '2024-01-15T10:30:00Z'
96
- return new Date(value.replace(' ', 'T') + 'Z');
96
+ return new Date(value.replace(' ', 'T') + 'Z')
97
97
  }
98
-
98
+
99
99
  // For date-only fields, ensure parsing at UTC midnight
100
100
  // This prevents timezone shifts when parsing dates
101
101
  if (type === 'date' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
102
- return new Date(value + 'T00:00:00Z');
102
+ return new Date(value + 'T00:00:00Z')
103
103
  }
104
104
  }
105
-
105
+
106
106
  // Handle numeric values (Unix timestamps)
107
107
  if (typeof value === 'number') {
108
- return new Date(value);
108
+ return new Date(value)
109
109
  }
110
-
110
+
111
111
  // Try to parse any other format
112
- const dateObj = new Date(value);
113
-
112
+ const dateObj = new Date(value)
113
+
114
114
  // Check if date is valid
115
115
  if (isNaN(dateObj.getTime())) {
116
- console.warn(`Invalid date value for field type '${type}': ${value}`);
117
- return null; // Return null for invalid dates
116
+ console.warn(`Invalid date value for field type '${type}': ${value}`)
117
+ return null // Return null for invalid dates
118
118
  }
119
-
120
- return dateObj;
119
+
120
+ return dateObj
121
121
  }
122
122
 
123
123
  /**
124
124
  * Normalizes database values in an attributes object
125
- *
125
+ *
126
126
  * @param {Object} attributes - The attributes object to normalize
127
127
  * @param {Object} schemaStructure - The schema structure defining field types
128
128
  * @returns {Object} The normalized attributes object
129
- *
129
+ *
130
130
  * @example
131
131
  * // Input: Raw database values
132
132
  * const attributes = {
@@ -136,16 +136,16 @@ function normalizeDateValue(value, type) {
136
136
  * birth_date: '1990-05-20', // Date string
137
137
  * shift_time: '09:00:00' // Time string
138
138
  * };
139
- *
139
+ *
140
140
  * const schema = {
141
141
  * is_active: { type: 'boolean' },
142
142
  * created_at: { type: 'dateTime' },
143
143
  * birth_date: { type: 'date' },
144
144
  * shift_time: { type: 'time' }
145
145
  * };
146
- *
146
+ *
147
147
  * const normalized = normalizeAttributes(attributes, schema);
148
- *
148
+ *
149
149
  * // Output: Properly typed values
150
150
  * // {
151
151
  * // id: 1,
@@ -154,7 +154,7 @@ function normalizeDateValue(value, type) {
154
154
  * // birth_date: Date('1990-05-20T00:00:00Z'), // Date at midnight UTC
155
155
  * // shift_time: '09:00:00' // String unchanged
156
156
  * // }
157
- *
157
+ *
158
158
  * @example
159
159
  * // Input: Boolean edge cases
160
160
  * const attributes = {
@@ -163,14 +163,14 @@ function normalizeDateValue(value, type) {
163
163
  * flag3: true, // Already boolean
164
164
  * flag4: null // Null value
165
165
  * };
166
- *
166
+ *
167
167
  * const normalized = normalizeAttributes(attributes, {
168
168
  * flag1: { type: 'boolean' },
169
169
  * flag2: { type: 'boolean' },
170
170
  * flag3: { type: 'boolean' },
171
171
  * flag4: { type: 'boolean' }
172
172
  * });
173
- *
173
+ *
174
174
  * // Output:
175
175
  * // {
176
176
  * // flag1: false, // 0 → false
@@ -178,18 +178,18 @@ function normalizeDateValue(value, type) {
178
178
  * // flag3: true, // unchanged
179
179
  * // flag4: null // null preserved
180
180
  * // }
181
- *
181
+ *
182
182
  * @description
183
183
  * Used by:
184
184
  * - normalizeRecordAttributes for each record's attributes
185
185
  * - Applied after fetching data from database
186
- *
186
+ *
187
187
  * Purpose:
188
188
  * - Handles databases without native boolean support (SQLite, older MySQL)
189
189
  * - Normalizes date formats from different databases
190
190
  * - Ensures consistent data types in API responses
191
191
  * - Preserves null values appropriately
192
- *
192
+ *
193
193
  * Data flow:
194
194
  * 1. Creates copy of attributes to avoid mutation
195
195
  * 2. Iterates through each field with schema definition
@@ -197,46 +197,46 @@ function normalizeDateValue(value, type) {
197
197
  * 4. Normalizes dates using normalizeDateValue
198
198
  * 5. Returns new object with normalized values
199
199
  */
200
- export function normalizeAttributes(attributes, schemaStructure) {
200
+ export function normalizeAttributes (attributes, schemaStructure) {
201
201
  if (!attributes || !schemaStructure) {
202
- return attributes;
202
+ return attributes
203
203
  }
204
204
 
205
- const normalized = { ...attributes };
205
+ const normalized = { ...attributes }
206
206
 
207
207
  // Iterate through each attribute
208
208
  for (const [fieldName, value] of Object.entries(attributes)) {
209
- const fieldDef = schemaStructure[fieldName];
210
-
209
+ const fieldDef = schemaStructure[fieldName]
210
+
211
211
  // Skip if no field definition found
212
- if (!fieldDef) continue;
212
+ if (!fieldDef) continue
213
213
 
214
214
  // Normalize boolean values
215
215
  if (fieldDef.type === 'boolean') {
216
216
  if (value === 1 || value === '1') {
217
- normalized[fieldName] = true;
217
+ normalized[fieldName] = true
218
218
  } else if (value === 0 || value === '0') {
219
- normalized[fieldName] = false;
219
+ normalized[fieldName] = false
220
220
  }
221
221
  // null/undefined remain as-is
222
222
  }
223
-
223
+
224
224
  // Normalize date/dateTime/time values
225
225
  if (fieldDef.type === 'date' || fieldDef.type === 'dateTime' || fieldDef.type === 'time') {
226
- normalized[fieldName] = normalizeDateValue(value, fieldDef.type);
226
+ normalized[fieldName] = normalizeDateValue(value, fieldDef.type)
227
227
  }
228
228
  }
229
229
 
230
- return normalized;
230
+ return normalized
231
231
  }
232
232
 
233
233
  /**
234
234
  * Normalizes all records in a JSON:API response
235
- *
235
+ *
236
236
  * @param {Object} record - The JSON:API response object
237
237
  * @param {Object} scopes - All available scopes for schema lookup
238
238
  * @returns {Object} The response with normalized values
239
- *
239
+ *
240
240
  * @example
241
241
  * // Input: JSON:API response with various data types
242
242
  * const response = {
@@ -259,9 +259,9 @@ export function normalizeAttributes(attributes, schemaStructure) {
259
259
  * }
260
260
  * }]
261
261
  * };
262
- *
262
+ *
263
263
  * const normalized = normalizeRecordAttributes(response, scopes);
264
- *
264
+ *
265
265
  * // Output: All booleans and dates normalized
266
266
  * // {
267
267
  * // data: {
@@ -283,7 +283,7 @@ export function normalizeAttributes(attributes, schemaStructure) {
283
283
  * // }
284
284
  * // }]
285
285
  * // }
286
- *
286
+ *
287
287
  * @example
288
288
  * // Input: Collection response (array of records)
289
289
  * const response = {
@@ -300,20 +300,20 @@ export function normalizeAttributes(attributes, schemaStructure) {
300
300
  * }
301
301
  * ]
302
302
  * };
303
- *
303
+ *
304
304
  * // Each record in the array is normalized
305
- *
305
+ *
306
306
  * @description
307
307
  * Used by:
308
308
  * - rest-api-knex-plugin after fetching data, before sending response
309
309
  * - Applied to both GET (single) and QUERY (collection) responses
310
- *
310
+ *
311
311
  * Purpose:
312
312
  * - Ensures consistent data types in API responses
313
313
  * - Handles both primary data and included resources
314
314
  * - Works with single records and collections
315
315
  * - Uses schema definitions to determine field types
316
- *
316
+ *
317
317
  * Data flow:
318
318
  * 1. Checks if data is array (collection) or object (single)
319
319
  * 2. For each record, looks up its schema by type
@@ -321,9 +321,9 @@ export function normalizeAttributes(attributes, schemaStructure) {
321
321
  * 4. Processes included array the same way
322
322
  * 5. Returns complete response with normalized values
323
323
  */
324
- export function normalizeRecordAttributes(record, scopes) {
324
+ export function normalizeRecordAttributes (record, scopes) {
325
325
  if (!record || !scopes) {
326
- return record;
326
+ return record
327
327
  }
328
328
 
329
329
  // Normalize main data records
@@ -331,23 +331,23 @@ export function normalizeRecordAttributes(record, scopes) {
331
331
  if (Array.isArray(record.data)) {
332
332
  // Handle array of records (query result)
333
333
  for (const entry of record.data) {
334
- const scope = scopes[entry.type];
334
+ const scope = scopes[entry.type]
335
335
  if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
336
336
  entry.attributes = normalizeAttributes(
337
337
  entry.attributes,
338
338
  scope.vars.schemaInfo.schemaStructure
339
- );
339
+ )
340
340
  }
341
341
  }
342
342
  } else {
343
343
  // Handle single record (get result)
344
- const entry = record.data;
345
- const scope = scopes[entry.type];
344
+ const entry = record.data
345
+ const scope = scopes[entry.type]
346
346
  if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
347
347
  entry.attributes = normalizeAttributes(
348
348
  entry.attributes,
349
349
  scope.vars.schemaInfo.schemaStructure
350
- );
350
+ )
351
351
  }
352
352
  }
353
353
  }
@@ -355,15 +355,15 @@ export function normalizeRecordAttributes(record, scopes) {
355
355
  // Normalize included records
356
356
  if (record.included && Array.isArray(record.included)) {
357
357
  for (const entry of record.included) {
358
- const scope = scopes[entry.type];
358
+ const scope = scopes[entry.type]
359
359
  if (scope?.vars?.schemaInfo?.schemaStructure && entry.attributes) {
360
360
  entry.attributes = normalizeAttributes(
361
361
  entry.attributes,
362
362
  scope.vars.schemaInfo.schemaStructure
363
- );
363
+ )
364
364
  }
365
365
  }
366
366
  }
367
367
 
368
- return record;
369
- }
368
+ return record
369
+ }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Default data helper implementations that instruct users to install a storage plugin
3
- *
3
+ *
4
4
  * @description
5
5
  * These default implementations ensure the REST API plugin fails gracefully
6
6
  * when no storage plugin is installed. Each method throws a descriptive error
7
7
  * guiding users to install a storage implementation.
8
- *
8
+ *
9
9
  * @example
10
10
  * // When no storage plugin is installed:
11
11
  * try {
@@ -14,23 +14,23 @@
14
14
  * console.log(error.message);
15
15
  * // "No storage implementation for get. Install a storage plugin."
16
16
  * }
17
- *
17
+ *
18
18
  * @example
19
19
  * // After installing rest-api-knex-plugin:
20
20
  * api.use(RestApiKnexPlugin, { knex });
21
21
  * // Now data helpers are replaced with actual implementations
22
22
  * const article = await api.resources.articles.get({ id: 1 });
23
23
  * // Works! Returns article from database
24
- *
24
+ *
25
25
  * Used by:
26
26
  * - rest-api-plugin sets these as initial data helpers
27
27
  * - Storage plugins replace these with actual implementations
28
- *
28
+ *
29
29
  * Purpose:
30
30
  * - Provides clear error messages when storage is missing
31
31
  * - Ensures REST API plugin can be installed independently
32
32
  * - Allows storage plugins to be swapped without changing API
33
- *
33
+ *
34
34
  * Data flow:
35
35
  * 1. REST API plugin initializes with these defaults
36
36
  * 2. User tries to use API without storage plugin
@@ -38,35 +38,35 @@
38
38
  * 4. Storage plugin replaces these with real implementations
39
39
  */
40
40
  export const defaultDataHelpers = {
41
- dataExists: async function(scope, deps) {
42
- throw new Error(`No storage implementation for exists. Install a storage plugin.`);
41
+ dataExists: async function (scope, deps) {
42
+ throw new Error('No storage implementation for exists. Install a storage plugin.')
43
43
  },
44
44
 
45
- dataGet: async function(scope, deps) {
46
- throw new Error(`No storage implementation for get. Install a storage plugin.`);
45
+ dataGet: async function (scope, deps) {
46
+ throw new Error('No storage implementation for get. Install a storage plugin.')
47
47
  },
48
48
 
49
- dataGetMinimal: async function(scope, deps) {
50
- throw new Error(`No storage implementation for getMinimal. Install a storage plugin.`);
49
+ dataGetMinimal: async function (scope, deps) {
50
+ throw new Error('No storage implementation for getMinimal. Install a storage plugin.')
51
51
  },
52
-
53
- dataQuery: async function(scope, deps) {
54
- throw new Error(`No storage implementation for query. Install a storage plugin.`);
52
+
53
+ dataQuery: async function (scope, deps) {
54
+ throw new Error('No storage implementation for query. Install a storage plugin.')
55
55
  },
56
-
57
- dataPost: async function(scope, deps) {
58
- throw new Error(`No storage implementation for post. Install a storage plugin.`);
56
+
57
+ dataPost: async function (scope, deps) {
58
+ throw new Error('No storage implementation for post. Install a storage plugin.')
59
59
  },
60
60
 
61
- dataPatch: async function(scope, deps) {
62
- throw new Error(`No storage implementation for patch. Install a storage plugin.`);
61
+ dataPatch: async function (scope, deps) {
62
+ throw new Error('No storage implementation for patch. Install a storage plugin.')
63
63
  },
64
-
65
- dataPut: async function(scope, deps) {
66
- throw new Error(`No storage implementation for put. Install a storage plugin.`);
64
+
65
+ dataPut: async function (scope, deps) {
66
+ throw new Error('No storage implementation for put. Install a storage plugin.')
67
67
  },
68
-
69
- dataDelete: async function(scope, deps) {
70
- throw new Error(`No storage implementation for delete. Install a storage plugin.`);
68
+
69
+ dataDelete: async function (scope, deps) {
70
+ throw new Error('No storage implementation for delete. Install a storage plugin.')
71
71
  }
72
- };
72
+ }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Extracts all foreign key fields from a schema definition
3
- *
3
+ *
4
4
  * @param {Object} schema - The schema definition (Schema object or plain object)
5
5
  * @returns {Set<string>} Set of foreign key field names
6
- *
6
+ *
7
7
  * @example
8
8
  * // Input: Schema with belongsTo relationships
9
9
  * const schema = {
@@ -13,12 +13,12 @@
13
13
  * category_id: { type: 'number', belongsTo: 'categories', as: 'category' },
14
14
  * status: { type: 'string' }
15
15
  * };
16
- *
16
+ *
17
17
  * const foreignKeys = getForeignKeyFields(schema);
18
- *
18
+ *
19
19
  * // Output: Set containing only foreign key fields
20
20
  * // Set(['author_id', 'category_id'])
21
- *
21
+ *
22
22
  * @example
23
23
  * // Input: Works with compiled Schema objects too
24
24
  * const schemaObject = {
@@ -27,24 +27,24 @@
27
27
  * user_id: { type: 'id', belongsTo: 'users', as: 'user' }
28
28
  * }
29
29
  * };
30
- *
30
+ *
31
31
  * const foreignKeys = getForeignKeyFields(schemaObject);
32
- *
32
+ *
33
33
  * // Output: Handles both formats
34
34
  * // Set(['post_id', 'user_id'])
35
- *
35
+ *
36
36
  * @description
37
37
  * Used by:
38
38
  * - toJsonApi to filter foreign keys from attributes
39
39
  * - buildFieldSelection to ensure foreign keys are always selected
40
40
  * - knex-json-api-helpers for field filtering
41
- *
41
+ *
42
42
  * Purpose:
43
43
  * - Identifies fields that should be relationships, not attributes
44
44
  * - Foreign keys are stored in DB but not exposed as JSON:API attributes
45
45
  * - Ensures these fields are included in SELECT for relationship building
46
46
  * - Helps transform database structure to JSON:API format
47
- *
47
+ *
48
48
  * Data flow:
49
49
  * 1. Accepts schema in either format (plain or with structure property)
50
50
  * 2. Iterates through all field definitions
@@ -52,32 +52,32 @@
52
52
  * 4. Returns Set for efficient lookups
53
53
  */
54
54
  export const getForeignKeyFields = (schema) => {
55
- const foreignKeys = new Set();
56
- if (!schema) return foreignKeys;
57
-
55
+ const foreignKeys = new Set()
56
+ if (!schema) return foreignKeys
57
+
58
58
  // Handle both Schema objects and plain objects
59
- const schemaStructure = schema.structure || schema;
60
-
59
+ const schemaStructure = schema.structure || schema
60
+
61
61
  Object.entries(schemaStructure).forEach(([field, def]) => {
62
62
  if (def.belongsTo) {
63
- foreignKeys.add(field);
63
+ foreignKeys.add(field)
64
64
  }
65
- });
66
- return foreignKeys;
67
- };
65
+ })
66
+ return foreignKeys
67
+ }
68
68
 
69
69
  /**
70
70
  * Filters hidden fields from attributes based on schema rules.
71
- *
71
+ *
72
72
  * This pure function removes fields marked as hidden or normallyHidden
73
73
  * based on the schema definition and requested fields. It ensures that
74
74
  * sensitive data is never exposed in API responses.
75
- *
75
+ *
76
76
  * @param {Object} attributes - The attributes object to filter
77
77
  * @param {Object} schema - The schema object with structure property
78
78
  * @param {Array<string>|string} requestedFields - Fields explicitly requested (for normallyHidden)
79
79
  * @returns {Object} Filtered attributes object
80
- *
80
+ *
81
81
  * @example <caption>Filtering hidden fields</caption>
82
82
  * const attributes = {
83
83
  * name: 'John',
@@ -96,7 +96,7 @@ export const getForeignKeyFields = (schema) => {
96
96
  * const filtered = filterHiddenFields(attributes, schema, null);
97
97
  * // Returns: { name: 'John', email: 'john@example.com' }
98
98
  * // password_hash (hidden) and internal_id (normallyHidden) are removed
99
- *
99
+ *
100
100
  * @example <caption>Including normallyHidden fields when requested</caption>
101
101
  * const filtered = filterHiddenFields(attributes, schema, 'name,internal_id');
102
102
  * // Returns: { name: 'John', internal_id: '123' }
@@ -104,34 +104,36 @@ export const getForeignKeyFields = (schema) => {
104
104
  * // password_hash is still filtered (always hidden)
105
105
  */
106
106
  export const filterHiddenFields = (attributes, schema, requestedFields) => {
107
- const filtered = {};
108
-
107
+ const filtered = {}
108
+
109
109
  // Parse requested fields if it's a string (from query params)
110
110
  // Example: "name,price,cost" -> ['name', 'price', 'cost']
111
- const requested = requestedFields ? (
112
- typeof requestedFields === 'string'
113
- ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
114
- : requestedFields
115
- ) : null;
116
-
111
+ const requested = requestedFields
112
+ ? (
113
+ typeof requestedFields === 'string'
114
+ ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
115
+ : requestedFields
116
+ )
117
+ : null
118
+
117
119
  Object.entries(attributes).forEach(([field, value]) => {
118
- const fieldDef = schema.structure?.[field];
119
-
120
+ const fieldDef = schema.structure?.[field]
121
+
120
122
  // Never include hidden fields - these are completely invisible
121
123
  // Example: password_hash with hidden:true is always filtered out
122
- if (fieldDef?.hidden === true) return;
123
-
124
+ if (fieldDef?.hidden === true) return
125
+
124
126
  // Include normallyHidden fields only if explicitly requested
125
127
  // Example: 'cost' with normallyHidden:true is only included if user requests it
126
128
  // via sparse fieldsets like ?fields[products]=name,cost
127
129
  if (fieldDef?.normallyHidden === true) {
128
130
  if (!requested || !requested.includes(field)) {
129
- return; // Filter out normallyHidden field
131
+ return // Filter out normallyHidden field
130
132
  }
131
133
  }
132
-
133
- filtered[field] = value;
134
- });
135
-
136
- return filtered;
137
- };
134
+
135
+ filtered[field] = value
136
+ })
137
+
138
+ return filtered
139
+ }