adapt-authoring-api 2.2.0 → 3.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.
- package/docs/writing-an-api.md +2 -0
- package/lib/AbstractApiModule.js +133 -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,43 @@ 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
|
+
if (config.permissionsScope !== undefined) this.permissionsScope = config.permissionsScope
|
|
248
|
+
/* eslint-disable no-template-curly-in-string */
|
|
249
|
+
const replacements = {
|
|
250
|
+
'${scope}': this.permissionsScope || this.root,
|
|
251
|
+
'${schemaName}': this.schemaName,
|
|
252
|
+
'${collectionName}': this.collectionName
|
|
253
|
+
}
|
|
254
|
+
/* eslint-enable no-template-curly-in-string */
|
|
255
|
+
this.routes = config.routes.map(r => this.replacePlaceholders(r, replacements))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Recursively replaces placeholder strings in an object tree.
|
|
260
|
+
* Non-string values (functions, numbers, booleans, null) pass through unchanged.
|
|
261
|
+
* @param {*} obj The value to process
|
|
262
|
+
* @param {Object<string,string>} replacements Map of placeholder to replacement value
|
|
263
|
+
* @returns {*} The value with all placeholders resolved
|
|
264
|
+
*/
|
|
265
|
+
replacePlaceholders (obj, replacements) {
|
|
266
|
+
if (typeof obj === 'string') {
|
|
267
|
+
return Object.entries(replacements).reduce((s, [k, v]) => v != null ? s.replaceAll(k, v) : s, obj)
|
|
268
|
+
}
|
|
269
|
+
if (Array.isArray(obj)) return obj.map(item => this.replacePlaceholders(item, replacements))
|
|
270
|
+
if (obj && typeof obj === 'object' && obj.constructor === Object) {
|
|
271
|
+
return Object.fromEntries(
|
|
272
|
+
Object.entries(obj).map(([k, v]) => [k, this.replacePlaceholders(v, replacements)])
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
return obj
|
|
264
276
|
}
|
|
265
277
|
|
|
266
278
|
/**
|
|
@@ -390,135 +402,129 @@ class AbstractApiModule extends AbstractModule {
|
|
|
390
402
|
* Middleware to handle a generic API request. Supports POST, GET, PUT and DELETE of items in the database.
|
|
391
403
|
* @return {Function} Express middleware function
|
|
392
404
|
*/
|
|
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)
|
|
405
|
+
async requestHandler (req, res, next) {
|
|
406
|
+
const method = req.method.toLowerCase()
|
|
407
|
+
const func = this[httpMethodToDBFunction(method)]
|
|
408
|
+
if (!func) {
|
|
409
|
+
return next(this.app.errors.HTTP_METHOD_NOT_SUPPORTED.setData({ method }))
|
|
410
|
+
}
|
|
411
|
+
let data
|
|
412
|
+
try {
|
|
413
|
+
await this.requestHook.invoke(req)
|
|
414
|
+
const preCheck = method !== 'get' && method !== 'post'
|
|
415
|
+
const postCheck = method === 'get'
|
|
416
|
+
if (preCheck) {
|
|
417
|
+
await this.checkAccess(req, req.apiData.query)
|
|
415
418
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
data = data[0]
|
|
419
|
+
data = await func.apply(this, argsFromReq(req))
|
|
420
|
+
if (postCheck) {
|
|
421
|
+
data = await this.checkAccess(req, data)
|
|
421
422
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
423
|
+
data = await this.sanitise(req.apiData.schemaName, data, { isInternal: true, strict: false })
|
|
424
|
+
} catch (e) {
|
|
425
|
+
return next(e)
|
|
426
|
+
}
|
|
427
|
+
if (Array.isArray(data) && req.params._id) { // special case for when _id param is present
|
|
428
|
+
if (!data.length) {
|
|
429
|
+
return next(this.app.errors.NOT_FOUND.setData({ id: req.params.id, type: req.apiData.schemaName }))
|
|
425
430
|
}
|
|
426
|
-
|
|
431
|
+
data = data[0]
|
|
427
432
|
}
|
|
428
|
-
|
|
433
|
+
if (method !== 'get') {
|
|
434
|
+
const resource = Array.isArray(data) ? req.apiData.query : data._id.toString()
|
|
435
|
+
this.log('debug', `API_${func.name.toUpperCase()}`, resource, 'by', req.auth.user._id.toString())
|
|
436
|
+
}
|
|
437
|
+
res.status(this.mapStatusCode(method)).json(data)
|
|
429
438
|
}
|
|
430
439
|
|
|
431
440
|
/**
|
|
432
441
|
* 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
442
|
* @return {function}
|
|
434
443
|
*/
|
|
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
|
|
444
|
+
async queryHandler (req, res, next) {
|
|
445
|
+
try {
|
|
446
|
+
const opts = {
|
|
447
|
+
schemaName: req.apiData.schemaName,
|
|
448
|
+
collectionName: req.apiData.collectionName
|
|
449
|
+
}
|
|
450
|
+
const mongoOpts = {}
|
|
451
|
+
// find and remove mongo options from the query
|
|
452
|
+
Object.entries(req.apiData.query).forEach(([key, val]) => {
|
|
453
|
+
if (['collation', 'limit', 'page', 'skip', 'sort'].includes(key)) {
|
|
461
454
|
try {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
455
|
+
mongoOpts[key] = JSON.parse(req.apiData.query[key])
|
|
456
|
+
} catch (e) {
|
|
457
|
+
this.log('warn', `failed to parse query ${key} param '${mongoOpts[key]}', ${e}`)
|
|
458
|
+
}
|
|
459
|
+
delete req.apiData.query[key]
|
|
460
|
+
} else {
|
|
461
|
+
// otherwise assume we have a query field or option and store for later processing
|
|
462
|
+
opts[key] = val
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
// handle search parameter
|
|
466
|
+
const search = req.apiData.query.search
|
|
467
|
+
if (search) {
|
|
468
|
+
delete req.apiData.query.search
|
|
469
|
+
try {
|
|
470
|
+
const schema = await this.getSchema(req.apiData.schemaName)
|
|
471
|
+
if (schema && schema.built && schema.built.properties) {
|
|
472
|
+
const searchableFields = Object.keys(schema.built.properties).filter(
|
|
473
|
+
field => schema.built.properties[field].isSearchable === true
|
|
474
|
+
)
|
|
475
|
+
if (searchableFields.length) {
|
|
476
|
+
// escape special regex characters to prevent ReDoS attacks
|
|
477
|
+
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
478
|
+
const regex = { $regex: escapedSearch, $options: 'i' }
|
|
479
|
+
const searchConditions = searchableFields.map(f => ({ [f]: regex }))
|
|
480
|
+
// merge with existing $or if present
|
|
481
|
+
if (req.apiData.query.$or) {
|
|
482
|
+
req.apiData.query.$and = [
|
|
483
|
+
{ $or: req.apiData.query.$or },
|
|
484
|
+
{ $or: searchConditions }
|
|
485
|
+
]
|
|
486
|
+
delete req.apiData.query.$or
|
|
487
|
+
} else {
|
|
488
|
+
req.apiData.query.$or = searchConditions
|
|
482
489
|
}
|
|
483
490
|
}
|
|
484
|
-
} catch (e) {
|
|
485
|
-
this.log('warn', `failed to process search parameter, ${e.message}`)
|
|
486
491
|
}
|
|
492
|
+
} catch (e) {
|
|
493
|
+
this.log('warn', `failed to process search parameter, ${e.message}`)
|
|
487
494
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
495
|
+
}
|
|
496
|
+
req.apiData.query = await this.parseQuery(req.apiData.schemaName, req.body, mongoOpts)
|
|
497
|
+
// remove any valid query keys from the options
|
|
498
|
+
Object.keys(req.apiData.query).forEach(key => delete opts[key])
|
|
491
499
|
|
|
492
|
-
|
|
500
|
+
await this.requestHook.invoke(req)
|
|
493
501
|
|
|
494
|
-
|
|
502
|
+
await this.setUpPagination(req, res, mongoOpts)
|
|
495
503
|
|
|
496
|
-
|
|
504
|
+
let results = await this.find(req.apiData.query, opts, mongoOpts)
|
|
497
505
|
|
|
498
|
-
|
|
506
|
+
results = await this.checkAccess(req, results)
|
|
499
507
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
if (results.length > pageSize) results = results.slice(0, pageSize)
|
|
508
|
+
// If checkAccess filtered some results, fetch more to fill the page
|
|
509
|
+
const pageSize = mongoOpts.limit
|
|
510
|
+
if (pageSize && results.length < pageSize) {
|
|
511
|
+
let fetchSkip = mongoOpts.skip + pageSize
|
|
512
|
+
while (results.length < pageSize) {
|
|
513
|
+
const extra = await this.find(req.apiData.query, opts, { ...mongoOpts, skip: fetchSkip })
|
|
514
|
+
if (!extra.length) break
|
|
515
|
+
const filtered = await this.checkAccess(req, extra)
|
|
516
|
+
results = results.concat(filtered)
|
|
517
|
+
fetchSkip += extra.length
|
|
512
518
|
}
|
|
519
|
+
if (results.length > pageSize) results = results.slice(0, pageSize)
|
|
520
|
+
}
|
|
513
521
|
|
|
514
|
-
|
|
522
|
+
results = await this.sanitise(req.apiData.schemaName, results, { isInternal: true, strict: false })
|
|
515
523
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
524
|
+
res.status(this.mapStatusCode('get')).json(results)
|
|
525
|
+
} catch (e) {
|
|
526
|
+
return next(e)
|
|
520
527
|
}
|
|
521
|
-
return queryHandler
|
|
522
528
|
}
|
|
523
529
|
|
|
524
530
|
/**
|
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
|