adapt-authoring-api 2.2.0 → 3.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/docs/writing-an-api.md +2 -0
- package/lib/AbstractApiModule.js +132 -127
- package/lib/default-routes.json +129 -7
- package/package.json +1 -1
- package/tests/AbstractApiModule.spec.js +2 -2
package/docs/writing-an-api.md
CHANGED
|
@@ -53,6 +53,8 @@ class NotesModule extends AbstractApiModule {
|
|
|
53
53
|
export default NotesModule
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
> **Tip:** For new modules, consider defining routes in a `routes.json` file instead of calling `useDefaultRouteConfig()`. See [Custom routes](#custom-routes) for details.
|
|
57
|
+
|
|
56
58
|
With a schema at `schema/note.schema.json`:
|
|
57
59
|
|
|
58
60
|
```json
|
package/lib/AbstractApiModule.js
CHANGED
|
@@ -12,11 +12,11 @@ class AbstractApiModule extends AbstractModule {
|
|
|
12
12
|
get DEFAULT_ROUTES () {
|
|
13
13
|
const readPerms = [`read:${this.permissionsScope || this.root}`]
|
|
14
14
|
const writePerms = [`write:${this.permissionsScope || this.root}`]
|
|
15
|
-
const handler = this.requestHandler()
|
|
15
|
+
const handler = this.requestHandler.bind(this)
|
|
16
16
|
return [
|
|
17
17
|
{
|
|
18
18
|
route: '/',
|
|
19
|
-
handlers: { post: handler, get: this.queryHandler() },
|
|
19
|
+
handlers: { post: handler, get: this.queryHandler.bind(this) },
|
|
20
20
|
permissions: { post: writePerms, get: readPerms }
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -33,7 +33,7 @@ class AbstractApiModule extends AbstractModule {
|
|
|
33
33
|
route: '/query',
|
|
34
34
|
validate: false,
|
|
35
35
|
modifying: false,
|
|
36
|
-
handlers: { post: this.queryHandler() },
|
|
36
|
+
handlers: { post: this.queryHandler.bind(this) },
|
|
37
37
|
permissions: { post: readPerms }
|
|
38
38
|
}
|
|
39
39
|
]
|
|
@@ -153,11 +153,6 @@ class AbstractApiModule extends AbstractModule {
|
|
|
153
153
|
|
|
154
154
|
const config = await loadRouteConfig(this.rootDir, this, {
|
|
155
155
|
schema: 'apiroutes',
|
|
156
|
-
handlerAliases: {
|
|
157
|
-
default: this.requestHandler(),
|
|
158
|
-
query: this.queryHandler(),
|
|
159
|
-
serveSchema: this.serveSchema.bind(this)
|
|
160
|
-
},
|
|
161
156
|
defaults: new URL('./default-routes.json', import.meta.url).pathname
|
|
162
157
|
})
|
|
163
158
|
if (config) this.applyRouteConfig(config)
|
|
@@ -241,26 +236,42 @@ class AbstractApiModule extends AbstractModule {
|
|
|
241
236
|
|
|
242
237
|
/**
|
|
243
238
|
* Applies route configuration loaded from routes.json.
|
|
244
|
-
*
|
|
239
|
+
* Resolves `${scope}`, `${schemaName}`, and `${collectionName}` placeholders
|
|
240
|
+
* throughout the route config (permissions, meta, etc.).
|
|
245
241
|
* @param {Object} config The route config object returned by loadRouteConfig
|
|
246
242
|
*/
|
|
247
243
|
applyRouteConfig (config) {
|
|
248
244
|
/** @ignore */ this.root = config.root
|
|
249
245
|
if (config.schemaName !== undefined) this.schemaName = config.schemaName
|
|
250
246
|
if (config.collectionName !== undefined) this.collectionName = config.collectionName
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
247
|
+
/* eslint-disable no-template-curly-in-string */
|
|
248
|
+
const replacements = {
|
|
249
|
+
'${scope}': this.permissionsScope || this.root,
|
|
250
|
+
'${schemaName}': this.schemaName,
|
|
251
|
+
'${collectionName}': this.collectionName
|
|
252
|
+
}
|
|
253
|
+
/* eslint-enable no-template-curly-in-string */
|
|
254
|
+
this.routes = config.routes.map(r => this.replacePlaceholders(r, replacements))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Recursively replaces placeholder strings in an object tree.
|
|
259
|
+
* Non-string values (functions, numbers, booleans, null) pass through unchanged.
|
|
260
|
+
* @param {*} obj The value to process
|
|
261
|
+
* @param {Object<string,string>} replacements Map of placeholder to replacement value
|
|
262
|
+
* @returns {*} The value with all placeholders resolved
|
|
263
|
+
*/
|
|
264
|
+
replacePlaceholders (obj, replacements) {
|
|
265
|
+
if (typeof obj === 'string') {
|
|
266
|
+
return Object.entries(replacements).reduce((s, [k, v]) => v != null ? s.replaceAll(k, v) : s, obj)
|
|
267
|
+
}
|
|
268
|
+
if (Array.isArray(obj)) return obj.map(item => this.replacePlaceholders(item, replacements))
|
|
269
|
+
if (obj && typeof obj === 'object' && obj.constructor === Object) {
|
|
270
|
+
return Object.fromEntries(
|
|
271
|
+
Object.entries(obj).map(([k, v]) => [k, this.replacePlaceholders(v, replacements)])
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
return obj
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
/**
|
|
@@ -390,135 +401,129 @@ class AbstractApiModule extends AbstractModule {
|
|
|
390
401
|
* Middleware to handle a generic API request. Supports POST, GET, PUT and DELETE of items in the database.
|
|
391
402
|
* @return {Function} Express middleware function
|
|
392
403
|
*/
|
|
393
|
-
requestHandler () {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
await this.checkAccess(req, req.apiData.query)
|
|
407
|
-
}
|
|
408
|
-
data = await func.apply(this, argsFromReq(req))
|
|
409
|
-
if (postCheck) {
|
|
410
|
-
data = await this.checkAccess(req, data)
|
|
411
|
-
}
|
|
412
|
-
data = await this.sanitise(req.apiData.schemaName, data, { isInternal: true, strict: false })
|
|
413
|
-
} catch (e) {
|
|
414
|
-
return next(e)
|
|
404
|
+
async requestHandler (req, res, next) {
|
|
405
|
+
const method = req.method.toLowerCase()
|
|
406
|
+
const func = this[httpMethodToDBFunction(method)]
|
|
407
|
+
if (!func) {
|
|
408
|
+
return next(this.app.errors.HTTP_METHOD_NOT_SUPPORTED.setData({ method }))
|
|
409
|
+
}
|
|
410
|
+
let data
|
|
411
|
+
try {
|
|
412
|
+
await this.requestHook.invoke(req)
|
|
413
|
+
const preCheck = method !== 'get' && method !== 'post'
|
|
414
|
+
const postCheck = method === 'get'
|
|
415
|
+
if (preCheck) {
|
|
416
|
+
await this.checkAccess(req, req.apiData.query)
|
|
415
417
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
data = data[0]
|
|
418
|
+
data = await func.apply(this, argsFromReq(req))
|
|
419
|
+
if (postCheck) {
|
|
420
|
+
data = await this.checkAccess(req, data)
|
|
421
421
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
data = await this.sanitise(req.apiData.schemaName, data, { isInternal: true, strict: false })
|
|
423
|
+
} catch (e) {
|
|
424
|
+
return next(e)
|
|
425
|
+
}
|
|
426
|
+
if (Array.isArray(data) && req.params._id) { // special case for when _id param is present
|
|
427
|
+
if (!data.length) {
|
|
428
|
+
return next(this.app.errors.NOT_FOUND.setData({ id: req.params.id, type: req.apiData.schemaName }))
|
|
425
429
|
}
|
|
426
|
-
|
|
430
|
+
data = data[0]
|
|
427
431
|
}
|
|
428
|
-
|
|
432
|
+
if (method !== 'get') {
|
|
433
|
+
const resource = Array.isArray(data) ? req.apiData.query : data._id.toString()
|
|
434
|
+
this.log('debug', `API_${func.name.toUpperCase()}`, resource, 'by', req.auth.user._id.toString())
|
|
435
|
+
}
|
|
436
|
+
res.status(this.mapStatusCode(method)).json(data)
|
|
429
437
|
}
|
|
430
438
|
|
|
431
439
|
/**
|
|
432
440
|
* Express request handler for advanced API queries. Supports collation/limit/page/skip/sort and pagination. For incoming query data to be correctly parsed, it must be sent as body data using a POST request.
|
|
433
441
|
* @return {function}
|
|
434
442
|
*/
|
|
435
|
-
queryHandler () {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (['collation', 'limit', 'page', 'skip', 'sort'].includes(key)) {
|
|
446
|
-
try {
|
|
447
|
-
mongoOpts[key] = JSON.parse(req.apiData.query[key])
|
|
448
|
-
} catch (e) {
|
|
449
|
-
this.log('warn', `failed to parse query ${key} param '${mongoOpts[key]}', ${e}`)
|
|
450
|
-
}
|
|
451
|
-
delete req.apiData.query[key]
|
|
452
|
-
} else {
|
|
453
|
-
// otherwise assume we have a query field or option and store for later processing
|
|
454
|
-
opts[key] = val
|
|
455
|
-
}
|
|
456
|
-
})
|
|
457
|
-
// handle search parameter
|
|
458
|
-
const search = req.apiData.query.search
|
|
459
|
-
if (search) {
|
|
460
|
-
delete req.apiData.query.search
|
|
443
|
+
async queryHandler (req, res, next) {
|
|
444
|
+
try {
|
|
445
|
+
const opts = {
|
|
446
|
+
schemaName: req.apiData.schemaName,
|
|
447
|
+
collectionName: req.apiData.collectionName
|
|
448
|
+
}
|
|
449
|
+
const mongoOpts = {}
|
|
450
|
+
// find and remove mongo options from the query
|
|
451
|
+
Object.entries(req.apiData.query).forEach(([key, val]) => {
|
|
452
|
+
if (['collation', 'limit', 'page', 'skip', 'sort'].includes(key)) {
|
|
461
453
|
try {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
454
|
+
mongoOpts[key] = JSON.parse(req.apiData.query[key])
|
|
455
|
+
} catch (e) {
|
|
456
|
+
this.log('warn', `failed to parse query ${key} param '${mongoOpts[key]}', ${e}`)
|
|
457
|
+
}
|
|
458
|
+
delete req.apiData.query[key]
|
|
459
|
+
} else {
|
|
460
|
+
// otherwise assume we have a query field or option and store for later processing
|
|
461
|
+
opts[key] = val
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
// handle search parameter
|
|
465
|
+
const search = req.apiData.query.search
|
|
466
|
+
if (search) {
|
|
467
|
+
delete req.apiData.query.search
|
|
468
|
+
try {
|
|
469
|
+
const schema = await this.getSchema(req.apiData.schemaName)
|
|
470
|
+
if (schema && schema.built && schema.built.properties) {
|
|
471
|
+
const searchableFields = Object.keys(schema.built.properties).filter(
|
|
472
|
+
field => schema.built.properties[field].isSearchable === true
|
|
473
|
+
)
|
|
474
|
+
if (searchableFields.length) {
|
|
475
|
+
// escape special regex characters to prevent ReDoS attacks
|
|
476
|
+
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
477
|
+
const regex = { $regex: escapedSearch, $options: 'i' }
|
|
478
|
+
const searchConditions = searchableFields.map(f => ({ [f]: regex }))
|
|
479
|
+
// merge with existing $or if present
|
|
480
|
+
if (req.apiData.query.$or) {
|
|
481
|
+
req.apiData.query.$and = [
|
|
482
|
+
{ $or: req.apiData.query.$or },
|
|
483
|
+
{ $or: searchConditions }
|
|
484
|
+
]
|
|
485
|
+
delete req.apiData.query.$or
|
|
486
|
+
} else {
|
|
487
|
+
req.apiData.query.$or = searchConditions
|
|
482
488
|
}
|
|
483
489
|
}
|
|
484
|
-
} catch (e) {
|
|
485
|
-
this.log('warn', `failed to process search parameter, ${e.message}`)
|
|
486
490
|
}
|
|
491
|
+
} catch (e) {
|
|
492
|
+
this.log('warn', `failed to process search parameter, ${e.message}`)
|
|
487
493
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
494
|
+
}
|
|
495
|
+
req.apiData.query = await this.parseQuery(req.apiData.schemaName, req.body, mongoOpts)
|
|
496
|
+
// remove any valid query keys from the options
|
|
497
|
+
Object.keys(req.apiData.query).forEach(key => delete opts[key])
|
|
491
498
|
|
|
492
|
-
|
|
499
|
+
await this.requestHook.invoke(req)
|
|
493
500
|
|
|
494
|
-
|
|
501
|
+
await this.setUpPagination(req, res, mongoOpts)
|
|
495
502
|
|
|
496
|
-
|
|
503
|
+
let results = await this.find(req.apiData.query, opts, mongoOpts)
|
|
497
504
|
|
|
498
|
-
|
|
505
|
+
results = await this.checkAccess(req, results)
|
|
499
506
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
if (results.length > pageSize) results = results.slice(0, pageSize)
|
|
507
|
+
// If checkAccess filtered some results, fetch more to fill the page
|
|
508
|
+
const pageSize = mongoOpts.limit
|
|
509
|
+
if (pageSize && results.length < pageSize) {
|
|
510
|
+
let fetchSkip = mongoOpts.skip + pageSize
|
|
511
|
+
while (results.length < pageSize) {
|
|
512
|
+
const extra = await this.find(req.apiData.query, opts, { ...mongoOpts, skip: fetchSkip })
|
|
513
|
+
if (!extra.length) break
|
|
514
|
+
const filtered = await this.checkAccess(req, extra)
|
|
515
|
+
results = results.concat(filtered)
|
|
516
|
+
fetchSkip += extra.length
|
|
512
517
|
}
|
|
518
|
+
if (results.length > pageSize) results = results.slice(0, pageSize)
|
|
519
|
+
}
|
|
513
520
|
|
|
514
|
-
|
|
521
|
+
results = await this.sanitise(req.apiData.schemaName, results, { isInternal: true, strict: false })
|
|
515
522
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
523
|
+
res.status(this.mapStatusCode('get')).json(results)
|
|
524
|
+
} catch (e) {
|
|
525
|
+
return next(e)
|
|
520
526
|
}
|
|
521
|
-
return queryHandler
|
|
522
527
|
}
|
|
523
528
|
|
|
524
529
|
/**
|
package/lib/default-routes.json
CHANGED
|
@@ -2,25 +2,147 @@
|
|
|
2
2
|
"routes": [
|
|
3
3
|
{
|
|
4
4
|
"route": "/",
|
|
5
|
-
"handlers": { "post": "
|
|
6
|
-
"permissions": { "post": ["write:${scope}"], "get": ["read:${scope}"] }
|
|
5
|
+
"handlers": { "post": "requestHandler", "get": "queryHandler" },
|
|
6
|
+
"permissions": { "post": ["write:${scope}"], "get": ["read:${scope}"] },
|
|
7
|
+
"meta": {
|
|
8
|
+
"post": {
|
|
9
|
+
"summary": "Insert a new ${schemaName} document",
|
|
10
|
+
"requestBody": {
|
|
11
|
+
"content": {
|
|
12
|
+
"application/json": {
|
|
13
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"responses": {
|
|
18
|
+
"201": {
|
|
19
|
+
"description": "The created ${schemaName} document",
|
|
20
|
+
"content": {
|
|
21
|
+
"application/json": {
|
|
22
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"get": {
|
|
29
|
+
"summary": "Retrieve all ${collectionName} documents",
|
|
30
|
+
"parameters": [
|
|
31
|
+
{ "name": "limit", "in": "query", "description": "How many results to return" },
|
|
32
|
+
{ "name": "page", "in": "query", "description": "The page of results to return" }
|
|
33
|
+
],
|
|
34
|
+
"responses": {
|
|
35
|
+
"200": {
|
|
36
|
+
"description": "List of ${schemaName} documents",
|
|
37
|
+
"content": {
|
|
38
|
+
"application/json": {
|
|
39
|
+
"schema": { "type": "array", "items": { "$ref": "#/components/schemas/${schemaName}" } }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
7
46
|
},
|
|
8
47
|
{
|
|
9
48
|
"route": "/schema",
|
|
10
49
|
"handlers": { "get": "serveSchema" },
|
|
11
|
-
"permissions": { "get": ["read:schema"] }
|
|
50
|
+
"permissions": { "get": ["read:schema"] },
|
|
51
|
+
"meta": {
|
|
52
|
+
"get": { "summary": "Retrieve ${schemaName} schema" }
|
|
53
|
+
}
|
|
12
54
|
},
|
|
13
55
|
{
|
|
14
56
|
"route": "/:_id",
|
|
15
|
-
"handlers": { "put": "
|
|
16
|
-
"permissions": { "put": ["write:${scope}"], "get": ["read:${scope}"], "patch": ["write:${scope}"], "delete": ["write:${scope}"] }
|
|
57
|
+
"handlers": { "put": "requestHandler", "get": "requestHandler", "patch": "requestHandler", "delete": "requestHandler" },
|
|
58
|
+
"permissions": { "put": ["write:${scope}"], "get": ["read:${scope}"], "patch": ["write:${scope}"], "delete": ["write:${scope}"] },
|
|
59
|
+
"meta": {
|
|
60
|
+
"put": {
|
|
61
|
+
"summary": "Replace an existing ${schemaName} document",
|
|
62
|
+
"requestBody": {
|
|
63
|
+
"content": {
|
|
64
|
+
"application/json": {
|
|
65
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"responses": {
|
|
70
|
+
"200": {
|
|
71
|
+
"description": "The updated ${schemaName} document",
|
|
72
|
+
"content": {
|
|
73
|
+
"application/json": {
|
|
74
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"get": {
|
|
81
|
+
"summary": "Retrieve an existing ${schemaName} document",
|
|
82
|
+
"responses": {
|
|
83
|
+
"200": {
|
|
84
|
+
"description": "The ${schemaName} document",
|
|
85
|
+
"content": {
|
|
86
|
+
"application/json": {
|
|
87
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"patch": {
|
|
94
|
+
"summary": "Update an existing ${schemaName} document",
|
|
95
|
+
"requestBody": {
|
|
96
|
+
"content": {
|
|
97
|
+
"application/json": {
|
|
98
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"responses": {
|
|
103
|
+
"200": {
|
|
104
|
+
"description": "The updated ${schemaName} document",
|
|
105
|
+
"content": {
|
|
106
|
+
"application/json": {
|
|
107
|
+
"schema": { "$ref": "#/components/schemas/${schemaName}" }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"delete": {
|
|
114
|
+
"summary": "Delete an existing ${schemaName} document",
|
|
115
|
+
"responses": {
|
|
116
|
+
"204": { "description": "Document deleted successfully" }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
17
120
|
},
|
|
18
121
|
{
|
|
19
122
|
"route": "/query",
|
|
20
123
|
"validate": false,
|
|
21
124
|
"modifying": false,
|
|
22
|
-
"handlers": { "post": "
|
|
23
|
-
"permissions": { "post": ["read:${scope}"] }
|
|
125
|
+
"handlers": { "post": "queryHandler" },
|
|
126
|
+
"permissions": { "post": ["read:${scope}"] },
|
|
127
|
+
"meta": {
|
|
128
|
+
"post": {
|
|
129
|
+
"summary": "Query all ${collectionName}",
|
|
130
|
+
"parameters": [
|
|
131
|
+
{ "name": "limit", "in": "query", "description": "How many results to return" },
|
|
132
|
+
{ "name": "page", "in": "query", "description": "The page of results to return" }
|
|
133
|
+
],
|
|
134
|
+
"responses": {
|
|
135
|
+
"200": {
|
|
136
|
+
"description": "List of ${schemaName} documents",
|
|
137
|
+
"content": {
|
|
138
|
+
"application/json": {
|
|
139
|
+
"schema": { "type": "array", "items": { "$ref": "#/components/schemas/${schemaName}" } }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
24
146
|
}
|
|
25
147
|
]
|
|
26
148
|
}
|
package/package.json
CHANGED
|
@@ -9,8 +9,8 @@ function createInstance (overrides = {}) {
|
|
|
9
9
|
instance.schemaName = undefined
|
|
10
10
|
instance.collectionName = undefined
|
|
11
11
|
instance.routes = []
|
|
12
|
-
instance.requestHandler =
|
|
13
|
-
instance.queryHandler =
|
|
12
|
+
instance.requestHandler = function defaultRequestHandler () {}
|
|
13
|
+
instance.queryHandler = function queryHandler () {}
|
|
14
14
|
instance.serveSchema = function serveSchema () {}
|
|
15
15
|
Object.assign(instance, overrides)
|
|
16
16
|
return instance
|