json-rest-api 1.0.29 → 2.1.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 (294) 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 +733 -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 +103 -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 +3838 -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 +37 -0
  44. package/index.js +3 -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 +70 -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 +586 -418
  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 +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. 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,30 @@
1
+ import type { Knex } from 'knex'
2
+
3
+ export type AnyapiSlotPool = 'string' | 'number' | 'boolean' | 'date' | 'json' | 'belongsTo'
4
+ export type AnyapiSlotCounts = Partial<Record<AnyapiSlotPool, number>>
5
+
6
+ export interface AnyapiSchemaChange {
7
+ tenant: string
8
+ resource: string
9
+ version: number
10
+ }
11
+
12
+ export interface AnyapiRegistryOptions {
13
+ knex: Knex
14
+ log?: Partial<Record<'debug' | 'info' | 'warn' | 'error', (...args: unknown[]) => unknown>>
15
+ // Descriptors cached across every tenant API that shares the registry.
16
+ cacheSize?: number
17
+ }
18
+
19
+ /** Canonical metadata shared by tenant APIs that use one Knex instance. */
20
+ export class AnyapiRegistry {
21
+ constructor(options: AnyapiRegistryOptions)
22
+ readonly knex: Knex
23
+ readonly cacheSize: number
24
+ ensureSchema(options?: { slots?: AnyapiSlotCounts }): Promise<void>
25
+ getSchemaVersion(tenant: string, resource: string, options?: { transaction?: Knex.Transaction }): Promise<number | null>
26
+ invalidateDescriptor(tenant: string, resource: string): void
27
+ }
28
+
29
+ /** Create or upgrade the canonical tables; counts only ever add slot columns. */
30
+ export function ensureAnyApiSchema(knex: Knex, options?: { slots?: AnyapiSlotCounts }): Promise<void>
@@ -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