json-rest-api 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +13 -0
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +9 -5
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,192 +1,191 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Store reference in vars for access in hooks
|
|
44
|
-
vars.cors = corsConfig;
|
|
45
|
-
|
|
46
|
-
// Helper to check if origin is allowed
|
|
47
|
-
function isOriginAllowed(origin, allowedOrigin) {
|
|
48
|
-
if (!origin) return !corsConfig.credentials; // No origin = same-origin request
|
|
49
|
-
|
|
50
|
-
// String match
|
|
51
|
-
if (typeof allowedOrigin === 'string') {
|
|
52
|
-
return allowedOrigin === '*' || allowedOrigin === origin;
|
|
53
|
-
}
|
|
1
|
+
export const CorsPlugin = {
|
|
2
|
+
name: 'rest-api-cors',
|
|
3
|
+
dependencies: ['rest-api'],
|
|
4
|
+
|
|
5
|
+
async install ({ api, addHook, vars, helpers, log, pluginOptions, runHooks }) {
|
|
6
|
+
// Get CORS configuration
|
|
7
|
+
const corsOptions = pluginOptions || {}
|
|
8
|
+
|
|
9
|
+
// Store configuration - using a plain object for runtime updates
|
|
10
|
+
const corsConfig = {
|
|
11
|
+
// Origin configuration
|
|
12
|
+
origin: corsOptions.origin || '*', // Can be string, regex, array, or function
|
|
13
|
+
credentials: corsOptions.credentials !== undefined ? corsOptions.credentials : true,
|
|
14
|
+
|
|
15
|
+
// Allowed methods
|
|
16
|
+
methods: corsOptions.methods || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
17
|
+
|
|
18
|
+
// Allowed headers
|
|
19
|
+
allowedHeaders: corsOptions.allowedHeaders || [
|
|
20
|
+
'Content-Type',
|
|
21
|
+
'Authorization',
|
|
22
|
+
'X-Requested-With',
|
|
23
|
+
'X-HTTP-Method-Override',
|
|
24
|
+
'Accept',
|
|
25
|
+
'Origin'
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
// Exposed headers (for client to read)
|
|
29
|
+
exposedHeaders: corsOptions.exposedHeaders || [
|
|
30
|
+
'X-Total-Count',
|
|
31
|
+
'X-Page-Count',
|
|
32
|
+
'Link',
|
|
33
|
+
'Location'
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
// Preflight cache
|
|
37
|
+
maxAge: corsOptions.maxAge || 86400, // 24 hours
|
|
38
|
+
|
|
39
|
+
// Options success status
|
|
40
|
+
optionsSuccessStatus: corsOptions.optionsSuccessStatus || 204
|
|
41
|
+
}
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return allowedOrigin.test(origin);
|
|
58
|
-
}
|
|
43
|
+
// Store reference in vars for access in hooks
|
|
44
|
+
vars.cors = corsConfig
|
|
59
45
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
46
|
+
// Helper to check if origin is allowed
|
|
47
|
+
function isOriginAllowed (origin, allowedOrigin) {
|
|
48
|
+
if (!origin) return !corsConfig.credentials // No origin = same-origin request
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
// String match
|
|
51
|
+
if (typeof allowedOrigin === 'string') {
|
|
52
|
+
return allowedOrigin === '*' || allowedOrigin === origin
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Regex match
|
|
56
|
+
if (allowedOrigin instanceof RegExp) {
|
|
57
|
+
return allowedOrigin.test(origin)
|
|
58
|
+
}
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
// Array of allowed origins
|
|
61
|
+
if (Array.isArray(allowedOrigin)) {
|
|
62
|
+
return allowedOrigin.some(allowed => isOriginAllowed(origin, allowed))
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
//
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
return;
|
|
65
|
+
// Function check
|
|
66
|
+
if (typeof allowedOrigin === 'function') {
|
|
67
|
+
return allowedOrigin(origin)
|
|
77
68
|
}
|
|
78
69
|
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
responseHeaders['Access-Control-Allow-Origin'] = origin;
|
|
99
|
-
responseHeaders['Vary'] = 'Origin';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Set credentials if enabled
|
|
103
|
-
if (corsConfig.credentials) {
|
|
104
|
-
responseHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
statusCode: corsConfig.optionsSuccessStatus,
|
|
109
|
-
headers: responseHeaders,
|
|
110
|
-
body: null
|
|
111
|
-
};
|
|
112
|
-
} else {
|
|
113
|
-
// Origin not allowed
|
|
114
|
-
log.warn('CORS origin not allowed', { origin });
|
|
115
|
-
return {
|
|
116
|
-
statusCode: 403,
|
|
117
|
-
body: { error: 'CORS origin not allowed' }
|
|
118
|
-
};
|
|
73
|
+
// Check if transport plugin is available
|
|
74
|
+
if (!vars.transport) {
|
|
75
|
+
log.error('CORS plugin requires a transport plugin (express, koa, etc.) to be installed')
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Register OPTIONS handler for all routes
|
|
80
|
+
const optionsHandler = async ({ context, headers }) => {
|
|
81
|
+
const origin = headers?.origin
|
|
82
|
+
|
|
83
|
+
log.debug('CORS OPTIONS request', { origin })
|
|
84
|
+
|
|
85
|
+
// Check if origin is allowed
|
|
86
|
+
if (isOriginAllowed(origin, corsConfig.origin)) {
|
|
87
|
+
const responseHeaders = {
|
|
88
|
+
'Access-Control-Allow-Methods': corsConfig.methods.join(', '),
|
|
89
|
+
'Access-Control-Allow-Headers': corsConfig.allowedHeaders.join(', '),
|
|
90
|
+
'Access-Control-Max-Age': String(corsConfig.maxAge)
|
|
119
91
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Hook to handle CORS headers for all responses
|
|
130
|
-
addHook('transport:response', 'cors-headers', { order: -1000 }, async ({ context }) => {
|
|
131
|
-
// Transport data is now nested in context
|
|
132
|
-
const { request, response } = context.transport || {};
|
|
133
|
-
|
|
134
|
-
if (!request) {
|
|
135
|
-
log.error('CORS: request is undefined in transport:response hook');
|
|
136
|
-
return;
|
|
92
|
+
|
|
93
|
+
// Set origin header
|
|
94
|
+
if (corsConfig.origin === '*' && !corsConfig.credentials) {
|
|
95
|
+
responseHeaders['Access-Control-Allow-Origin'] = '*'
|
|
96
|
+
} else if (origin) {
|
|
97
|
+
responseHeaders['Access-Control-Allow-Origin'] = origin
|
|
98
|
+
responseHeaders['Vary'] = 'Origin'
|
|
137
99
|
}
|
|
138
|
-
|
|
139
|
-
const origin = request.headers?.origin;
|
|
140
|
-
const method = request.method?.toUpperCase();
|
|
141
100
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
});
|
|
101
|
+
// Set credentials if enabled
|
|
102
|
+
if (corsConfig.credentials) {
|
|
103
|
+
responseHeaders['Access-Control-Allow-Credentials'] = 'true'
|
|
104
|
+
}
|
|
147
105
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
106
|
+
return {
|
|
107
|
+
statusCode: corsConfig.optionsSuccessStatus,
|
|
108
|
+
headers: responseHeaders,
|
|
109
|
+
body: null
|
|
151
110
|
}
|
|
111
|
+
} else {
|
|
112
|
+
// Origin not allowed
|
|
113
|
+
log.warn('CORS origin not allowed', { origin })
|
|
114
|
+
return {
|
|
115
|
+
statusCode: 403,
|
|
116
|
+
body: { error: 'CORS origin not allowed' }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Register OPTIONS route for all paths
|
|
122
|
+
await api.addRoute({
|
|
123
|
+
method: 'OPTIONS',
|
|
124
|
+
path: vars.transport.matchAll,
|
|
125
|
+
handler: optionsHandler
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Hook to handle CORS headers for all responses
|
|
129
|
+
addHook('transport:response', 'cors-headers', { order: -1000 }, async ({ context }) => {
|
|
130
|
+
// Transport data is now nested in context
|
|
131
|
+
const { request, response } = context.transport || {}
|
|
132
|
+
|
|
133
|
+
if (!request) {
|
|
134
|
+
log.error('CORS: request is undefined in transport:response hook')
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const origin = request.headers?.origin
|
|
139
|
+
const method = request.method?.toUpperCase()
|
|
140
|
+
|
|
141
|
+
log.debug('CORS processing response', {
|
|
142
|
+
origin,
|
|
143
|
+
method,
|
|
144
|
+
path: request.path
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Skip if this was an OPTIONS request (already handled by route)
|
|
148
|
+
if (method === 'OPTIONS') {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
} else if (origin) {
|
|
159
|
-
response.headers['Access-Control-Allow-Origin'] = origin;
|
|
160
|
-
response.headers['Vary'] = 'Origin';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Set credentials if enabled
|
|
164
|
-
if (corsConfig.credentials) {
|
|
165
|
-
response.headers['Access-Control-Allow-Credentials'] = 'true';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Set exposed headers
|
|
169
|
-
if (corsConfig.exposedHeaders.length > 0) {
|
|
170
|
-
response.headers['Access-Control-Expose-Headers'] = corsConfig.exposedHeaders.join(', ');
|
|
171
|
-
}
|
|
152
|
+
// Check if origin is allowed
|
|
153
|
+
if (isOriginAllowed(origin, corsConfig.origin)) {
|
|
154
|
+
// Set origin header
|
|
155
|
+
if (corsConfig.origin === '*' && !corsConfig.credentials) {
|
|
156
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
|
172
157
|
} else if (origin) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
origin,
|
|
176
|
-
allowedOrigins: corsConfig.origin
|
|
177
|
-
});
|
|
158
|
+
response.headers['Access-Control-Allow-Origin'] = origin
|
|
159
|
+
response.headers['Vary'] = 'Origin'
|
|
178
160
|
}
|
|
179
|
-
});
|
|
180
161
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
162
|
+
// Set credentials if enabled
|
|
163
|
+
if (corsConfig.credentials) {
|
|
164
|
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Set exposed headers
|
|
168
|
+
if (corsConfig.exposedHeaders.length > 0) {
|
|
169
|
+
response.headers['Access-Control-Expose-Headers'] = corsConfig.exposedHeaders.join(', ')
|
|
170
|
+
}
|
|
171
|
+
} else if (origin) {
|
|
172
|
+
// Origin not allowed - don't set any CORS headers
|
|
173
|
+
log.warn('CORS origin not allowed for response', {
|
|
174
|
+
origin,
|
|
175
|
+
allowedOrigins: corsConfig.origin
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
log.info('CORS plugin installed', {
|
|
181
|
+
origin: corsConfig.origin,
|
|
182
|
+
credentials: corsConfig.credentials,
|
|
183
|
+
methods: corsConfig.methods
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
/*
|
|
190
189
|
Usage examples:
|
|
191
190
|
|
|
192
191
|
// Basic usage - allow all origins
|
|
@@ -235,4 +234,4 @@
|
|
|
235
234
|
}
|
|
236
235
|
});
|
|
237
236
|
|
|
238
|
-
*/
|
|
237
|
+
*/
|