json-rest-api 1.0.29 → 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 (292) 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 +744 -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} +74 -30
  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 -177
  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 +48 -317
  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 +134 -37
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +13 -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 +179 -194
  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 +100 -290
  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 +93 -38
  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 +128 -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 -2485
  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 -1077
  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 -395
  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 -148
  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 +36 -68
  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 +37 -81
  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 -1247
  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 -148
  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 -155
  251. package/tests/fixtures/api-configs.js +0 -2566
  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-boundaries.test.js +0 -154
  264. package/tests/pagination-contract.test.js +0 -122
  265. package/tests/pagination-cursor-multifield.test.js +0 -641
  266. package/tests/pagination-enhanced.test.js +0 -563
  267. package/tests/polymorphic-relationships.test.js +0 -347
  268. package/tests/polymorphic-simple.test.js +0 -148
  269. package/tests/positioning-advanced.test.js +0 -692
  270. package/tests/positioning-extended.test.js +0 -915
  271. package/tests/positioning-multifilter.test.js +0 -699
  272. package/tests/positioning.test.js +0 -560
  273. package/tests/post-commit-hooks.test.js +0 -120
  274. package/tests/projected-fields.test.js +0 -319
  275. package/tests/public-exports.test.js +0 -15
  276. package/tests/put-create-parity.test.js +0 -216
  277. package/tests/queries.test.js +0 -521
  278. package/tests/query-limits.test.js +0 -584
  279. package/tests/relationship-endpoints.test.js +0 -620
  280. package/tests/relationship-include-storage-adapter.test.js +0 -56
  281. package/tests/relationships.test.js +0 -681
  282. package/tests/rest-api.test.js +0 -337
  283. package/tests/return-record-settings.test.js +0 -184
  284. package/tests/row-policy.test.js +0 -489
  285. package/tests/s3-storage.test.js +0 -38
  286. package/tests/searchschema-knex-behavior.test.js +0 -187
  287. package/tests/searchschema-merge.test.js +0 -151
  288. package/tests/socketio.test.js +0 -860
  289. package/tests/storage-mapping.test.js +0 -362
  290. package/tests/temporal-boundaries.test.js +0 -208
  291. package/tests/test-development-guide.md +0 -370
  292. package/tests/virtual-fields.test.js +0 -404
@@ -4,7 +4,7 @@
4
4
  * Production-ready local storage with secure filename handling
5
5
  */
6
6
 
7
- import { promises as fs } from 'fs'
7
+ import { createReadStream, promises as fs } from 'fs'
8
8
  import path from 'path'
9
9
  import crypto from 'crypto'
10
10
 
@@ -147,25 +147,16 @@ export class LocalStorage {
147
147
  */
148
148
  async ensureUnique (filename) {
149
149
  const filepath = this.resolveStoragePath(filename)
150
-
151
- try {
152
- await fs.access(filepath)
153
- // File exists, generate unique name
154
- const ext = path.extname(filename)
155
- const base = path.basename(filename, ext)
156
- let counter = 1
157
- let newFilename
158
-
159
- do {
160
- newFilename = `${base}_${counter}${ext}`
161
- counter++
162
- } while (await this.fileExists(this.resolveStoragePath(newFilename)))
163
-
164
- return newFilename
165
- } catch (error) {
166
- // File doesn't exist, name is unique
167
- return filename
168
- }
150
+ if (!await this.fileExists(filepath)) return filename
151
+ const ext = path.extname(filename)
152
+ const base = path.basename(filename, ext)
153
+ let counter = 1
154
+ let newFilename
155
+ do {
156
+ newFilename = `${base}_${counter}${ext}`
157
+ counter++
158
+ } while (await this.fileExists(this.resolveStoragePath(newFilename)))
159
+ return newFilename
169
160
  }
170
161
 
171
162
  /**
@@ -173,9 +164,10 @@ export class LocalStorage {
173
164
  */
174
165
  async fileExists (filepath) {
175
166
  try {
176
- await fs.access(filepath)
167
+ await fs.lstat(filepath)
177
168
  return true
178
- } catch {
169
+ } catch (error) {
170
+ if (error.code !== 'ENOENT') throw error
179
171
  return false
180
172
  }
181
173
  }
@@ -205,24 +197,36 @@ export class LocalStorage {
205
197
  * Upload file with secure filename
206
198
  */
207
199
  async upload (file) {
200
+ if (!file.data && !file.filepath) throw new Error('File has no data or filepath')
208
201
  // Ensure directory exists
209
202
  await fs.mkdir(this.directory, { recursive: true })
210
203
 
211
- // Generate secure filename
212
- const filename = await this.generateFilename(file)
213
- const filepath = this.resolveStoragePath(filename)
214
-
215
- // Write file
216
- if (file.data) {
217
- await fs.writeFile(filepath, file.data)
218
- } else if (file.filepath) {
219
- await fs.rename(file.filepath, filepath)
220
- } else {
221
- throw new Error('File has no data or filepath')
204
+ const requested = await this.generateFilename(file)
205
+ let filename = requested
206
+ for (;;) {
207
+ const filepath = this.resolveStoragePath(filename)
208
+ let handle
209
+ try {
210
+ handle = await fs.open(filepath, 'wx')
211
+ } catch (error) {
212
+ if (error.code !== 'EEXIST') throw error
213
+ filename = await this.ensureUnique(requested)
214
+ continue
215
+ }
216
+ try {
217
+ await handle.writeFile(file.data || createReadStream(file.filepath))
218
+ await handle.close()
219
+ if (!file.data) await fs.unlink(file.filepath)
220
+ return `${this.fileBaseUrl}/${filename}`
221
+ } catch (error) {
222
+ // Only this upload's exclusively reserved destination may be removed.
223
+ const cleanupErrors = []
224
+ try { await handle.close() } catch (cleanupError) { cleanupErrors.push(cleanupError) }
225
+ try { await fs.unlink(filepath) } catch (cleanupError) { if (cleanupError.code !== 'ENOENT') cleanupErrors.push(cleanupError) }
226
+ if (cleanupErrors.length) throw new AggregateError([error, ...cleanupErrors], 'Upload failed and its destination could not be fully cleaned up', { cause: error })
227
+ throw error
228
+ }
222
229
  }
223
-
224
- // Return public URL
225
- return `${this.fileBaseUrl}/${filename}`
226
230
  }
227
231
 
228
232
  /**
@@ -0,0 +1,57 @@
1
+ import type { StorageFieldDefinition } from '../plugins/core/lib/storage/storage-types.js'
2
+
3
+ export interface AutoFilterResolverParams<Context extends object = Record<string, unknown>, Api extends object = object> {
4
+ context: Context
5
+ scopeName: string
6
+ filter: CompiledAutoFilter<Context, Api>
7
+ api: Api
8
+ helpers: Record<string, unknown>
9
+ scopes: Record<string, unknown>
10
+ vars: Record<string, unknown>
11
+ log: unknown
12
+ }
13
+
14
+ // The field serializer owns the value domain; undefined is meaningful for optional filters.
15
+ export type AutoFilterResolver<Context extends object = Record<string, unknown>, Api extends object = object> = (
16
+ params: AutoFilterResolverParams<Context, Api>
17
+ ) => unknown
18
+
19
+ export interface CompiledAutoFilter<Context extends object = Record<string, unknown>, Api extends object = object> {
20
+ field: string
21
+ fieldDef: StorageFieldDefinition
22
+ resolve: AutoFilterResolver<Context, Api>
23
+ resolverName: string
24
+ required: boolean
25
+ relationshipName: string | null
26
+ relationshipType: string | null
27
+ inputPath: string
28
+ }
29
+
30
+ export type AutoFilterDefinition<Context extends object = Record<string, unknown>, Api extends object = object> = {
31
+ field: string
32
+ required?: boolean
33
+ } & (
34
+ { resolve: string | AutoFilterResolver<Context, Api>; resolver?: string | AutoFilterResolver<Context, Api> }
35
+ | { resolve?: undefined | null; resolver: string | AutoFilterResolver<Context, Api> }
36
+ )
37
+
38
+ export type AutoFilterPreset<Context extends object = Record<string, unknown>, Api extends object = object> =
39
+ AutoFilterDefinition<Context, Api>[] | { filters: AutoFilterDefinition<Context, Api>[] }
40
+
41
+ export interface AutoFilterPluginOptions<Context extends object = Record<string, unknown>, Api extends object = object> {
42
+ resolvers?: Record<string, AutoFilterResolver<Context, Api>>
43
+ presets?: Record<string, AutoFilterPreset<Context, Api>>
44
+ }
45
+
46
+ export type ResourceAutoFilter<Context extends object = Record<string, unknown>, Api extends object = object> =
47
+ string | false | null | AutoFilterDefinition<Context, Api>[]
48
+ | { preset: string; filters?: AutoFilterDefinition<Context, Api>[] }
49
+ | { preset?: string; filters: AutoFilterDefinition<Context, Api>[] }
50
+
51
+ export interface AutoFilterRegistry {
52
+ getConfig(): { presets: string[]; resolvers: string[] }
53
+ getScopeConfig(scopeName: string): {
54
+ preset: string | null
55
+ filters: Array<{ field: string; resolver: string; required: boolean }>
56
+ } | null
57
+ }
@@ -0,0 +1,63 @@
1
+ import type { ManagedTransaction } from './transactions.js'
2
+ import type { TransactionOutcome } from './errors.js'
3
+ import type { DirectResourceId, InputResourceId, JsonApiResource, PlainResource, ResourceFormat, ResourceIdentifier, WriteReturning } from './representations.js'
4
+ import type { JsonApiWriteDocument, RemovedResourceOptions, ResourceWriteInput, WriteOptions } from './resource-methods.js'
5
+
6
+ export type BulkTransactionOptions<DefaultAtomic extends boolean = true> =
7
+ { atomic: true; transaction?: ManagedTransaction } |
8
+ { atomic: false; transaction?: never } |
9
+ { atomic?: undefined; transaction?: DefaultAtomic extends true ? ManagedTransaction : never }
10
+ export interface BulkError {
11
+ index: number
12
+ id?: DirectResourceId
13
+ status: 'error'
14
+ error: { code: unknown; message: string; details?: unknown; transactionOutcome: TransactionOutcome }
15
+ }
16
+ export interface BulkMeta {
17
+ total: number
18
+ succeeded: number
19
+ failed: number
20
+ atomic: boolean
21
+ }
22
+ export type BulkWriteResult<Format extends ResourceFormat, Returning extends WriteReturning, Fields extends object, Type extends string> =
23
+ { meta: BulkMeta; errors?: BulkError[] } & (Returning extends 'none'
24
+ ? { data?: never }
25
+ : { data: Array<Returning extends 'minimal' ? ResourceIdentifier<Type> : Format extends 'plain' ? PlainResource<Fields> : JsonApiResource<Fields, Type>> })
26
+ export interface BulkDeleteResult {
27
+ meta: BulkMeta & { deleted: DirectResourceId[] }
28
+ errors?: BulkError[]
29
+ }
30
+ export type BulkPostInput<Input extends object, Type extends string> =
31
+ { data: Array<Partial<Input> & { id?: InputResourceId }>; document?: never } |
32
+ { data?: never; document: Omit<JsonApiWriteDocument<Input, Type>, 'data'> & { data: JsonApiWriteDocument<Input, Type>['data'][] } }
33
+ export type BulkPatchInput<Input extends object, Type extends string> = {
34
+ id: DirectResourceId
35
+ } & ResourceWriteInput<Input, Type>
36
+ export interface BulkResourceMethods<
37
+ Fields extends object = Record<string, unknown>, Input extends object = Fields,
38
+ Type extends string = string, DefaultFormat extends ResourceFormat = 'plain',
39
+ DefaultReturning extends WriteReturning = 'full', DefaultAtomic extends boolean = true,
40
+ Context extends object = object
41
+ > {
42
+ bulkPost<Format extends ResourceFormat = DefaultFormat, Returning extends WriteReturning = DefaultReturning>(
43
+ params: Omit<WriteOptions<Format, Returning>, 'queryParams'> & BulkTransactionOptions<DefaultAtomic> & BulkPostInput<Input, Type> & {
44
+ expectedVersion?: never
45
+ expectedVersions?: never
46
+ }, context?: Context
47
+ ): Promise<BulkWriteResult<Format, Returning, Fields, Type>>
48
+ bulkPatch<Format extends ResourceFormat = DefaultFormat, Returning extends WriteReturning = DefaultReturning>(
49
+ params: Omit<WriteOptions<Format, Returning>, 'queryParams'> & BulkTransactionOptions<DefaultAtomic> & {
50
+ operations: BulkPatchInput<Input, Type>[]
51
+ expectedVersion?: never
52
+ expectedVersions?: string[]
53
+ }, context?: Context
54
+ ): Promise<BulkWriteResult<Format, Returning, Fields, Type>>
55
+ bulkDelete(
56
+ params: RemovedResourceOptions & BulkTransactionOptions<DefaultAtomic> & {
57
+ ids: DirectResourceId[]
58
+ format?: ResourceFormat
59
+ expectedVersion?: never
60
+ expectedVersions?: string[]
61
+ }, context?: Context
62
+ ): Promise<BulkDeleteResult>
63
+ }
@@ -0,0 +1,118 @@
1
+ export class RestApiError extends Error {
2
+ constructor(message: string, code?: unknown)
3
+ code: unknown
4
+ }
5
+
6
+ export type TransactionOutcome = 'none' | 'pending' | 'committed' | 'rolledBack' | 'unknown'
7
+ export interface ResourceErrorOptions {
8
+ resourceType?: string
9
+ resourceId?: string | number
10
+ }
11
+ export interface ValidationViolation {
12
+ field: string
13
+ rule?: string
14
+ message: string
15
+ }
16
+ export interface SerializedError {
17
+ name: string
18
+ message: string
19
+ stack?: string
20
+ [key: string]: unknown
21
+ }
22
+
23
+ export const REST_API_FIELDSET_ERROR_CODE: 'REST_API_FIELDSET_INVALID'
24
+ export const REST_API_INCLUDE_ERROR_CODE: 'REST_API_INCLUDE_INVALID'
25
+ export const REST_API_TEMPORAL_DATA_ERROR_CODE: 'REST_API_TEMPORAL_DATA_INVALID'
26
+
27
+ export class RestApiWriteError extends RestApiError {
28
+ constructor(message: string, options: { cause?: unknown; transactionOutcome: TransactionOutcome })
29
+ cause?: unknown
30
+ readonly transactionOutcome: TransactionOutcome
31
+ code: unknown
32
+ type?: unknown
33
+ subtype?: unknown
34
+ details?: unknown
35
+ statusCode?: unknown
36
+ status?: unknown
37
+ path?: unknown
38
+ parameter?: unknown
39
+ validation?: unknown
40
+ toJSON(): SerializedError & { transactionOutcome: TransactionOutcome }
41
+ }
42
+
43
+ export class RestApiValidationError extends RestApiError {
44
+ constructor(message: string, options?: { fields?: string[]; violations?: ValidationViolation[] })
45
+ code: 'REST_API_VALIDATION'
46
+ type: 'rest_api_validation'
47
+ details: { fields: string[]; violations: ValidationViolation[] }
48
+ toJSON(): SerializedError & Pick<RestApiValidationError, 'code' | 'type' | 'details'>
49
+ }
50
+
51
+ export class RestApiResourceError extends RestApiError {
52
+ constructor(message: string, options?: ResourceErrorOptions & { subtype?: string })
53
+ code: string
54
+ type: string
55
+ subtype: string | undefined
56
+ details: ResourceErrorOptions
57
+ toJSON(): SerializedError & Pick<RestApiResourceError, 'code' | 'type' | 'subtype' | 'details'>
58
+ }
59
+
60
+ export class RestApiVersionConflictError extends RestApiResourceError {
61
+ constructor(options?: ResourceErrorOptions)
62
+ code: 'REST_API_VERSION_CONFLICT'
63
+ type: 'rest_api_version_conflict'
64
+ subtype: 'conflict'
65
+ }
66
+
67
+ export class RestApiPreconditionFailedError extends RestApiResourceError {
68
+ constructor(options?: ResourceErrorOptions)
69
+ code: 'REST_API_PRECONDITION_FAILED'
70
+ type: 'rest_api_precondition_failed'
71
+ subtype: 'precondition_failed'
72
+ }
73
+
74
+ export class RestApiPayloadError extends RestApiError {
75
+ constructor(message: string, options?: {
76
+ path?: string
77
+ parameter?: string
78
+ expected?: unknown
79
+ received?: unknown
80
+ statusCode?: number
81
+ cause?: unknown
82
+ })
83
+ code: 'REST_API_PAYLOAD'
84
+ type: 'rest_api_payload'
85
+ statusCode: number
86
+ cause?: unknown
87
+ path: string | undefined
88
+ parameter: string | undefined
89
+ details: { path: string | undefined; expected: unknown; received: unknown }
90
+ toJSON(): SerializedError & Pick<RestApiPayloadError, 'code' | 'type' | 'statusCode' | 'path' | 'parameter' | 'details'>
91
+ }
92
+
93
+ export class RestApiFieldsetError extends RestApiError {
94
+ constructor(options?: { field?: string; resourceType?: string })
95
+ code: typeof REST_API_FIELDSET_ERROR_CODE
96
+ type: 'rest_api_fieldset'
97
+ statusCode: number
98
+ details: { field?: string; resourceType?: string }
99
+ toJSON(): SerializedError & Pick<RestApiFieldsetError, 'code' | 'type' | 'statusCode' | 'details'>
100
+ }
101
+
102
+ export class RestApiIncludeError extends RestApiError {
103
+ constructor(options?: { path?: string; resourceType?: string })
104
+ code: typeof REST_API_INCLUDE_ERROR_CODE
105
+ type: 'rest_api_include'
106
+ statusCode: number
107
+ details: { path?: string; resourceType?: string }
108
+ toJSON(): SerializedError & Pick<RestApiIncludeError, 'code' | 'type' | 'statusCode' | 'details'>
109
+ }
110
+
111
+ export class RestApiTemporalDataError extends RestApiError {
112
+ constructor(options?: { field?: string; resourceType?: string; fieldType?: string; source?: string })
113
+ code: typeof REST_API_TEMPORAL_DATA_ERROR_CODE
114
+ type: 'rest_api_temporal_data'
115
+ statusCode: number
116
+ details: { field?: string; resourceType?: string; fieldType?: string; source: string }
117
+ toJSON(): SerializedError & Pick<RestApiTemporalDataError, 'code' | 'type' | 'statusCode' | 'details'>
118
+ }
@@ -0,0 +1,19 @@
1
+ import type { HttpConnectorOptions } from './plugin-options.js'
2
+
3
+ // This checks supplied host capabilities; it does not replace Fastify's API types.
4
+ type HostMethod = (...args: never[]) => unknown
5
+
6
+ export interface FastifyHost {
7
+ route: HostMethod
8
+ register: HostMethod
9
+ hasContentTypeParser: HostMethod
10
+ removeContentTypeParser: HostMethod
11
+ addContentTypeParser: HostMethod
12
+ setErrorHandler: HostMethod
13
+ addHook: HostMethod
14
+ setNotFoundHandler: HostMethod
15
+ }
16
+
17
+ export interface FastifyPluginOptions<App extends FastifyHost = FastifyHost> extends HttpConnectorOptions {
18
+ app: App
19
+ }
@@ -0,0 +1,33 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+ import type { StorageFile } from './file-storage.js'
3
+
4
+ export interface DetectedFile extends StorageFile {
5
+ mimetype: string
6
+ size: number
7
+ }
8
+ export interface ParsedFiles {
9
+ fields?: Record<string, unknown>
10
+ files?: Record<string, DetectedFile>
11
+ }
12
+ export interface FileDetector<Params extends object = Record<string, unknown>, Context extends object = object> {
13
+ name: string
14
+ detect(params: Params, context?: Context): boolean | PromiseLike<boolean>
15
+ parse(params: Params, context?: Context): ParsedFiles | null | undefined | PromiseLike<ParsedFiles | null | undefined>
16
+ }
17
+ export interface ExpressFileParserParams extends Record<string, unknown> {
18
+ _expressReq: IncomingMessage
19
+ _expressRes?: ServerResponse
20
+ }
21
+ export type ExpressFileParserFactory = (
22
+ options: Record<string, unknown>
23
+ ) => FileDetector<ExpressFileParserParams> | PromiseLike<FileDetector<ExpressFileParserParams>>
24
+ export interface ExpressFileOptions {
25
+ enableFileUploads?: boolean
26
+ fileParser?: 'busboy' | 'formidable' | ExpressFileParserFactory
27
+ // Parser-specific peer options are passed through by the connector.
28
+ fileParserOptions?: Record<string, unknown>
29
+ }
30
+ export interface FileDetectorRegistry {
31
+ registerFileDetector(detector: FileDetector): void
32
+ fileDetectors: FileDetector[]
33
+ }
@@ -0,0 +1,48 @@
1
+ import type { writeFile } from 'node:fs/promises'
2
+
3
+ export type FileContents = Parameters<typeof writeFile>[1]
4
+ export interface StorageFile {
5
+ filename: string
6
+ mimetype?: string
7
+ size?: number
8
+ data?: FileContents
9
+ filepath?: string
10
+ cleanup?: () => unknown
11
+ }
12
+ export type UploadFile = StorageFile & ({ data: FileContents } | { filepath: string })
13
+ export interface FileStorage<File extends StorageFile = UploadFile> {
14
+ upload(file: File): string | PromiseLike<string>
15
+ delete(url: string): void | PromiseLike<void>
16
+ }
17
+ export type FileNameGenerator = (file: StorageFile) => string | Promise<string>
18
+ export type LocalStorageOptions = {
19
+ directory?: string
20
+ fileBaseUrl?: string
21
+ preserveExtension?: boolean
22
+ allowedExtensions?: string[]
23
+ maxFilenameLength?: number
24
+ } & (
25
+ { nameStrategy: 'custom'; nameGenerator: FileNameGenerator } |
26
+ { nameStrategy?: 'hash' | 'timestamp' | 'original'; nameGenerator?: FileNameGenerator }
27
+ )
28
+
29
+ export class LocalStorage implements FileStorage {
30
+ constructor(options?: LocalStorageOptions)
31
+ directory: string
32
+ fileBaseUrl: string
33
+ nameStrategy: 'hash' | 'timestamp' | 'original' | 'custom'
34
+ nameGenerator?: FileNameGenerator
35
+ preserveExtension: boolean
36
+ allowedExtensions?: string[]
37
+ maxFilenameLength: number
38
+ hasPathSegments(filename: string): boolean
39
+ validateCustomBasename(basename: unknown): string
40
+ resolveStoragePath(filename: string): string
41
+ generateFilename(file: StorageFile): Promise<string>
42
+ sanitizeFilename(filename: string): string
43
+ ensureUnique(filename: string): Promise<string>
44
+ fileExists(filepath: string): Promise<boolean>
45
+ getExtensionFromMimeType(mimetype?: string): string
46
+ upload(file: UploadFile): Promise<string>
47
+ delete(url: string): Promise<void>
48
+ }
@@ -0,0 +1,180 @@
1
+ import type { Knex } from 'knex'
2
+ import type { CleanupDiagnostic } from '../lib/transaction-types.js'
3
+ import type { QueryFilteringState, StorageAdapter } from '../plugins/core/lib/storage/storage-types.js'
4
+ import type { TransactionOutcome } from './errors.js'
5
+ import type { DirectResourceId, InputResourceId, InputResourceIdentifier, JsonApiDocument, JsonApiResource, PlainResource, ResourceFormat, WriteReturning } from './representations.js'
6
+ import type { JsonApiWriteDocument, QueryParams } from './resource-methods.js'
7
+ import type { RuntimeArguments } from './runtime.js'
8
+ import type { ManagedTransaction } from './transactions.js'
9
+
10
+ interface LibraryHookFields {
11
+ readonly method?: string
12
+ readonly scopeName?: string
13
+ readonly id?: DirectResourceId
14
+ readonly relationshipName?: string
15
+ readonly isUpdate?: boolean
16
+ readonly schemaInfo?: Readonly<Record<string, unknown>>
17
+ readonly params?: Readonly<Record<string, unknown>>
18
+ readonly queryParams?: Readonly<QueryParams>
19
+ readonly format?: ResourceFormat
20
+ readonly returning?: WriteReturning
21
+ readonly simplified?: boolean
22
+ readonly transaction?: Knex.Transaction
23
+ readonly db?: Knex | Knex.Transaction
24
+ readonly shouldCommit?: boolean
25
+ readonly transactionOutcome?: TransactionOutcome
26
+ readonly transactionCommitted?: boolean
27
+ readonly error?: unknown
28
+ readonly cleanupErrors?: readonly CleanupDiagnostic[]
29
+ readonly minimalRecord?: unknown
30
+ readonly originalMinimalRecord?: unknown
31
+ readonly originalInputAttributes?: Readonly<Record<string, unknown>>
32
+ readonly originalRecord?: unknown
33
+ readonly inputRecord?: unknown
34
+ readonly record?: unknown
35
+ readonly responseRecord?: unknown
36
+ readonly knexQuery?: QueryFilteringState | null
37
+ readonly storageAdapter?: StorageAdapter
38
+ }
39
+
40
+ // Readonly marks library ownership; it does not freeze runtime context or nested objects.
41
+ type ContextWithFields<Context extends object, Fields extends object> =
42
+ Omit<Context, keyof LibraryHookFields | 'originalContext' | 'parentContext' | 'attributes'> &
43
+ Omit<LibraryHookFields, keyof Fields> & Fields
44
+
45
+ export type HookContext<Context extends object = Record<string, unknown>> = ContextWithFields<Context, LibraryHookFields>
46
+
47
+ type WriteMethod = 'post' | 'put' | 'patch'
48
+ type ReadMethod = 'get' | 'query'
49
+ type RelationshipMethod = 'getRelationship' | 'getRelated' | 'postRelationship' | 'patchRelationship' | 'deleteRelationship'
50
+
51
+ // Early processing receives an object, but its document structure is not validated yet.
52
+ interface ProcessingFields<Method extends WriteMethod> {
53
+ readonly method: Method
54
+ readonly scopeName: string
55
+ readonly format: ResourceFormat
56
+ readonly returning: WriteReturning
57
+ readonly transaction: ManagedTransaction
58
+ readonly inputRecord: Record<string, unknown>
59
+ }
60
+ export type ProcessingHookContext<Context extends object = Record<string, unknown>, Method extends WriteMethod = WriteMethod> =
61
+ ContextWithFields<Context, ProcessingFields<Method>>
62
+
63
+ // Identity and relationship planning are fixed before the schema-validation hooks.
64
+ type HookWriteDocument = Omit<JsonApiWriteDocument<Record<string, unknown>, string>, 'data'> & {
65
+ readonly data: {
66
+ readonly type: string
67
+ readonly id?: InputResourceId
68
+ attributes?: Record<string, unknown>
69
+ readonly relationships?: Readonly<Record<string, {
70
+ readonly data: Readonly<InputResourceIdentifier> | readonly Readonly<InputResourceIdentifier>[] | null
71
+ }>>
72
+ }
73
+ }
74
+
75
+ // The document structure is checked here; individual attribute values remain unknown.
76
+ interface WriteFields<Method extends WriteMethod> extends Omit<ProcessingFields<Method>, 'inputRecord'> {
77
+ readonly inputRecord: HookWriteDocument
78
+ }
79
+ export type WriteHookContext<Context extends object = Record<string, unknown>, Method extends WriteMethod = WriteMethod> =
80
+ ContextWithFields<Context, WriteFields<Method>>
81
+
82
+ export type WriteFinishHookContext<Context extends object = Record<string, unknown>, Method extends WriteMethod = WriteMethod> =
83
+ ContextWithFields<Context, Omit<WriteFields<Method>, 'format'> & (
84
+ { readonly format: 'plain'; responseRecord?: PlainResource } |
85
+ { readonly format: 'jsonapi'; responseRecord?: JsonApiDocument<JsonApiResource> }
86
+ )>
87
+
88
+ interface ReadFields<Method extends ReadMethod> {
89
+ readonly method: Method
90
+ readonly scopeName: string
91
+ readonly format: ResourceFormat
92
+ }
93
+ export type ReadHookContext<Context extends object = Record<string, unknown>, Method extends ReadMethod = ReadMethod> =
94
+ ContextWithFields<Context, ReadFields<Method>>
95
+
96
+ export type ReadResultHookContext<Context extends object = Record<string, unknown>, Method extends ReadMethod = ReadMethod> =
97
+ ContextWithFields<Context, ReadFields<Method> & {
98
+ record: JsonApiDocument<Method extends 'get' ? JsonApiResource : JsonApiResource[]>
99
+ }>
100
+
101
+ export type RelationshipHookContext<Context extends object = Record<string, unknown>, Method extends RelationshipMethod = RelationshipMethod> =
102
+ HookContext<Context> & {
103
+ readonly method: Method
104
+ readonly id: DirectResourceId
105
+ readonly relationshipName: string
106
+ readonly originalContext?: undefined
107
+ }
108
+
109
+ // CRUD permission checks use a wrapper; relationship hooks receive their operation directly.
110
+ export type PermissionHookContext<Context extends object = Record<string, unknown>> =
111
+ (Record<string, unknown> & { readonly method: string; readonly originalContext: HookContext<Context> }) |
112
+ RelationshipHookContext<Context>
113
+
114
+ export type EnrichmentHookContext<Context extends object = Record<string, unknown>> = {
115
+ readonly parentContext: ReadResultHookContext<Context>
116
+ readonly scopeName: string
117
+ readonly computedFields: Readonly<Record<string, unknown>>
118
+ readonly requestedComputedFields: readonly string[]
119
+ attributes: Record<string, unknown>
120
+ [customProperty: string]: unknown
121
+ }
122
+
123
+ export interface HookContextMap<Context extends object = Record<string, unknown>> {
124
+ beforeProcessing: ProcessingHookContext<Context>
125
+ beforeProcessingPost: ProcessingHookContext<Context, 'post'>
126
+ beforeProcessingPut: ProcessingHookContext<Context, 'put'>
127
+ beforeProcessingPatch: ProcessingHookContext<Context, 'patch'>
128
+ beforeSchemaValidate: WriteHookContext<Context>
129
+ beforeSchemaValidatePost: WriteHookContext<Context, 'post'>
130
+ beforeSchemaValidatePut: WriteHookContext<Context, 'put'>
131
+ beforeSchemaValidatePatch: WriteHookContext<Context, 'patch'>
132
+ afterSchemaValidate: WriteHookContext<Context>
133
+ afterSchemaValidatePost: WriteHookContext<Context, 'post'>
134
+ afterSchemaValidatePut: WriteHookContext<Context, 'put'>
135
+ afterSchemaValidatePatch: WriteHookContext<Context, 'patch'>
136
+ beforeDataCall: HookContext<Context>
137
+ beforeDataCallPost: WriteHookContext<Context, 'post'>
138
+ beforeDataCallPut: WriteHookContext<Context, 'put'>
139
+ beforeDataCallPatch: WriteHookContext<Context, 'patch'>
140
+ beforeDataCallDelete: HookContext<Context> & { readonly method: 'delete'; readonly id: DirectResourceId }
141
+ beforeDataCallPostRelationship: RelationshipHookContext<Context, 'postRelationship'>
142
+ beforeDataCallDeleteRelationship: RelationshipHookContext<Context, 'deleteRelationship'>
143
+ afterDataCall: HookContext<Context>
144
+ afterDataCallPost: WriteHookContext<Context, 'post'> & { readonly id: DirectResourceId }
145
+ afterDataCallPut: WriteHookContext<Context, 'put'> & { readonly id: DirectResourceId }
146
+ afterDataCallPatch: WriteHookContext<Context, 'patch'> & { readonly id: DirectResourceId }
147
+ afterDataCallDelete: HookContext<Context> & { readonly method: 'delete'; readonly id: DirectResourceId }
148
+ beforeData: ReadHookContext<Context>
149
+ beforeDataGet: ReadHookContext<Context, 'get'>
150
+ beforeDataQuery: ReadHookContext<Context, 'query'>
151
+ checkPermissions: PermissionHookContext<Context>
152
+ checkPermissionsGetRelationship: RelationshipHookContext<Context, 'getRelationship'>
153
+ checkPermissionsGetRelated: RelationshipHookContext<Context, 'getRelated'>
154
+ checkPermissionsPostRelationship: RelationshipHookContext<Context, 'postRelationship'>
155
+ checkPermissionsPatchRelationship: RelationshipHookContext<Context, 'patchRelationship'>
156
+ checkPermissionsDeleteRelationship: RelationshipHookContext<Context, 'deleteRelationship'>
157
+ checkDataPermissions: ReadResultHookContext<Context, 'get'>
158
+ checkDataPermissionsGet: ReadResultHookContext<Context, 'get'>
159
+ enrichRecord: ReadResultHookContext<Context>
160
+ enrichRecordWithRelationships: ReadResultHookContext<Context, 'get'>
161
+ enrichAttributes: EnrichmentHookContext<Context>
162
+ finish: HookContext<Context>
163
+ finishPost: WriteFinishHookContext<Context, 'post'>
164
+ finishPut: WriteFinishHookContext<Context, 'put'>
165
+ finishPatch: WriteFinishHookContext<Context, 'patch'>
166
+ finishGet: ReadResultHookContext<Context, 'get'>
167
+ finishQuery: ReadResultHookContext<Context, 'query'>
168
+ finishDelete: HookContext<Context> & { readonly method: 'delete'; readonly id: DirectResourceId }
169
+ finishPostRelationship: RelationshipHookContext<Context, 'postRelationship'>
170
+ finishPatchRelationship: RelationshipHookContext<Context, 'patchRelationship'>
171
+ finishDeleteRelationship: RelationshipHookContext<Context, 'deleteRelationship'>
172
+ afterCommit: HookContext<Context>
173
+ afterRollback: HookContext<Context>
174
+ }
175
+
176
+ export type KnownHookName = keyof HookContextMap
177
+ export type HookArguments<Event extends KnownHookName, Context extends object = Record<string, unknown>> =
178
+ Omit<RuntimeArguments, 'context'> & { context: HookContextMap<Context>[Event] }
179
+ export type HookHandler<Event extends KnownHookName, Context extends object = Record<string, unknown>> =
180
+ (args: HookArguments<Event, Context>) => unknown