json-rest-api 1.0.28 → 2.0.0

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 (289) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +746 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +634 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +100 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
  38. package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +36 -0
  44. package/index.js +1 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +64 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -174
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +20 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +142 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1069
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -385
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +38 -69
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +39 -82
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -150
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -118
  251. package/tests/fixtures/api-configs.js +0 -2509
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-cursor-multifield.test.js +0 -643
  264. package/tests/pagination-enhanced.test.js +0 -563
  265. package/tests/polymorphic-relationships.test.js +0 -347
  266. package/tests/polymorphic-simple.test.js +0 -148
  267. package/tests/positioning-advanced.test.js +0 -692
  268. package/tests/positioning-extended.test.js +0 -915
  269. package/tests/positioning-multifilter.test.js +0 -699
  270. package/tests/positioning.test.js +0 -560
  271. package/tests/post-commit-hooks.test.js +0 -120
  272. package/tests/projected-fields.test.js +0 -319
  273. package/tests/public-exports.test.js +0 -15
  274. package/tests/put-create-parity.test.js +0 -216
  275. package/tests/queries.test.js +0 -521
  276. package/tests/query-limits.test.js +0 -584
  277. package/tests/relationship-endpoints.test.js +0 -620
  278. package/tests/relationship-include-storage-adapter.test.js +0 -56
  279. package/tests/relationships.test.js +0 -681
  280. package/tests/rest-api.test.js +0 -337
  281. package/tests/return-record-settings.test.js +0 -184
  282. package/tests/row-policy.test.js +0 -489
  283. package/tests/s3-storage.test.js +0 -38
  284. package/tests/searchschema-knex-behavior.test.js +0 -187
  285. package/tests/searchschema-merge.test.js +0 -151
  286. package/tests/socketio.test.js +0 -860
  287. package/tests/storage-mapping.test.js +0 -362
  288. package/tests/test-development-guide.md +0 -370
  289. package/tests/virtual-fields.test.js +0 -404
@@ -0,0 +1,43 @@
1
+ import type { ResourceConversionSchema, StorageAdapter, StorageContext, StorageDatabase, StorageFieldDefinition, StorageQuery } from '../storage/storage-types.js'
2
+ import type { CompiledQueryField, ProjectionRuntime } from './query-field-types.js'
3
+ import type { Fieldsets } from './field-utils.js'
4
+
5
+ export interface SelectionSchema extends Partial<ResourceConversionSchema> {
6
+ queryFields?: Record<string, CompiledQueryField>
7
+ outputRelationships?: Record<string, StorageFieldDefinition>
8
+ readDependencies?: Record<string, readonly string[]>
9
+ }
10
+
11
+ export interface SelectionScope {
12
+ vars?: { schemaInfo?: SelectionSchema }
13
+ }
14
+
15
+ export interface SelectionContext extends StorageContext {
16
+ scopeName: string
17
+ queryParams?: { fields?: Fieldsets; sort?: unknown }
18
+ }
19
+
20
+ export interface FieldSelection {
21
+ fieldsToSelect: string[]
22
+ queryFieldsToSelect: string[]
23
+ requestedFields: readonly string[] | null
24
+ computedDependencies: string[]
25
+ idProperty: 'id'
26
+ }
27
+
28
+ export interface SelectionQueryRequest {
29
+ query: StorageQuery
30
+ scope?: SelectionScope | null
31
+ fieldSelectionInfo?: Partial<FieldSelection> | null
32
+ tableName: string
33
+ useTablePrefix?: boolean
34
+ storageAdapter?: StorageAdapter | null
35
+ db?: StorageDatabase | null
36
+ context?: StorageContext | null
37
+ scopeName?: string
38
+ }
39
+
40
+ export interface SelectedQuery {
41
+ query: StorageQuery
42
+ queryFieldRuntimeByField: Map<string, ProjectionRuntime>
43
+ }
@@ -1,71 +1,122 @@
1
+ // @ts-check
2
+ /** @import { StorageFieldDefinition } from '../storage/storage-types.js' */
3
+ /** @import { JsonApiDocument } from '../../../../types/representations.js' */
4
+ /** @typedef {string | readonly string[] | null | undefined} Fieldset */
5
+ /** @typedef {Record<string, Fieldset>} Fieldsets */
6
+ import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
7
+ import { SERIALIZATION_CAPABILITIES } from './database-capabilities.js'
8
+
9
+ /** @param {string} name @param {string} location */
10
+ export function assertFieldName (name, location) {
11
+ if (name === '__proto__') {
12
+ throw new Error(`Invalid ${location}: '__proto__' cannot be a field, filter or relationship name. Choose another name.`)
13
+ }
14
+ }
15
+
16
+ /** @param {unknown} declarations @param {string} location */
17
+ export function assertFieldNameMap (declarations, location) {
18
+ if (declarations == null) return
19
+ const prototype = typeof declarations === 'object' ? Object.getPrototypeOf(declarations) : undefined
20
+ if (typeof declarations !== 'object' || Array.isArray(declarations) || (prototype !== null && prototype !== Object.prototype)) {
21
+ throw new Error(`Invalid ${location}: Expected an object with own field declarations and a plain or null prototype.`)
22
+ }
23
+ for (const name of Object.keys(declarations)) assertFieldName(name, location)
24
+ }
25
+
26
+ /** @param {StorageFieldDefinition | null | undefined} definition @param {string} fieldName @param {string} operation */
27
+ export function assertScalarQueryField (definition, fieldName, operation) {
28
+ const type = definition?.type
29
+ if (type === undefined || !SERIALIZATION_CAPABILITIES.structuredTypes.includes(type)) return
30
+ const parameter = operation === 'sort' ? 'sort' : `filters.${fieldName}`
31
+ const message = `Field '${fieldName}' has type '${type}' and cannot be used in whole-document ${operation} operations. Use a scalar field or projection for sorting, or a custom applyFilter function for JSON queries.`
32
+ throw new RestApiValidationError(message, {
33
+ fields: [parameter],
34
+ violations: [{ field: parameter, rule: 'structured_query', message }]
35
+ })
36
+ }
37
+
1
38
  /**
2
- * Extracts all foreign key fields from a schema definition
3
- *
4
- * @param {Object} schema - The schema definition (Schema object or plain object)
5
- * @returns {Set<string>} Set of foreign key field names
6
- *
7
- * @example
8
- * // Input: Schema with belongsTo relationships
9
- * const schema = {
10
- * title: { type: 'string' },
11
- * content: { type: 'string' },
12
- * author_id: { type: 'number', belongsTo: 'users', as: 'author' },
13
- * category_id: { type: 'number', belongsTo: 'categories', as: 'category' },
14
- * status: { type: 'string' }
15
- * };
16
- *
17
- * const foreignKeys = getForeignKeyFields(schema);
18
- *
19
- * // Output: Set containing only foreign key fields
20
- * // Set(['author_id', 'category_id'])
21
- *
22
- * @example
23
- * // Input: Works with compiled Schema objects too
24
- * const schemaObject = {
25
- * structure: {
26
- * post_id: { type: 'id', belongsTo: 'posts', as: 'post' },
27
- * user_id: { type: 'id', belongsTo: 'users', as: 'user' }
28
- * }
29
- * };
30
- *
31
- * const foreignKeys = getForeignKeyFields(schemaObject);
32
- *
33
- * // Output: Handles both formats
34
- * // Set(['post_id', 'user_id'])
35
- *
36
- * @description
37
- * Used by:
38
- * - toJsonApi to filter foreign keys from attributes
39
- * - buildFieldSelection to ensure foreign keys are always selected
40
- * - knex-json-api-helpers for field filtering
41
- *
42
- * Purpose:
43
- * - Identifies fields that should be relationships, not attributes
44
- * - Foreign keys are stored in DB but not exposed as JSON:API attributes
45
- * - Ensures these fields are included in SELECT for relationship building
46
- * - Helps transform database structure to JSON:API format
47
- *
48
- * Data flow:
49
- * 1. Accepts schema in either format (plain or with structure property)
50
- * 2. Iterates through all field definitions
51
- * 3. Collects fields with belongsTo property
52
- * 4. Returns Set for efficient lookups
39
+ * Collect logical relationship backing fields from an explicit field map.
40
+ * @param {Record<string, StorageFieldDefinition> | null | undefined} schemaStructure - Compiled field definitions, not a Schema wrapper.
41
+ * @param {Record<string, { belongsToPolymorphic?: { typeField: string, idField: string } }>} [relationships] - Includes polymorphic backing fields.
42
+ * @returns {Set<string>}
53
43
  */
54
- export const getForeignKeyFields = (schema) => {
44
+ export const getForeignKeyFields = (schemaStructure, relationships = {}) => {
45
+ /** @type {Set<string>} */
55
46
  const foreignKeys = new Set()
56
- if (!schema) return foreignKeys
57
-
58
- // Handle both Schema objects and plain objects
59
- const schemaStructure = schema.structure || schema
47
+ if (!schemaStructure) return foreignKeys
60
48
 
61
49
  Object.entries(schemaStructure).forEach(([field, def]) => {
62
50
  if (def.belongsTo) {
63
51
  foreignKeys.add(field)
64
52
  }
65
53
  })
54
+ for (const definition of Object.values(relationships)) {
55
+ if (definition.belongsToPolymorphic) {
56
+ foreignKeys.add(definition.belongsToPolymorphic.typeField)
57
+ foreignKeys.add(definition.belongsToPolymorphic.idField)
58
+ }
59
+ }
66
60
  return foreignKeys
67
61
  }
68
62
 
63
+ /** @param {Fieldset} requestedFields */
64
+ export const parseFieldset = requestedFields => requestedFields == null
65
+ ? null
66
+ : typeof requestedFields === 'string'
67
+ ? requestedFields.split(',').map(field => field.trim()).filter(Boolean)
68
+ : requestedFields
69
+
70
+ /** @param {Fieldsets | null | undefined} fieldsets @param {string} resourceType */
71
+ export const getResourceFieldset = (fieldsets, resourceType) => fieldsets && Object.hasOwn(fieldsets, resourceType)
72
+ ? fieldsets[resourceType]
73
+ : undefined
74
+
75
+ /**
76
+ * @overload
77
+ * @param {JsonApiDocument | null | undefined} record
78
+ * @param {Fieldsets} [fieldsets]
79
+ * @param {{ simplified?: false, resourceType?: string }} [options]
80
+ * @returns {void}
81
+ */
82
+ /**
83
+ * @overload
84
+ * @param {Record<string, unknown> | null | undefined} record
85
+ * @param {Fieldsets | undefined} fieldsets
86
+ * @param {{ simplified: true, resourceType: string }} options
87
+ * @returns {void}
88
+ */
89
+ /**
90
+ * @param {JsonApiDocument | Record<string, unknown> | null | undefined} record
91
+ * @param {Fieldsets} [fieldsets]
92
+ * @param {{ simplified?: boolean, resourceType?: string }} [options]
93
+ */
94
+ export const filterResponseFields = (record, fieldsets = {}, { simplified = false, resourceType } = {}) => {
95
+ if (simplified) {
96
+ const fields = parseFieldset(getResourceFieldset(fieldsets, /** @type {string} */ (resourceType)))
97
+ if (fields === null || !record || typeof record !== 'object') return
98
+ const allowed = new Set(['id', '_type', ...fields])
99
+ for (const name of Object.keys(record)) if (!allowed.has(name)) delete /** @type {Record<string, unknown>} */ (record)[name]
100
+ return
101
+ }
102
+ const selections = new Map(Object.entries(fieldsets).map(([type, fields]) => [type, new Set(parseFieldset(fields))]))
103
+ // The overload ties the representation to the corresponding record shape.
104
+ const document = /** @type {JsonApiDocument | null | undefined} */ (record)
105
+ const primary = Array.isArray(document?.data) ? document.data : [document?.data]
106
+ for (const resource of [...primary, ...(document?.included || [])]) {
107
+ if (!resource) continue
108
+ const allowed = selections.get(resource.type)
109
+ if (!allowed) continue
110
+ // Selection inspects keys and passes member values through unchanged.
111
+ /** @type {Partial<Record<'attributes' | 'relationships', Record<string, unknown>>>} */
112
+ const members = resource
113
+ for (const member of /** @type {const} */ (['attributes', 'relationships'])) {
114
+ if (!members[member]) continue
115
+ members[member] = Object.fromEntries(Object.entries(members[member]).filter(([name]) => allowed.has(name)))
116
+ }
117
+ }
118
+ }
119
+
69
120
  /**
70
121
  * Filters hidden fields from attributes based on schema rules.
71
122
  *
@@ -73,48 +124,18 @@ export const getForeignKeyFields = (schema) => {
73
124
  * based on the schema definition and requested fields. It ensures that
74
125
  * sensitive data is never exposed in API responses.
75
126
  *
76
- * @param {Object} attributes - The attributes object to filter
77
- * @param {Object} schema - The schema object with structure property
78
- * @param {Array<string>|string} requestedFields - Fields explicitly requested (for normallyHidden)
79
- * @returns {Object} Filtered attributes object
80
- *
81
- * @example <caption>Filtering hidden fields</caption>
82
- * const attributes = {
83
- * name: 'John',
84
- * email: 'john@example.com',
85
- * password_hash: 'xxx',
86
- * internal_id: '123'
87
- * };
88
- * const schema = {
89
- * structure: {
90
- * name: { type: 'string' },
91
- * email: { type: 'string' },
92
- * password_hash: { type: 'string', hidden: true },
93
- * internal_id: { type: 'string', normallyHidden: true }
94
- * }
95
- * };
96
- * const filtered = filterHiddenFields(attributes, schema, null);
97
- * // Returns: { name: 'John', email: 'john@example.com' }
98
- * // password_hash (hidden) and internal_id (normallyHidden) are removed
99
- *
100
- * @example <caption>Including normallyHidden fields when requested</caption>
101
- * const filtered = filterHiddenFields(attributes, schema, 'name,internal_id');
102
- * // Returns: { name: 'John', internal_id: '123' }
103
- * // internal_id is included because explicitly requested
104
- * // password_hash is still filtered (always hidden)
127
+ * @param {Record<string, unknown>} attributes - The attributes object to filter
128
+ * @param {{ structure?: Record<string, StorageFieldDefinition> }} schema - The schema object with structure property
129
+ * @param {Fieldset} requestedFields - Fields explicitly requested (for normallyHidden)
130
+ * @returns {Record<string, unknown>} Filtered attributes object
105
131
  */
106
132
  export const filterHiddenFields = (attributes, schema, requestedFields) => {
133
+ /** @type {Record<string, unknown>} */
107
134
  const filtered = {}
108
135
 
109
136
  // Parse requested fields if it's a string (from query params)
110
137
  // Example: "name,price,cost" -> ['name', 'price', 'cost']
111
- const requested = requestedFields
112
- ? (
113
- typeof requestedFields === 'string'
114
- ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
115
- : requestedFields
116
- )
117
- : null
138
+ const requested = parseFieldset(requestedFields)
118
139
 
119
140
  Object.entries(attributes).forEach(([field, value]) => {
120
141
  const fieldDef = schema.structure?.[field]
@@ -0,0 +1,18 @@
1
+ export declare const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__'
2
+ export declare const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__'
3
+ export declare const ROW_NUMBER_KEY = '__$jsonrestapi_rn$__'
4
+ export declare const COMPUTED_DEPENDENCIES_KEY = '__$jsonrestapi_computed_deps$__'
5
+
6
+ export declare const DEFAULT_QUERY_LIMIT = 20
7
+ export declare const DEFAULT_MAX_QUERY_LIMIT = 100
8
+ export declare const DEFAULT_MAX_INCLUDE_LIMIT = 1000
9
+ export declare const DEFAULT_INCLUDE_DEPTH_LIMIT = 3
10
+ export declare const RELATIONSHIP_WRITE_BATCH_SIZE = 100
11
+ export declare const RELATIONSHIP_READ_BATCH_SIZE = 100
12
+
13
+ export declare const ERROR_SUBTYPES: {
14
+ NOT_FOUND: string
15
+ VALIDATION: string
16
+ PERMISSION: string
17
+ CONFLICT: string
18
+ }
@@ -1,11 +1,3 @@
1
- /**
2
- * @module knex-constants
3
- * @description Shared constants for the REST API Knex plugin
4
- *
5
- * This module contains all the magic strings and shared constants used
6
- * throughout the plugin to avoid repetition and improve maintainability.
7
- */
8
-
9
1
  // Internal property names - using unique names to avoid collisions with user data
10
2
  export const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__'
11
3
  export const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__'
@@ -17,24 +9,8 @@ export const DEFAULT_QUERY_LIMIT = 20
17
9
  export const DEFAULT_MAX_QUERY_LIMIT = 100
18
10
  export const DEFAULT_MAX_INCLUDE_LIMIT = 1000
19
11
  export const DEFAULT_INCLUDE_DEPTH_LIMIT = 3
20
-
21
- // Schema handling helpers
22
- export const getSchemaStructure = (schema) => schema?.structure || schema || {}
23
- export const getIdProperty = (schemaInfo) => schemaInfo?.idProperty || 'id'
24
-
25
- // Default values
26
- export const DEFAULT_PAGE_SIZE = 20
27
- export const DEFAULT_MAX_PAGE_SIZE = 100
28
- export const DEFAULT_PAGE_NUMBER = 1
29
-
30
- // Database clients
31
- export const DB_CLIENTS = {
32
- POSTGRES: 'pg',
33
- MYSQL: 'mysql',
34
- MYSQL2: 'mysql2',
35
- SQLITE: 'sqlite3',
36
- MSSQL: 'mssql'
37
- }
12
+ export const RELATIONSHIP_WRITE_BATCH_SIZE = 100
13
+ export const RELATIONSHIP_READ_BATCH_SIZE = 100
38
14
 
39
15
  // Error subtypes
40
16
  export const ERROR_SUBTYPES = {