@logux/server 0.13.0 → 0.14.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.
@@ -1,10 +1,5 @@
1
- import { once } from 'node:events'
2
1
  import os from 'node:os'
3
- import { Transform } from 'node:stream'
4
- import pico from 'picocolors'
5
- import pino from 'pino'
6
- import abstractTransport from 'pino-abstract-transport'
7
- import stripAnsi from 'strip-ansi'
2
+ import { stripVTControlCharacters, styleText } from 'node:util'
8
3
 
9
4
  import { mulberry32, onceXmur3 } from './utils.js'
10
5
 
@@ -30,10 +25,11 @@ const PARAMS_BLACKLIST = {
30
25
  }
31
26
 
32
27
  const LABELS = {
33
- 30: (c, str) => label(c, ' INFO ', 'green', 'bgGreen', 'black', str),
34
- 40: (c, str) => label(c, ' WARN ', 'yellow', 'bgYellow', 'black', str),
35
- 50: (c, str) => label(c, ' ERROR ', 'red', 'bgRed', 'white', str),
36
- 60: (c, str) => label(c, ' FATAL ', 'red', 'bgRed', 'white', str)
28
+ 20: str => label(' DEBUG ', 'white', 'bgWhite', 'black', str),
29
+ 30: str => label(' INFO ', 'green', 'bgGreen', 'black', str),
30
+ 40: str => label(' WARN ', 'yellow', 'bgYellow', 'black', str),
31
+ 50: str => label(' ERROR ', 'red', 'bgRed', 'white', str),
32
+ 60: str => label(' FATAL ', 'red', 'bgRed', 'white', str)
37
33
  }
38
34
 
39
35
  const COLORS = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan']
@@ -50,16 +46,16 @@ function formatNow() {
50
46
  }
51
47
 
52
48
  function rightPag(str, length) {
53
- let add = length - stripAnsi(str).length
49
+ let add = length - stripVTControlCharacters(str).length
54
50
  for (let i = 0; i < add; i++) str += ' '
55
51
  return str
56
52
  }
57
53
 
58
- function label(c, type, color, labelBg, labelText, message) {
59
- let pagged = rightPag(c[labelBg](c[labelText](type)), 8)
60
- let time = c.dim(`at ${formatNow()}`)
61
- let highlighted = message.replace(/`([^`]+)`/g, c.yellow('$1'))
62
- return `${pagged}${c.bold(c[color](highlighted))} ${time}`
54
+ function label(type, color, labelBg, labelText, message) {
55
+ let pagged = rightPag(styleText(labelBg, styleText(labelText, type)), 8)
56
+ let time = styleText('dim', `at ${formatNow()}`)
57
+ let highlighted = message.replace(/`([^`]+)`/g, styleText('yellow', '$1'))
58
+ return `${pagged}${styleText('bold', styleText(color, highlighted))} ${time}`
63
59
  }
64
60
 
65
61
  function formatName(key) {
@@ -88,7 +84,7 @@ function shuffledColors(str) {
88
84
  return result
89
85
  }
90
86
 
91
- function splitAndColorize(c, partLength, str) {
87
+ function splitAndColorize(partLength, str) {
92
88
  let strBuilder = []
93
89
  let colors = shuffledColors(str)
94
90
 
@@ -104,58 +100,58 @@ function splitAndColorize(c, partLength, str) {
104
100
  if (strToColorize.length === 1) {
105
101
  color = colors[Math.abs(n - 1) % colors.length]
106
102
  }
107
- strBuilder.push(c[color](strToColorize))
103
+ strBuilder.push(styleText(color, strToColorize))
108
104
  }
109
105
 
110
106
  return strBuilder.join('')
111
107
  }
112
108
 
113
- function formatNodeId(c, nodeId) {
109
+ function formatNodeId(nodeId) {
114
110
  let pos = nodeId.lastIndexOf(':')
115
111
  if (pos === -1) {
116
112
  return nodeId
117
113
  } else {
118
114
  let s = nodeId.split(':')
119
- let id = c.bold(s[0])
120
- let random = splitAndColorize(c, 3, s[1])
115
+ let id = styleText('bold', s[0])
116
+ let random = splitAndColorize(3, s[1])
121
117
  return `${id}:${random}`
122
118
  }
123
119
  }
124
120
 
125
- function formatValue(c, value) {
121
+ function formatValue(value) {
126
122
  if (typeof value === 'string') {
127
- return '"' + c.bold(value) + '"'
123
+ return '"' + styleText('bold', value) + '"'
128
124
  } else if (Array.isArray(value)) {
129
- return formatArray(c, value)
125
+ return formatArray(value)
130
126
  } else if (typeof value === 'object' && value) {
131
- return formatObject(c, value)
127
+ return formatObject(value)
132
128
  } else {
133
- return c.bold(value)
129
+ return styleText('bold', `${value}`)
134
130
  }
135
131
  }
136
132
 
137
- function formatObject(c, obj) {
138
- let items = Object.keys(obj).map(k => `${k}: ${formatValue(c, obj[k])}`)
133
+ function formatObject(obj) {
134
+ let items = Object.keys(obj).map(k => `${k}: ${formatValue(obj[k])}`)
139
135
  return '{ ' + items.join(', ') + ' }'
140
136
  }
141
137
 
142
- function formatArray(c, array) {
143
- let items = array.map(i => formatValue(c, i))
138
+ function formatArray(array) {
139
+ let items = array.map(i => formatValue(i))
144
140
  return '[' + items.join(', ') + ']'
145
141
  }
146
142
 
147
- function formatActionId(c, id) {
143
+ function formatActionId(id) {
148
144
  let p = id.split(' ')
149
145
  if (p.length === 1) {
150
146
  return p
151
147
  }
152
- return `${c.bold(splitAndColorize(c, 4, p[0]))} ${formatNodeId(
153
- c,
154
- p[1]
155
- )} ${c.bold(p[2])}`
148
+ return (
149
+ `${styleText('bold', splitAndColorize(4, p[0]))} ` +
150
+ `${formatNodeId(p[1])} ${styleText('bold', p[2])}`
151
+ )
156
152
  }
157
153
 
158
- function formatParams(c, params, parent) {
154
+ function formatParams(params, parent) {
159
155
  let maxName = params.reduce((max, param) => {
160
156
  let name = param[0]
161
157
  return name.length > max ? name.length : max
@@ -169,30 +165,30 @@ function formatParams(c, params, parent) {
169
165
  let start = PADDING + rightPag(`${name}: `, maxName + 2)
170
166
 
171
167
  if (name === 'Node ID' || (parent === 'Meta' && name === 'server')) {
172
- return start + formatNodeId(c, value)
168
+ return start + formatNodeId(value)
173
169
  } else if (
174
170
  parent === 'Meta' &&
175
171
  (name === 'clients' || name === 'excludeClients')
176
172
  ) {
177
- return `${start}[${value.map(v => `"${formatNodeId(c, v)}"`).join()}]`
173
+ return `${start}[${value.map(v => `"${formatNodeId(v)}"`).join()}]`
178
174
  } else if (name === 'Action ID' || (parent === 'Meta' && name === 'id')) {
179
- return start + formatActionId(c, value)
175
+ return start + formatActionId(value)
180
176
  } else if (Array.isArray(value)) {
181
- return start + formatArray(c, value)
177
+ return start + formatArray(value)
182
178
  } else if (typeof value === 'object' && value) {
183
179
  let nested = Object.keys(value).map(key => [key, value[key]])
184
180
  return (
185
181
  start +
186
182
  NEXT_LINE +
187
183
  INDENT +
188
- formatParams(c, nested, name)
184
+ formatParams(nested, name)
189
185
  .split(NEXT_LINE)
190
186
  .join(NEXT_LINE + INDENT)
191
187
  )
192
188
  } else if (typeof value === 'string' && parent) {
193
- return start + '"' + c.bold(value) + '"'
189
+ return start + '"' + styleText('bold', value) + '"'
194
190
  } else {
195
- return start + c.bold(value)
191
+ return start + styleText('bold', `${value}`)
196
192
  }
197
193
  })
198
194
  .join(NEXT_LINE)
@@ -212,7 +208,7 @@ function splitByLength(string, max) {
212
208
  return lines.map(i => i.trim())
213
209
  }
214
210
 
215
- function prettyStackTrace(c, stack, basepath) {
211
+ function prettyStackTrace(stack, basepath) {
216
212
  return stack
217
213
  .split('\n')
218
214
  .slice(1)
@@ -220,24 +216,25 @@ function prettyStackTrace(c, stack, basepath) {
220
216
  let match = line.match(/\s+at ([^(]+) \(([^)]+)\)/)
221
217
  let isSystem = !match || !match[2].startsWith(basepath)
222
218
  if (isSystem) {
223
- return c.gray(line.replace(/^\s*/, PADDING))
219
+ return styleText('gray', line.replace(/^\s*/, PADDING))
224
220
  } else {
225
221
  let func = match[1]
226
222
  let relative = match[2].slice(basepath.length)
227
223
  let converted = `${PADDING}at ${func} (./${relative})`
228
224
  let isDependency = match[2].includes('node_modules')
229
- return isDependency ? c.gray(converted) : c.red(converted)
225
+ return isDependency
226
+ ? styleText('gray', converted)
227
+ : styleText('red', converted)
230
228
  }
231
229
  })
232
230
  .join(NEXT_LINE)
233
231
  }
234
232
 
235
- function humanFormatter(options) {
236
- let c = pico.createColors(options.color)
233
+ export default function humanFormatter(options) {
237
234
  let basepath = options.basepath
238
235
 
239
236
  return function format(record) {
240
- let message = [LABELS[record.level](c, record.msg)]
237
+ let message = [LABELS[record.level](record.msg)]
241
238
  let params = Object.keys(record)
242
239
  .filter(key => !PARAMS_BLACKLIST[key])
243
240
  .map(key => [formatName(key), record[key]])
@@ -252,53 +249,26 @@ function humanFormatter(options) {
252
249
  }
253
250
 
254
251
  if (record.err && record.err.stack) {
255
- message.push(prettyStackTrace(c, record.err.stack, basepath))
252
+ message.push(prettyStackTrace(record.err.stack, basepath))
256
253
  }
257
254
 
258
- message.push(formatParams(c, params))
255
+ message.push(formatParams(params))
259
256
 
260
257
  if (record.note) {
261
258
  let note = record.note
262
259
  if (typeof note === 'string') {
263
- note = note.replace(/`([^`]+)`/g, c.bold('$1'))
260
+ note = note.replace(/`([^`]+)`/g, styleText('bold', '$1'))
264
261
  note = [].concat(
265
262
  ...note
266
263
  .split('\n')
267
264
  .map(row => splitByLength(row, 80 - PADDING.length))
268
265
  )
269
266
  }
270
- message.push(note.map(i => PADDING + c.gray(i)).join(NEXT_LINE))
267
+ message.push(
268
+ note.map(i => PADDING + styleText('gray', i)).join(NEXT_LINE)
269
+ )
271
270
  }
272
271
 
273
272
  return message.filter(i => i !== '').join(NEXT_LINE) + SEPARATOR
274
273
  }
275
274
  }
276
-
277
- export default async function (options) {
278
- let format = humanFormatter(options)
279
- let destination = pino.destination({
280
- dest: options.destination || 1,
281
- sync: options.sync || false
282
- })
283
- await once(destination, 'ready')
284
- let transform = new Transform({
285
- autoDestroy: true,
286
- objectMode: true,
287
- transform(chunk, encoding, callback) {
288
- callback(null, format(chunk))
289
- }
290
- })
291
-
292
- return abstractTransport(
293
- source => {
294
- source.pipe(transform)
295
- transform.pipe(destination)
296
- },
297
- {
298
- close(err, cb) {
299
- transform.end()
300
- transform.on('close', cb.bind(null, err))
301
- }
302
- }
303
- )
304
- }
package/index.js CHANGED
@@ -8,15 +8,7 @@ export { ALLOWED_META } from './allowed-meta/index.js'
8
8
  export { BaseServer, wasNot403 } from './base-server/index.js'
9
9
  export { Context } from './context/index.js'
10
10
  export { filterMeta } from './filter-meta/index.js'
11
- export {
12
- del,
13
- get,
14
- patch,
15
- post,
16
- put,
17
- request,
18
- ResponseError
19
- } from './request/index.js'
11
+ export { ResponseError } from './request/index.js'
20
12
  export { Server } from './server/index.js'
21
13
  export { TestClient } from './test-client/index.js'
22
14
  export { TestServer } from './test-server/index.js'
@@ -1,5 +1,6 @@
1
- import dotenv from 'dotenv'
2
- import pico from 'picocolors'
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { styleText } from 'node:util'
3
4
 
4
5
  export function loadOptions(spec, process, env) {
5
6
  let rawCliArgs = gatherCliArgs(process.argv)
@@ -18,7 +19,7 @@ export function loadOptions(spec, process, env) {
18
19
  }
19
20
 
20
21
  let cliArgs = parseValues(spec, mapArgs(rawCliArgs, namesMap))
21
- let dotenvArgs = parseEnvArgs(env)
22
+ let dotenvArgs = loadEnv(env)
22
23
  if (dotenvArgs) {
23
24
  dotenvArgs = Object.fromEntries(
24
25
  Object.entries(dotenvArgs).filter(([key]) =>
@@ -72,17 +73,38 @@ function parseValues(spec, args) {
72
73
  parsed[key] = parsingResult[1]
73
74
  } else {
74
75
  throw Error(
75
- `Failed to parse ${pico.bold(key)} argument value. \n${
76
+ `Failed to parse ${styleText('bold', key)} argument value. \n` +
76
77
  parsingResult[0]
77
- }`
78
78
  )
79
79
  }
80
80
  }
81
81
  return parsed
82
82
  }
83
83
 
84
- function parseEnvArgs(file) {
85
- return dotenv.config(file).parsed
84
+ function loadEnv(file) {
85
+ if (!file || !existsSync(file)) {
86
+ file = join(process.cwd(), '.env')
87
+ if (!existsSync(file)) return undefined
88
+ }
89
+ let result = {}
90
+ let lines
91
+ try {
92
+ lines = readFileSync(file, 'utf8').split('\n')
93
+ } catch {
94
+ /* c8 ignore next 2 */
95
+ return undefined
96
+ }
97
+ for (let line of lines) {
98
+ if (line.trim().startsWith('#') || !line.includes('=')) {
99
+ continue
100
+ }
101
+ let [key, ...valueParts] = line.split('=')
102
+ result[key] = valueParts
103
+ .join('=')
104
+ .trim()
105
+ .replace(/(^"|"$|^'|'$)/g, '')
106
+ }
107
+ return result
86
108
  }
87
109
 
88
110
  function mapArgs(parsedCliArgs, argsSpec) {
@@ -106,8 +128,9 @@ function composeHelp(spec, argv) {
106
128
  let nameColumnLength = Math.max(...options.map(it => it.full.length))
107
129
  let envColumnLength = Math.max(...options.map(it => it.env.length))
108
130
 
109
- let composeName = name => pico.yellow(name.padEnd(nameColumnLength + 2))
110
- let composeEnv = env => pico.cyan(env.padEnd(envColumnLength + 2))
131
+ let composeName = name =>
132
+ styleText('yellow', name.padEnd(nameColumnLength + 2))
133
+ let composeEnv = env => styleText('cyan', env.padEnd(envColumnLength + 2))
111
134
  let composeOptionHelp = option => {
112
135
  return (
113
136
  composeName(option.full) + composeEnv(option.env) + option.description
@@ -120,7 +143,7 @@ function composeHelp(spec, argv) {
120
143
  let lastPart = pathParts[pathParts.length - 1]
121
144
  examples = [
122
145
  '',
123
- pico.bold('Examples:'),
146
+ styleText('bold', 'Examples:'),
124
147
  ...spec.examples.map(i => i.replace('$0', lastPart))
125
148
  ]
126
149
  }
@@ -128,7 +151,7 @@ function composeHelp(spec, argv) {
128
151
  return [
129
152
  'Start Logux Server',
130
153
  '',
131
- pico.bold('Options:'),
154
+ styleText('bold', 'Options:'),
132
155
  ...options.map(option => composeOptionHelp(option)),
133
156
  ...examples
134
157
  ].join('\n')
@@ -157,7 +180,8 @@ export function oneOf(options, rawValue) {
157
180
  if (!options.includes(rawValue)) {
158
181
  let opt = JSON.stringify(options)
159
182
  return [
160
- `Expected ${pico.green('one of ' + opt)}, got ${pico.red(rawValue)}`,
183
+ `Expected ${styleText('green', 'one of ' + opt)}, ` +
184
+ `got ${styleText('red', `${rawValue}`)}`,
161
185
  null
162
186
  ]
163
187
  } else {
@@ -168,7 +192,11 @@ export function oneOf(options, rawValue) {
168
192
  export function number(rawValue) {
169
193
  let parsed = Number.parseInt(rawValue, 10)
170
194
  if (Number.isNaN(parsed)) {
171
- return [`Expected ${pico.green('number')}, got ${pico.red(rawValue)}`, null]
195
+ return [
196
+ `Expected ${styleText('green', 'number')}, ` +
197
+ `got ${styleText('red', `${rawValue}`)}`,
198
+ null
199
+ ]
172
200
  } else {
173
201
  return [null, parsed]
174
202
  }
@@ -176,7 +204,11 @@ export function number(rawValue) {
176
204
 
177
205
  export function string(rawValue) {
178
206
  if (typeof rawValue !== 'string') {
179
- return [`Expected ${pico.green('string')}, got ${pico.red(rawValue)}`, null]
207
+ return [
208
+ `Expected ${styleText('green', 'string')}, ` +
209
+ `got ${styleText('red', `${rawValue}`)}`,
210
+ null
211
+ ]
180
212
  } else {
181
213
  return [null, rawValue]
182
214
  }
@@ -1,3 +1,5 @@
1
+ import '../test/force-colors.js'
2
+
1
3
  import { resolve } from 'node:path'
2
4
  import { describe, expect, it } from 'vitest'
3
5
 
@@ -73,9 +75,7 @@ describe('loadOptions', () => {
73
75
  }
74
76
  },
75
77
  fakeProcess([], {}),
76
- {
77
- path: resolve(process.cwd(), 'options-loader/test.env')
78
- }
78
+ resolve(process.cwd(), 'options-loader/test.env')
79
79
  )
80
80
 
81
81
  expect(options.port).toEqual(31337)
@@ -1,2 +1,4 @@
1
+ # Comment
2
+
1
3
  DATABASE_URL=postgresql://localhost/logux
2
4
  LOGUX_PORT=31337
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@logux/server",
3
- "version": "0.13.0",
4
- "description": "Build own Logux server or make proxy between WebSocket and HTTP backend on any language",
3
+ "version": "0.14.0",
4
+ "description": "Build own Logux server",
5
5
  "keywords": [
6
6
  "logux",
7
7
  "server",
@@ -18,7 +18,7 @@
18
18
  "homepage": "https://logux.org/",
19
19
  "repository": "logux/server",
20
20
  "engines": {
21
- "node": "^18.0.0 || ^18.0.0 || >=22.0.0"
21
+ "node": "^20.0.0 || >=22.0.0"
22
22
  },
23
23
  "type": "module",
24
24
  "types": "./index.d.ts",
@@ -27,24 +27,14 @@
27
27
  "./package.json": "./package.json"
28
28
  },
29
29
  "dependencies": {
30
- "@logux/actions": "^0.4.0",
31
- "@logux/core": "^0.9.0",
32
- "JSONStream": "^1.3.5",
33
- "cookie": "^0.6.0",
34
- "dotenv": "^16.4.5",
35
- "fast-glob": "^3.3.2",
36
- "fastq": "^1.17.1",
37
- "nanodelay": "^2.0.2",
38
- "nanoevents": "^9.0.0",
39
- "nanoid": "^5.0.7",
40
- "netmask": "^2.0.2",
41
- "node-fetch": "^3.3.2",
42
- "picocolors": "^1.0.1",
43
- "pino": "9.3.2",
44
- "pino-abstract-transport": "^1.2.0",
45
- "semver": "^7.6.3",
46
- "strip-ansi": "^7.1.0",
30
+ "@logux/actions": "^0.5.0",
31
+ "@logux/core": "^0.10.0",
32
+ "cookie": "^1.0.2",
33
+ "fastq": "^1.19.1",
34
+ "nanoevents": "^9.1.0",
35
+ "nanoid": "^5.1.5",
36
+ "tinyglobby": "^0.2.14",
47
37
  "url-pattern": "^1.0.3",
48
- "ws": "^8.18.0"
38
+ "ws": "^8.18.3"
49
39
  }
50
40
  }
@@ -1,6 +1,3 @@
1
- import type { RequestInit } from 'node-fetch'
2
- import type fetch from 'node-fetch'
3
-
4
1
  /**
5
2
  * Throwing this error in `accessAndProcess` or `accessAndLoad`
6
3
  * will deny the action.
@@ -9,100 +6,5 @@ export class ResponseError extends Error {
9
6
  name: 'ResponseError'
10
7
  statusCode: number
11
8
 
12
- constructor(
13
- statusCode: number,
14
- url: string,
15
- opts: RequestInit,
16
- response: string
17
- )
9
+ constructor(statusCode: number, url: string)
18
10
  }
19
-
20
- /**
21
- * Syntax sugar around `node-fetch`, which throws {@link ResponseError}
22
- * on non-2xx response.
23
- *
24
- * @param url Resource URL.
25
- * @param opts `fetch()` options.
26
- * @param fetcher A way to replace `fetch()` for tests.
27
- */
28
- export function request<Body = any>(
29
- url: string,
30
- opts?: RequestInit,
31
- fetcher?: typeof fetch
32
- ): Body
33
-
34
- /**
35
- * Syntax sugar for `GET` requests by `node-fetch`, throwing
36
- * {@link ResponseError} on non-2xx response and parsing JSON response.
37
- *
38
- * ```js
39
- * import { get } from '@logux/server'
40
- *
41
- * let user = get(url)
42
- * ```
43
- *
44
- * @param url Resource URL.
45
- * @param opts `fetch()` options.
46
- * @param fetcher A way to replace `fetch()` for tests.
47
- */
48
- export function get<Body = any>(
49
- url: string,
50
- opts?: Omit<RequestInit, 'method'>,
51
- fetcher?: typeof fetch
52
- ): Body
53
-
54
- /**
55
- * Syntax sugar for `POST` requests by `node-fetch`, throwing
56
- * {@link ResponseError} on non-2xx response.
57
- *
58
- * @param url Resource URL.
59
- * @param opts `fetch()` options.
60
- * @param fetcher A way to replace `fetch()` for tests.
61
- */
62
- export function post<Body = any>(
63
- url: string,
64
- opts?: Omit<RequestInit, 'method'>,
65
- fetcher?: typeof fetch
66
- ): Body
67
-
68
- /**
69
- * Syntax sugar for `PUT` requests by `node-fetch`, throwing
70
- * {@link ResponseError} on non-2xx response and parsing JSON response.
71
- *
72
- * @param url Resource URL.
73
- * @param opts `fetch()` options.
74
- * @param fetcher A way to replace `fetch()` for tests.
75
- */
76
- export function put<Body = any>(
77
- url: string,
78
- opts?: Omit<RequestInit, 'method'>,
79
- fetcher?: typeof fetch
80
- ): Body
81
-
82
- /**
83
- * Syntax sugar for `PATCH` requests by `node-fetch`, throwing
84
- * {@link ResponseError} on non-2xx response and parsing JSON response.
85
- *
86
- * @param url Resource URL.
87
- * @param opts `fetch()` options.
88
- * @param fetcher A way to replace `fetch()` for tests.
89
- */
90
- export function patch<Body = any>(
91
- url: string,
92
- opts?: Omit<RequestInit, 'method'>,
93
- fetcher?: typeof fetch
94
- ): Body
95
-
96
- /**
97
- * Syntax sugar for `DELETE` requests by `node-fetch`, throwing
98
- * {@link ResponseError} on non-2xx response and parsing JSON response.
99
- *
100
- * @param url Resource URL.
101
- * @param opts `fetch()` options.
102
- * @param fetcher A way to replace `fetch()` for tests.
103
- */
104
- export function del<Body = any>(
105
- url: string,
106
- opts?: Omit<RequestInit, 'method'>,
107
- fetcher?: typeof fetch
108
- ): Body
package/request/index.js CHANGED
@@ -1,45 +1,7 @@
1
- import fetch from 'node-fetch'
2
-
3
- function argsToString(url, opts, response) {
4
- let { method, ...other } = opts
5
- return `${method || 'GET'} ${url} ${JSON.stringify(other)} ${response}`
6
- }
7
-
8
1
  export class ResponseError extends Error {
9
- constructor(statusCode, url, opts, response) {
10
- super(`${statusCode} response on ${argsToString(url, opts, response)}`)
2
+ constructor(statusCode, url) {
3
+ super(`${statusCode} response on ${url}`)
11
4
  this.name = 'ResponseError'
12
5
  this.statusCode = statusCode
13
6
  }
14
7
  }
15
-
16
- export async function request(url, opts = {}, fetcher = fetch) {
17
- let response = await fetcher(url, opts)
18
- if (response.status === 204) {
19
- return undefined
20
- } else if (response.status >= 200 && response.status <= 299) {
21
- return response.json()
22
- } else {
23
- throw new ResponseError(response.status, url, opts, await response.text())
24
- }
25
- }
26
-
27
- export function get(url, opts = {}, fetcher = fetch) {
28
- return request(url, { ...opts, method: 'GET' }, fetcher)
29
- }
30
-
31
- export function post(url, opts = {}, fetcher = fetch) {
32
- return request(url, { ...opts, method: 'POST' }, fetcher)
33
- }
34
-
35
- export function put(url, opts = {}, fetcher = fetch) {
36
- return request(url, { ...opts, method: 'PUT' }, fetcher)
37
- }
38
-
39
- export function patch(url, opts = {}, fetcher = fetch) {
40
- return request(url, { ...opts, method: 'PATCH' }, fetcher)
41
- }
42
-
43
- export function del(url, opts = {}, fetcher = fetch) {
44
- return request(url, { ...opts, method: 'DELETE' }, fetcher)
45
- }