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.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- 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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|