@pbvision/fastify-firestore-service 0.0.50
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 +204 -0
- package/README.md +187 -0
- package/docs/api.md +706 -0
- package/docs/build.sh +24 -0
- package/docs/jsdoc.config.json +22 -0
- package/package.json +93 -0
- package/src/api/api.js +852 -0
- package/src/api/db-api.js +109 -0
- package/src/api/exception.js +344 -0
- package/src/api/response.js +8 -0
- package/src/component-registrar.js +27 -0
- package/src/fetch-wrapper.js +30 -0
- package/src/index.js +13 -0
- package/src/make-app.js +223 -0
- package/src/make-logger.js +27 -0
- package/src/plugins/compress.js +28 -0
- package/src/plugins/content-parser.js +21 -0
- package/src/plugins/cookie.js +17 -0
- package/src/plugins/error-handler.js +180 -0
- package/src/plugins/health-check.js +19 -0
- package/src/plugins/latency-tracker.js +34 -0
- package/src/plugins/sentry-rate-limit.js +37 -0
- package/src/plugins/swagger.js +50 -0
- package/test/base-test.js +203 -0
- package/test/environment.js +3 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import db from '@pbvision/firestore-orm'
|
|
2
|
+
|
|
3
|
+
import API from './api.js'
|
|
4
|
+
import { __RequestDone } from './exception.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thrown to avoid committing a transaction when an error occurs.
|
|
8
|
+
* @access private
|
|
9
|
+
*/
|
|
10
|
+
export class RequestDoneAbortTransaction extends __RequestDone {
|
|
11
|
+
constructor (code = 200, data = '') {
|
|
12
|
+
super(undefined, data, code)
|
|
13
|
+
this.retryable = false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An API whose response is computed inside a transaction.
|
|
19
|
+
* @public
|
|
20
|
+
* @class
|
|
21
|
+
*/
|
|
22
|
+
class DatabaseAPI extends API {
|
|
23
|
+
/**
|
|
24
|
+
* Whether this API can only read from Firestore. If false, the API's
|
|
25
|
+
* computeResponse() method will run in a Firestore transaction.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
static IS_READ_ONLY = true
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options to pass the Firestore db.Context which wraps computeResponse().
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
static CONTEXT_OPTIONS = {}
|
|
35
|
+
|
|
36
|
+
async _computeResponse () {
|
|
37
|
+
await this.preTxStart()
|
|
38
|
+
|
|
39
|
+
let ret
|
|
40
|
+
try {
|
|
41
|
+
const opts = {
|
|
42
|
+
...this.constructor.CONTEXT_OPTIONS,
|
|
43
|
+
readOnly: this.constructor.IS_READ_ONLY
|
|
44
|
+
}
|
|
45
|
+
ret = await db.Context.run(opts, async tx => {
|
|
46
|
+
this.tx = tx
|
|
47
|
+
this.req.tx = tx
|
|
48
|
+
let respData = await super._computeResponse()
|
|
49
|
+
if (this.__reply.statusCode < 400) {
|
|
50
|
+
// pre-commit hook may change the response data (and status code!)
|
|
51
|
+
respData = await this._callAndHandleRequestDone(
|
|
52
|
+
this.preCommit, respData)
|
|
53
|
+
}
|
|
54
|
+
// if the response code indicates an error, then don't commit
|
|
55
|
+
if (this.__reply.statusCode >= 400) {
|
|
56
|
+
throw new RequestDoneAbortTransaction(
|
|
57
|
+
this.__reply.statusCode, respData)
|
|
58
|
+
}
|
|
59
|
+
return respData
|
|
60
|
+
})
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (e instanceof RequestDoneAbortTransaction) {
|
|
63
|
+
return e.respData
|
|
64
|
+
} else {
|
|
65
|
+
throw e
|
|
66
|
+
}
|
|
67
|
+
} finally {
|
|
68
|
+
delete this.tx
|
|
69
|
+
delete this.req.tx
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// _computeResponse() is called within _callAndHandleRequestDone; so we
|
|
73
|
+
// don't need to wrap this call to postCommit() in it (redundant)
|
|
74
|
+
return this.postCommit(ret)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Called just before the transaction starts. Useful to do async computation
|
|
79
|
+
* outside the transaction (reducing the window for contention).
|
|
80
|
+
* @protected
|
|
81
|
+
*/
|
|
82
|
+
async preTxStart () {}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Called just before the transaction ATTEMPTS to commit. May alter the
|
|
86
|
+
* response by returning a new response value, or throwing RequestDone.
|
|
87
|
+
* Throwing an error will be propagated and handled by Transaction.run(),
|
|
88
|
+
* and will prevent the transaction from committing (unless the error is
|
|
89
|
+
* retryable).
|
|
90
|
+
*
|
|
91
|
+
* @param {*} respData the response data
|
|
92
|
+
* @returns {*} the (possibly updated) response data
|
|
93
|
+
* @protected
|
|
94
|
+
*/
|
|
95
|
+
async preCommit (respData) { return respData }
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Called after the transaction commits (and ONLY if it commits
|
|
99
|
+
* successfully). May alter the response by returning a new response value,
|
|
100
|
+
* or throwing RequestDone.
|
|
101
|
+
*
|
|
102
|
+
* @param {*} respData the response data
|
|
103
|
+
* @returns {*} the (possibly updated) response data
|
|
104
|
+
* @protected
|
|
105
|
+
*/
|
|
106
|
+
async postCommit (respData) { return respData }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default DatabaseAPI
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
3
|
+
import S from '@pbvision/schema'
|
|
4
|
+
|
|
5
|
+
import RESPONSES from './response.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @namespace Exceptions
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Thrown to shortcut request handling.
|
|
13
|
+
*
|
|
14
|
+
* {@link API} will catch this exception and send the HTTP response code
|
|
15
|
+
* and (optional) data. This is useful to immediately stop request processing,
|
|
16
|
+
* especially from deeply nested locations in the call stack where it would
|
|
17
|
+
* be cumbersome to pass return information (especially errors) all the way
|
|
18
|
+
* back up the call stack.
|
|
19
|
+
*
|
|
20
|
+
* Typically, users should throw {@link RequestError} for error responses or
|
|
21
|
+
* @see RequestOkay for non-error responses (not @this).
|
|
22
|
+
*
|
|
23
|
+
* @arg {Number} httpCode The HTTP status code to respond with.
|
|
24
|
+
* @arg {String|Object} [respData=''] The object (to JSON.stringify()) or
|
|
25
|
+
* string to send in the HTTP response body.
|
|
26
|
+
* @package
|
|
27
|
+
* @memberof Exceptions
|
|
28
|
+
*/
|
|
29
|
+
class __RequestDone extends Error {
|
|
30
|
+
static STATUS = undefined
|
|
31
|
+
static SCHEMA = RESPONSES.NO_OUTPUT
|
|
32
|
+
|
|
33
|
+
static get schemaValidator () {
|
|
34
|
+
const cacheKey = '_CACHED_SCHEMA_VALIDATOR'
|
|
35
|
+
if (!Object.prototype.hasOwnProperty.call(this, cacheKey)) {
|
|
36
|
+
this[cacheKey] = this.schema.compile(`data input to ${this.name}`)
|
|
37
|
+
}
|
|
38
|
+
return this[cacheKey]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets a Schema object.
|
|
43
|
+
*/
|
|
44
|
+
static get schema () {
|
|
45
|
+
let schema = this.SCHEMA
|
|
46
|
+
if (!schema.isSchema) {
|
|
47
|
+
schema = S.obj(schema)
|
|
48
|
+
}
|
|
49
|
+
return schema
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create an exception.
|
|
54
|
+
* @param {String} message An error message
|
|
55
|
+
* @param {Object} data A JSON object containing additional data
|
|
56
|
+
* @param {Integer} code An optional code to override exception's default
|
|
57
|
+
* STATUS
|
|
58
|
+
*/
|
|
59
|
+
constructor (message, data = undefined, code = undefined) {
|
|
60
|
+
super(message)
|
|
61
|
+
this.httpCode = code ?? this.constructor.STATUS
|
|
62
|
+
assert(this.httpCode !== undefined, 'Status must be defined')
|
|
63
|
+
if (this.constructor.SCHEMA !== undefined) {
|
|
64
|
+
this.constructor.schemaValidator?.(data)
|
|
65
|
+
}
|
|
66
|
+
this.data = data
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Concrete class to indicate request is completed ok.
|
|
72
|
+
* @public
|
|
73
|
+
* @memberof Exceptions
|
|
74
|
+
*/
|
|
75
|
+
class RequestDone extends __RequestDone {
|
|
76
|
+
static STATUS = 200
|
|
77
|
+
static SCHEMA = S.obj() // optional
|
|
78
|
+
|
|
79
|
+
static get schemaValidator () {
|
|
80
|
+
const orig = super.schemaValidator
|
|
81
|
+
// istanbul ignore else
|
|
82
|
+
if (this.SCHEMA === RequestDone.SCHEMA) {
|
|
83
|
+
return data => {
|
|
84
|
+
if (data !== undefined) {
|
|
85
|
+
orig(data)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// istanbul ignore next
|
|
90
|
+
return orig
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Return data to return to caller.
|
|
95
|
+
*/
|
|
96
|
+
get respData () {
|
|
97
|
+
return this.data
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a success response. Status must be smaller than 400.
|
|
102
|
+
* @param {Object} data Data to return to caller
|
|
103
|
+
* @param {Integer} code A status to override default status.
|
|
104
|
+
*/
|
|
105
|
+
constructor (data = undefined, code = undefined) {
|
|
106
|
+
super(undefined, data, code)
|
|
107
|
+
assert(this.httpCode < 300, 'Status code must be less than 300')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Thrown to shortcut request handling and return an HTTP success code (200).
|
|
113
|
+
*
|
|
114
|
+
* @arg {String|Object} [respData=''] The object (to JSON.stringify()) or
|
|
115
|
+
* string to send in the HTTP response body.
|
|
116
|
+
* @public
|
|
117
|
+
* @memberof Exceptions
|
|
118
|
+
* @see RequestDone
|
|
119
|
+
*/
|
|
120
|
+
class RequestOkay extends RequestDone {
|
|
121
|
+
static STATUS = 200
|
|
122
|
+
// request okay response will be verified like a regular response
|
|
123
|
+
static SCHEMA = undefined
|
|
124
|
+
constructor (data = undefined) {
|
|
125
|
+
super(data, 200)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Thrown to shortcut request handling and return an HTTP error.
|
|
131
|
+
*
|
|
132
|
+
* @arg {String} message The human-readable error message to send back in the
|
|
133
|
+
* JSON response data (in the "error" key).
|
|
134
|
+
* @arg {Object} [data={}] Optional additional JSON data to send back
|
|
135
|
+
* in the error response body.
|
|
136
|
+
* @arg {Number} [code=400] The HTTP status code to respond with; defaults
|
|
137
|
+
* to STATUS
|
|
138
|
+
* @public
|
|
139
|
+
* @memberof Exceptions
|
|
140
|
+
* @see RequestDone
|
|
141
|
+
*/
|
|
142
|
+
class RequestError extends __RequestDone {
|
|
143
|
+
static SCHEMA = S.obj() // optional
|
|
144
|
+
|
|
145
|
+
static get schemaValidator () {
|
|
146
|
+
const orig = super.schemaValidator
|
|
147
|
+
// istanbul ignore else
|
|
148
|
+
if (this.SCHEMA === RequestError.SCHEMA) {
|
|
149
|
+
return data => {
|
|
150
|
+
if (data !== undefined) {
|
|
151
|
+
orig(data)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return orig
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
constructor (message, data = undefined, code = undefined) {
|
|
159
|
+
super(message, data, code)
|
|
160
|
+
assert(this.httpCode >= 300, 'Status code must be at least 300')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Schema to use on returned data
|
|
165
|
+
*/
|
|
166
|
+
static get respSchema () {
|
|
167
|
+
return S.obj({
|
|
168
|
+
code: S.str,
|
|
169
|
+
message: S.str.optional(),
|
|
170
|
+
data: S.obj().optional() // Data is validated in constructor, don't validate again.
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Data to return to the caller
|
|
176
|
+
*/
|
|
177
|
+
get respData () {
|
|
178
|
+
return {
|
|
179
|
+
code: this.constructor.name,
|
|
180
|
+
message: this.message,
|
|
181
|
+
data: this.data
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mark this error for rate-limited Sentry reporting. Useful when a known
|
|
187
|
+
* dependency outage causes the same error to fire repeatedly -- Cloud Tasks
|
|
188
|
+
* retries, etc. The HTTP response and logs are unaffected; only the Sentry
|
|
189
|
+
* report is throttled so the error budget doesn't get exhausted during the
|
|
190
|
+
* outage. Returns `this`, so it can be chained at the throw site:
|
|
191
|
+
*
|
|
192
|
+
* throw new RequestError('failed to send email', { err }, 550)
|
|
193
|
+
* .rateLimitSentry()
|
|
194
|
+
*
|
|
195
|
+
* Rate-limit keying follows the Sentry fingerprint we'd have used anyway
|
|
196
|
+
* (so Sentry grouping and our suppression agree on what "the same error" is).
|
|
197
|
+
*
|
|
198
|
+
* @param {Number} [windowMs=300000] rate-limit window in ms; at most one
|
|
199
|
+
* Sentry report is sent per window per key (default 5 minutes).
|
|
200
|
+
* @returns {RequestError} this
|
|
201
|
+
*/
|
|
202
|
+
rateLimitSentry (windowMs = 5 * 60 * 1000) {
|
|
203
|
+
this._sentryRateLimitMs = windowMs
|
|
204
|
+
return this
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Thrown when a redirect is required.
|
|
210
|
+
* @public
|
|
211
|
+
* @memberof Exceptions
|
|
212
|
+
*/
|
|
213
|
+
class RedirectException extends RequestError {
|
|
214
|
+
static STATUS = 302
|
|
215
|
+
static SCHEMA = S.str.max(0)
|
|
216
|
+
|
|
217
|
+
constructor (url, code) {
|
|
218
|
+
super('', '', code)
|
|
219
|
+
assert(this.httpCode < 400, 'Status code must be less than 400')
|
|
220
|
+
assert(typeof url === 'string' && url.length)
|
|
221
|
+
this.url = url
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Thrown when a error is induced by client.
|
|
227
|
+
* @public
|
|
228
|
+
* @memberof Exceptions
|
|
229
|
+
*/
|
|
230
|
+
class BadRequestException extends RequestError {
|
|
231
|
+
static STATUS = 400
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Wraps Request Validation failures into a RequestError object for processing.
|
|
236
|
+
* @public
|
|
237
|
+
* @memberof Exceptions
|
|
238
|
+
*/
|
|
239
|
+
class InvalidInputException extends BadRequestException {
|
|
240
|
+
static STATUS = 400
|
|
241
|
+
static __ERROR_PREFIX = {
|
|
242
|
+
headers: 'Header Validation Failure',
|
|
243
|
+
body: 'Body Validation Failure',
|
|
244
|
+
querystring: 'Query Validation Failure',
|
|
245
|
+
params: 'Path Validation Failure'
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
constructor (schemaError) {
|
|
249
|
+
const prefixMap = InvalidInputException.__ERROR_PREFIX
|
|
250
|
+
const prefix = prefixMap[schemaError.validationContext] ??
|
|
251
|
+
'Unknown Validation Failure'
|
|
252
|
+
super(`${prefix}: ${schemaError.message}`)
|
|
253
|
+
this.name = this.constructor.name
|
|
254
|
+
// istanbul ignore else
|
|
255
|
+
if (process.env.NODE_ENV !== 'prod' && schemaError.validation) {
|
|
256
|
+
for (const err of schemaError.validation) {
|
|
257
|
+
console.log(JSON.stringify(err))
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Thrown when a client is not authorized to access the requested resource. For
|
|
265
|
+
* example, user is trying to log in using invalid credentials.
|
|
266
|
+
* @public
|
|
267
|
+
* @memberof Exceptions
|
|
268
|
+
*/
|
|
269
|
+
class UnauthorizedException extends RequestError {
|
|
270
|
+
static STATUS = 401
|
|
271
|
+
|
|
272
|
+
constructor (message = 'access denied') {
|
|
273
|
+
super(message)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Thrown when an authenticated client does not have enough privilege to access
|
|
279
|
+
* the requested resource. For example, a user tries to change other user's
|
|
280
|
+
* data.
|
|
281
|
+
* @public
|
|
282
|
+
* @memberof Exceptions
|
|
283
|
+
*/
|
|
284
|
+
class ForbiddenException extends RequestError {
|
|
285
|
+
static STATUS = 403
|
|
286
|
+
|
|
287
|
+
constructor (message = 'access denied') {
|
|
288
|
+
super(message)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Thrown when the requested resource is not found, or should be hidden.
|
|
294
|
+
* @public
|
|
295
|
+
* @memberof Exceptions
|
|
296
|
+
*/
|
|
297
|
+
class NotFoundException extends RequestError {
|
|
298
|
+
static STATUS = 404
|
|
299
|
+
|
|
300
|
+
constructor () {
|
|
301
|
+
super('Not found')
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Thrown when an error is induced by a server bug.
|
|
307
|
+
* @public
|
|
308
|
+
* @memberof Exceptions
|
|
309
|
+
*/
|
|
310
|
+
class InternalFailureException extends RequestError {
|
|
311
|
+
static STATUS = 500
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Thrown when server is temporarily unable to serve the request, due to
|
|
316
|
+
* internal timeouts or service outage.
|
|
317
|
+
* @public
|
|
318
|
+
* @memberof Exceptions
|
|
319
|
+
*/
|
|
320
|
+
class ServiceUnavailableException extends RequestError {
|
|
321
|
+
static STATUS = 503
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export {
|
|
325
|
+
// Base exceptions
|
|
326
|
+
__RequestDone,
|
|
327
|
+
RequestError,
|
|
328
|
+
RequestDone,
|
|
329
|
+
|
|
330
|
+
// Success
|
|
331
|
+
RequestOkay,
|
|
332
|
+
|
|
333
|
+
// Redirect
|
|
334
|
+
RedirectException,
|
|
335
|
+
|
|
336
|
+
// Error
|
|
337
|
+
BadRequestException,
|
|
338
|
+
InvalidInputException,
|
|
339
|
+
ForbiddenException,
|
|
340
|
+
InternalFailureException,
|
|
341
|
+
NotFoundException,
|
|
342
|
+
ServiceUnavailableException,
|
|
343
|
+
UnauthorizedException
|
|
344
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class ComponentRegistrar {
|
|
2
|
+
apis = []
|
|
3
|
+
|
|
4
|
+
constructor (app, service) {
|
|
5
|
+
this.app = app
|
|
6
|
+
this.service = service
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async registerComponents (components) {
|
|
10
|
+
const promises = []
|
|
11
|
+
for (const component of Object.values(components)) {
|
|
12
|
+
if (component.register) {
|
|
13
|
+
promises.push(component.register(this))
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
await Promise.all(promises)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async registerAPI (api) {
|
|
20
|
+
this.apis.push(api)
|
|
21
|
+
await api.registerAPI(this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async registerModel (model) {
|
|
25
|
+
// nothing to do here
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import querystring from 'node:querystring'
|
|
2
|
+
|
|
3
|
+
import realFetch from 'node-fetch'
|
|
4
|
+
|
|
5
|
+
async function fetchWrapper (request, mockedFetch) {
|
|
6
|
+
const { compress = true, method = 'POST', url, qsParams, json } = request
|
|
7
|
+
let { body, headers } = request
|
|
8
|
+
headers = { ...headers } // copy the headers before we change them
|
|
9
|
+
|
|
10
|
+
if (json) {
|
|
11
|
+
headers['content-type'] = 'application/json'
|
|
12
|
+
body = JSON.stringify(request.json)
|
|
13
|
+
}
|
|
14
|
+
const fetch = (mockedFetch === false)
|
|
15
|
+
? realFetch
|
|
16
|
+
: (mockedFetch ?? fetchWrapper.__mock ?? realFetch)
|
|
17
|
+
|
|
18
|
+
// compute the full URL including search params
|
|
19
|
+
let fullURL = url
|
|
20
|
+
if (qsParams) {
|
|
21
|
+
const qsStr = querystring.stringify(qsParams)
|
|
22
|
+
if (qsStr) {
|
|
23
|
+
fullURL += `?${qsStr}`
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const options = { body, headers, method, compress }
|
|
27
|
+
return fetch(fullURL, options)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default fetchWrapper
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import API from './api/api.js'
|
|
2
|
+
import DatabaseAPI, { RequestDoneAbortTransaction } from './api/db-api.js'
|
|
3
|
+
import * as EXCEPTIONS from './api/exception.js'
|
|
4
|
+
import RESPONSES from './api/response.js'
|
|
5
|
+
import ComponentRegistrar from './component-registrar.js'
|
|
6
|
+
import makeService from './make-app.js'
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
API, DatabaseAPI, EXCEPTIONS, RESPONSES,
|
|
10
|
+
makeService,
|
|
11
|
+
ComponentRegistrar,
|
|
12
|
+
RequestDoneAbortTransaction
|
|
13
|
+
}
|