@logux/server 0.13.1 → 0.15.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,13 +1,9 @@
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
- import { mulberry32, onceXmur3 } from './utils.js'
4
+ import { isBinary, mulberry32, onceXmur3 } from './utils.js'
10
5
 
6
+ const BINARY_MARK = '[binary]'
11
7
  const INDENT = ' '
12
8
  const PADDING = ' '
13
9
  const SEPARATOR = os.EOL + os.EOL
@@ -30,10 +26,11 @@ const PARAMS_BLACKLIST = {
30
26
  }
31
27
 
32
28
  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)
29
+ 20: str => label(' DEBUG ', 'white', 'bgWhite', 'black', str),
30
+ 30: str => label(' INFO ', 'green', 'bgGreen', 'black', str),
31
+ 40: str => label(' WARN ', 'yellow', 'bgYellow', 'black', str),
32
+ 50: str => label(' ERROR ', 'red', 'bgRed', 'white', str),
33
+ 60: str => label(' FATAL ', 'red', 'bgRed', 'white', str)
37
34
  }
38
35
 
39
36
  const COLORS = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan']
@@ -50,16 +47,16 @@ function formatNow() {
50
47
  }
51
48
 
52
49
  function rightPag(str, length) {
53
- let add = length - stripAnsi(str).length
50
+ let add = length - stripVTControlCharacters(str).length
54
51
  for (let i = 0; i < add; i++) str += ' '
55
52
  return str
56
53
  }
57
54
 
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}`
55
+ function label(type, color, labelBg, labelText, message) {
56
+ let pagged = rightPag(styleText(labelBg, styleText(labelText, type)), 8)
57
+ let time = styleText('dim', `at ${formatNow()}`)
58
+ let highlighted = message.replace(/`([^`]+)`/g, styleText('yellow', '$1'))
59
+ return `${pagged}${styleText('bold', styleText(color, highlighted))} ${time}`
63
60
  }
64
61
 
65
62
  function formatName(key) {
@@ -88,7 +85,7 @@ function shuffledColors(str) {
88
85
  return result
89
86
  }
90
87
 
91
- function splitAndColorize(c, partLength, str) {
88
+ function splitAndColorize(partLength, str) {
92
89
  let strBuilder = []
93
90
  let colors = shuffledColors(str)
94
91
 
@@ -104,58 +101,57 @@ function splitAndColorize(c, partLength, str) {
104
101
  if (strToColorize.length === 1) {
105
102
  color = colors[Math.abs(n - 1) % colors.length]
106
103
  }
107
- strBuilder.push(c[color](strToColorize))
104
+ strBuilder.push(styleText(color, strToColorize))
108
105
  }
109
106
 
110
107
  return strBuilder.join('')
111
108
  }
112
109
 
113
- function formatNodeId(c, nodeId) {
110
+ function formatNodeId(nodeId) {
114
111
  let pos = nodeId.lastIndexOf(':')
115
112
  if (pos === -1) {
116
113
  return nodeId
117
114
  } else {
118
115
  let s = nodeId.split(':')
119
- let id = c.bold(s[0])
120
- let random = splitAndColorize(c, 3, s[1])
116
+ let id = styleText('bold', s[0])
117
+ let random = splitAndColorize(3, s[1])
121
118
  return `${id}:${random}`
122
119
  }
123
120
  }
124
121
 
125
- function formatValue(c, value) {
126
- if (typeof value === 'string') {
127
- return '"' + c.bold(value) + '"'
122
+ function formatValue(value) {
123
+ if (isBinary(value)) {
124
+ return styleText('bold', BINARY_MARK)
125
+ } else if (typeof value === 'string') {
126
+ return '"' + styleText('bold', value) + '"'
128
127
  } else if (Array.isArray(value)) {
129
- return formatArray(c, value)
128
+ return formatArray(value)
130
129
  } else if (typeof value === 'object' && value) {
131
- return formatObject(c, value)
130
+ return formatObject(value)
132
131
  } else {
133
- return c.bold(value)
132
+ return styleText('bold', `${value}`)
134
133
  }
135
134
  }
136
135
 
137
- function formatObject(c, obj) {
138
- let items = Object.keys(obj).map(k => `${k}: ${formatValue(c, obj[k])}`)
136
+ function formatObject(obj) {
137
+ let items = Object.keys(obj).map(k => `${k}: ${formatValue(obj[k])}`)
139
138
  return '{ ' + items.join(', ') + ' }'
140
139
  }
141
140
 
142
- function formatArray(c, array) {
143
- let items = array.map(i => formatValue(c, i))
141
+ function formatArray(array) {
142
+ let items = array.map(i => formatValue(i))
144
143
  return '[' + items.join(', ') + ']'
145
144
  }
146
145
 
147
- function formatActionId(c, id) {
146
+ function formatActionId(id) {
148
147
  let p = id.split(' ')
149
148
  if (p.length === 1) {
150
149
  return p
151
150
  }
152
- return `${c.bold(splitAndColorize(c, 4, p[0]))} ${formatNodeId(
153
- c,
154
- p[1]
155
- )} ${c.bold(p[2])}`
151
+ return `${styleText('bold', splitAndColorize(4, p[0]))} ${formatNodeId(p[1])}`
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
@@ -168,31 +164,36 @@ function formatParams(c, params, parent) {
168
164
 
169
165
  let start = PADDING + rightPag(`${name}: `, maxName + 2)
170
166
 
171
- if (name === 'Node ID' || (parent === 'Meta' && name === 'server')) {
172
- return start + formatNodeId(c, value)
167
+ if (isBinary(value)) {
168
+ return start + styleText('bold', BINARY_MARK)
169
+ } else if (
170
+ name === 'Node ID' ||
171
+ (parent === 'Meta' && name === 'server')
172
+ ) {
173
+ return start + formatNodeId(value)
173
174
  } else if (
174
175
  parent === 'Meta' &&
175
176
  (name === 'clients' || name === 'excludeClients')
176
177
  ) {
177
- return `${start}[${value.map(v => `"${formatNodeId(c, v)}"`).join()}]`
178
+ return `${start}[${value.map(v => `"${formatNodeId(v)}"`).join()}]`
178
179
  } else if (name === 'Action ID' || (parent === 'Meta' && name === 'id')) {
179
- return start + formatActionId(c, value)
180
+ return start + formatActionId(value)
180
181
  } else if (Array.isArray(value)) {
181
- return start + formatArray(c, value)
182
+ return start + formatArray(value)
182
183
  } else if (typeof value === 'object' && value) {
183
184
  let nested = Object.keys(value).map(key => [key, value[key]])
184
185
  return (
185
186
  start +
186
187
  NEXT_LINE +
187
188
  INDENT +
188
- formatParams(c, nested, name)
189
+ formatParams(nested, name)
189
190
  .split(NEXT_LINE)
190
191
  .join(NEXT_LINE + INDENT)
191
192
  )
192
193
  } else if (typeof value === 'string' && parent) {
193
- return start + '"' + c.bold(value) + '"'
194
+ return start + '"' + styleText('bold', value) + '"'
194
195
  } else {
195
- return start + c.bold(value)
196
+ return start + styleText('bold', `${value}`)
196
197
  }
197
198
  })
198
199
  .join(NEXT_LINE)
@@ -212,7 +213,7 @@ function splitByLength(string, max) {
212
213
  return lines.map(i => i.trim())
213
214
  }
214
215
 
215
- function prettyStackTrace(c, stack, basepath) {
216
+ function prettyStackTrace(stack, basepath) {
216
217
  return stack
217
218
  .split('\n')
218
219
  .slice(1)
@@ -220,24 +221,25 @@ function prettyStackTrace(c, stack, basepath) {
220
221
  let match = line.match(/\s+at ([^(]+) \(([^)]+)\)/)
221
222
  let isSystem = !match || !match[2].startsWith(basepath)
222
223
  if (isSystem) {
223
- return c.gray(line.replace(/^\s*/, PADDING))
224
+ return styleText('gray', line.replace(/^\s*/, PADDING))
224
225
  } else {
225
226
  let func = match[1]
226
227
  let relative = match[2].slice(basepath.length)
227
228
  let converted = `${PADDING}at ${func} (./${relative})`
228
229
  let isDependency = match[2].includes('node_modules')
229
- return isDependency ? c.gray(converted) : c.red(converted)
230
+ return isDependency
231
+ ? styleText('gray', converted)
232
+ : styleText('red', converted)
230
233
  }
231
234
  })
232
235
  .join(NEXT_LINE)
233
236
  }
234
237
 
235
- function humanFormatter(options) {
236
- let c = pico.createColors(options.color)
238
+ export default function humanFormatter(options) {
237
239
  let basepath = options.basepath
238
240
 
239
241
  return function format(record) {
240
- let message = [LABELS[record.level](c, record.msg)]
242
+ let message = [LABELS[record.level](record.msg)]
241
243
  let params = Object.keys(record)
242
244
  .filter(key => !PARAMS_BLACKLIST[key])
243
245
  .map(key => [formatName(key), record[key]])
@@ -252,53 +254,26 @@ function humanFormatter(options) {
252
254
  }
253
255
 
254
256
  if (record.err && record.err.stack) {
255
- message.push(prettyStackTrace(c, record.err.stack, basepath))
257
+ message.push(prettyStackTrace(record.err.stack, basepath))
256
258
  }
257
259
 
258
- message.push(formatParams(c, params))
260
+ message.push(formatParams(params))
259
261
 
260
262
  if (record.note) {
261
263
  let note = record.note
262
264
  if (typeof note === 'string') {
263
- note = note.replace(/`([^`]+)`/g, c.bold('$1'))
265
+ note = note.replace(/`([^`]+)`/g, styleText('bold', '$1'))
264
266
  note = [].concat(
265
267
  ...note
266
268
  .split('\n')
267
269
  .map(row => splitByLength(row, 80 - PADDING.length))
268
270
  )
269
271
  }
270
- message.push(note.map(i => PADDING + c.gray(i)).join(NEXT_LINE))
272
+ message.push(
273
+ note.map(i => PADDING + styleText('gray', i)).join(NEXT_LINE)
274
+ )
271
275
  }
272
276
 
273
277
  return message.filter(i => i !== '').join(NEXT_LINE) + SEPARATOR
274
278
  }
275
279
  }
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
- }
@@ -1,3 +1,17 @@
1
+ export const BINARY = {
2
+ toJSON() {
3
+ return '[binary]'
4
+ }
5
+ }
6
+
7
+ export function isBinary(value) {
8
+ return (
9
+ value === BINARY ||
10
+ ArrayBuffer.isView(value) ||
11
+ value instanceof ArrayBuffer
12
+ )
13
+ }
14
+
1
15
  export function onceXmur3(str) {
2
16
  let h = 1779033703 ^ str.length
3
17
  for (let i = 0; i < str.length; i++) {
package/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export {
16
16
  ServerMeta,
17
17
  wasNot403
18
18
  } from './base-server/index.js'
19
- export { ChannelContext, Context } from './context/index.js'
19
+ export { Context, ConnectContext, ChannelContext } from './context/index.js'
20
20
  export { filterMeta } from './filter-meta/index.js'
21
21
  export {
22
22
  del,
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,25 +1,22 @@
1
1
  {
2
2
  "name": "@logux/server",
3
- "version": "0.13.1",
4
- "description": "Build own Logux server or make proxy between WebSocket and HTTP backend on any language",
3
+ "version": "0.15.0",
4
+ "description": "Build own Logux server",
5
5
  "keywords": [
6
- "logux",
7
- "server",
8
- "framework",
9
- "websocket",
10
- "proxy",
6
+ "collaborative",
11
7
  "crdt",
12
- "event sourcing",
13
8
  "distributed systems",
14
- "collaborative"
9
+ "event sourcing",
10
+ "framework",
11
+ "logux",
12
+ "proxy",
13
+ "server",
14
+ "websocket"
15
15
  ],
16
- "author": "Andrey Sitnik <andrey@sitnik.ru>",
17
- "license": "MIT",
18
16
  "homepage": "https://logux.org/",
17
+ "license": "MIT",
18
+ "author": "Andrey Sitnik <andrey@sitnik.es>",
19
19
  "repository": "logux/server",
20
- "engines": {
21
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
22
- },
23
20
  "type": "module",
24
21
  "types": "./index.d.ts",
25
22
  "exports": {
@@ -27,23 +24,15 @@
27
24
  "./package.json": "./package.json"
28
25
  },
29
26
  "dependencies": {
30
- "@logux/actions": "^0.4.0",
31
- "@logux/core": "^0.9.0",
32
- "JSONStream": "^1.3.5",
33
- "cookie": "^0.7.1",
34
- "dotenv": "^16.4.5",
35
- "fast-glob": "^3.3.2",
36
- "fastq": "^1.17.1",
37
- "nanoevents": "^9.0.0",
38
- "nanoid": "^5.0.7",
39
- "netmask": "^2.0.2",
40
- "node-fetch": "^3.3.2",
41
- "picocolors": "^1.0.1",
42
- "pino": "9.3.2",
43
- "pino-abstract-transport": "^1.2.0",
44
- "semver": "^7.6.3",
45
- "strip-ansi": "^7.1.0",
46
- "url-pattern": "^1.0.3",
47
- "ws": "^8.18.0"
27
+ "@logux/actions": "^0.6.0",
28
+ "@logux/core": "^0.11.0",
29
+ "cookie": "^2.0.1",
30
+ "fastq": "^1.20.1",
31
+ "nanoevents": "^10.0.0",
32
+ "nanoid": "^6.0.1",
33
+ "ws": "^8.21.3"
34
+ },
35
+ "engines": {
36
+ "node": "^22.5.0 || ^24.0.0 || >=26.0.0"
48
37
  }
49
38
  }
@@ -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