@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.
package/server/index.d.ts CHANGED
@@ -4,18 +4,25 @@ import {
4
4
  type Logger
5
5
  } from '../base-server/index.js'
6
6
 
7
+ export interface LogStream {
8
+ /**
9
+ * Used to synchronously write log messages on application failure.
10
+ */
11
+ flushSync?(): void
12
+
13
+ write(str: string): void
14
+ }
15
+
7
16
  export interface LoggerOptions {
8
17
  /**
9
- * Stream to be used by logger to write log.
18
+ * Use color for human output.
10
19
  */
11
- stream?: {
12
- /**
13
- * Used by pino to synchronously write log messages on application failure.
14
- */
15
- flushSync?(): void
20
+ color?: boolean
16
21
 
17
- write(str: string): void
18
- }
22
+ /**
23
+ * Stream to be used by logger to write log.
24
+ */
25
+ stream?: LogStream
19
26
 
20
27
  /**
21
28
  * Logger message format.
@@ -28,28 +35,6 @@ export interface ServerOptions extends BaseServerOptions {
28
35
  * Logger with custom settings.
29
36
  *
30
37
  * You can either configure built-in logger or provide your own.
31
- * Custom logger example: pino logger that streams logs to
32
- * elasticsearch
33
- *
34
- * ```js
35
- * import pino from 'pino'
36
- * import pinoElastic from 'pino-elasticsearch'
37
- *
38
- * const streamToElastic = pinoElastic({
39
- * consistency: 'one',
40
- * node: 'http://localhost:9200',
41
- * ecs: true
42
- * })
43
- *
44
- * const server = new Server(
45
- * Server.loadOptions(process, {
46
- * …,
47
- * logger: pino({ level: 'info' }, streamToElastic)
48
- * })
49
- * )
50
- * ```
51
- *
52
- * Other logger examples can be found here http://getpino.io/#/docs/ecosystem
53
38
  */
54
39
  logger?: Logger | LoggerOptions
55
40
  }
@@ -68,15 +53,17 @@ export interface ServerOptions extends BaseServerOptions {
68
53
  * }
69
54
  *
70
55
  * const server = new Server(Object.assign({
71
- * subprotocol: '1.0.0',
72
- * supports: '1.x || 0.x',
73
- * fileUrl: import.meta.url
56
+ * subprotocol: 1,
57
+ * minSubprotocol: 1,
58
+ * root: import.meta.dirname
74
59
  * }, envOptions))
75
60
  *
76
61
  * server.listen()
77
62
  * ```
78
63
  */
79
- export class Server<Headers extends object = {}> extends BaseServer<Headers> {
64
+ export class Server<
65
+ Headers extends object = unknown
66
+ > extends BaseServer<Headers> {
80
67
  /**
81
68
  * Server options.
82
69
  *
@@ -96,9 +83,9 @@ export class Server<Headers extends object = {}> extends BaseServer<Headers> {
96
83
  *
97
84
  * ```js
98
85
  * const server = new Server(Server.loadOptions(process, {
99
- * subprotocol: '1.0.0',
100
- * supports: '1.x',
101
- * fileUrl: import.meta.url,
86
+ * minSubprotocol: 1,
87
+ * subprotocol: 1,
88
+ * root: import.meta.dirname,
102
89
  * port: 31337
103
90
  * }))
104
91
  * ```
package/server/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import glob from 'fast-glob'
2
1
  import { join, relative } from 'node:path'
3
- import pico from 'picocolors'
2
+ import { styleText } from 'node:util'
3
+ import { glob } from 'tinyglobby'
4
4
 
5
5
  import { BaseServer } from '../base-server/index.js'
6
6
  import { createReporter } from '../create-reporter/index.js'
@@ -13,18 +13,9 @@ let cliOptionsSpec = {
13
13
  'LOGUX_PORT=1337 LOGUX_HOST=127.0.0.1 $0'
14
14
  ],
15
15
  options: {
16
- backend: {
17
- description: 'Backend to process actions'
18
- },
19
16
  cert: {
20
17
  description: 'Path to SSL certificate'
21
18
  },
22
- controlMask: {
23
- description: 'CIDR masks of control servers'
24
- },
25
- controlSecret: {
26
- description: 'Secret to control Logux server'
27
- },
28
19
  host: {
29
20
  alias: 'h',
30
21
  description: 'Host to bind server'
@@ -37,6 +28,9 @@ let cliOptionsSpec = {
37
28
  description: 'Logger type',
38
29
  parse: value => oneOf(['human', 'json'], value)
39
30
  },
31
+ minSubprotocol: {
32
+ description: 'Check supported client subprotocols'
33
+ },
40
34
  port: {
41
35
  alias: 'p',
42
36
  description: 'Port to bind server',
@@ -47,9 +41,6 @@ let cliOptionsSpec = {
47
41
  },
48
42
  subprotocol: {
49
43
  description: 'Server subprotocol'
50
- },
51
- supports: {
52
- description: 'Range of supported client subprotocols'
53
44
  }
54
45
  }
55
46
  }
@@ -106,7 +97,7 @@ export class Server extends BaseServer {
106
97
  let [help, options] = loadOptions(
107
98
  cliOptionsSpec,
108
99
  process,
109
- defaults.root ? { path: join(defaults.root, '.env') } : undefined
100
+ defaults.root ? join(defaults.root, '.env') : undefined
110
101
  )
111
102
  if (help) {
112
103
  process.stdout.write(help + '\n')
@@ -116,7 +107,10 @@ export class Server extends BaseServer {
116
107
  return Object.assign(defaults, options)
117
108
  } catch (e) {
118
109
  process.stderr.write(
119
- `${pico.bgRed(pico.black(' FATAL '))} ${e.message}\n`
110
+ styleText(
111
+ 'red',
112
+ styleText('bgRed', styleText('black', ' FATAL ')) + `${e.message}\n`
113
+ )
120
114
  )
121
115
  return process.exit(1)
122
116
  }
@@ -125,6 +119,7 @@ export class Server extends BaseServer {
125
119
  async autoloadModules(
126
120
  files = ['modules/*/index.js', 'modules/*.js', '!**/*.{test,spec}.js']
127
121
  ) {
122
+ if (!Array.isArray(files)) files = [files]
128
123
  let matches = await glob(files, {
129
124
  absolute: true,
130
125
  cwd: this.options.root,
@@ -100,19 +100,4 @@ export class ServerClient {
100
100
  * Disconnect client.
101
101
  */
102
102
  destroy(): void
103
-
104
- /**
105
- * Check client subprotocol version. It uses `semver` npm package
106
- * to parse requirements.
107
- *
108
- * ```js
109
- * if (client.isSubprotocol('4.x')) {
110
- * useOldAPI()
111
- * }
112
- * ```
113
- *
114
- * @param range npm’s version requirements.
115
- * @returns Is version satisfies requirements.
116
- */
117
- isSubprotocol(range: string): boolean
118
103
  }
@@ -1,9 +1,9 @@
1
1
  import { LoguxError, parseId } from '@logux/core'
2
2
  import cookie from 'cookie'
3
3
  import fastq from 'fastq'
4
- import semver from 'semver'
5
4
 
6
5
  import { ALLOWED_META } from '../allowed-meta/index.js'
6
+ import { Context } from '../context/index.js'
7
7
  import { filterMeta } from '../filter-meta/index.js'
8
8
  import { FilteredNode } from '../filtered-node/index.js'
9
9
 
@@ -81,7 +81,9 @@ export class ServerClient {
81
81
  this.httpHeaders = {}
82
82
  }
83
83
 
84
- this.node = new FilteredNode(this, app.nodeId, app.log, connection, {
84
+ let Node = app.options.Node || FilteredNode
85
+
86
+ this.node = new Node(this, app.nodeId, app.log, connection, {
85
87
  auth: this.auth.bind(this),
86
88
  onReceive: this.onReceive.bind(this),
87
89
  onSend,
@@ -93,6 +95,19 @@ export class ServerClient {
93
95
  this.node.setLocalHeaders({ env: 'development' })
94
96
  }
95
97
 
98
+ if (app.connectLoader) {
99
+ this.node.syncSinceQuery = async lastSynced => {
100
+ let context = new Context(app, this)
101
+ let entries = await app.connectLoader(context, lastSynced)
102
+ let added = entries.reduce((max, entry) => {
103
+ let meta = filterMeta(entry[1])
104
+ entry[1] = meta
105
+ return meta.added > max ? meta.added : max
106
+ }, 0)
107
+ return { added, entries }
108
+ }
109
+ }
110
+
96
111
  this.node.catch(err => {
97
112
  err.connectionId = this.key
98
113
  this.app.emitter.emit('error', err)
@@ -116,10 +131,10 @@ export class ServerClient {
116
131
  this.clientId = clientId
117
132
  this.userId = userId
118
133
 
119
- if (this.app.options.supports) {
120
- if (!this.isSubprotocol(this.app.options.supports)) {
134
+ if (this.app.options.minSubprotocol) {
135
+ if (this.node.remoteSubprotocol < this.app.options.minSubprotocol) {
121
136
  throw new LoguxError('wrong-subprotocol', {
122
- supported: this.app.options.supports,
137
+ supported: this.app.options.minSubprotocol,
123
138
  used: this.node.remoteSubprotocol
124
139
  })
125
140
  }
@@ -149,6 +164,7 @@ export class ServerClient {
149
164
  })
150
165
  } catch (e) {
151
166
  if (e.name === 'LoguxError') {
167
+ /* c8 ignore next 1 */
152
168
  throw e
153
169
  } else {
154
170
  e.nodeId = nodeId
@@ -223,10 +239,6 @@ export class ServerClient {
223
239
  this.app.connected.delete(this.key)
224
240
  }
225
241
 
226
- isSubprotocol(range) {
227
- return semver.satisfies(this.node.remoteSubprotocol, range)
228
- }
229
-
230
242
  onReceive(action, meta) {
231
243
  if (this.app.actionToQueue.has(meta.id)) {
232
244
  return Promise.resolve(false)
@@ -1,5 +1,14 @@
1
- import type { LoguxSubscribeAction, LoguxUnsubscribeAction } from '@logux/actions'
2
- import type { Action, AnyAction, TestLog, TestPair } from '@logux/core'
1
+ import type {
2
+ LoguxSubscribeAction,
3
+ LoguxUnsubscribeAction
4
+ } from '@logux/actions'
5
+ import type {
6
+ Action,
7
+ AnyAction,
8
+ ClientNode,
9
+ TestLog,
10
+ TestPair
11
+ } from '@logux/core'
3
12
 
4
13
  import type { ServerMeta } from '../base-server/index.js'
5
14
  import type { TestServer } from '../test-server/index.js'
@@ -12,7 +21,7 @@ export interface TestClientOptions {
12
21
  cookie?: object
13
22
  headers?: object
14
23
  httpHeaders?: { [key: string]: string }
15
- subprotocol?: string
24
+ subprotocol?: number
16
25
  token?: string
17
26
  }
18
27
 
@@ -77,6 +86,11 @@ export class TestClient {
77
86
  */
78
87
  log: TestLog
79
88
 
89
+ /**
90
+ * Logux node.
91
+ */
92
+ node: ClientNode<object, TestLog<ServerMeta>>
93
+
80
94
  /**
81
95
  * Client’s node ID.
82
96
  *
@@ -181,10 +195,10 @@ export class TestClient {
181
195
  * @param test Function, where do you expect action will be received
182
196
  * @returns Promise with all received actions
183
197
  */
184
- received(test: () => any | Promise<any>): Promise<Action[]>
198
+ received(test: () => unknown): Promise<Action[]>
185
199
 
186
200
  /**
187
- * Subscribe to the channel and collect all actions dunring the subscription.
201
+ * Subscribe to the channel and collect all actions during the subscription.
188
202
  *
189
203
  * ```js
190
204
  * let posts = await client.subscribe('posts')
@@ -1,6 +1,6 @@
1
1
  import { ClientNode, TestPair } from '@logux/core'
2
2
  import cookie from 'cookie'
3
- import { delay } from 'nanodelay'
3
+ import { setTimeout } from 'node:timers/promises'
4
4
 
5
5
  import { filterMeta } from '../filter-meta/index.js'
6
6
 
@@ -141,7 +141,7 @@ export class TestClient {
141
141
  }
142
142
  })
143
143
  await test()
144
- await delay(1)
144
+ await setTimeout(1)
145
145
  unbind()
146
146
  return actions
147
147
  }
@@ -10,7 +10,7 @@ import type { LoggerOptions } from '../server/index.js'
10
10
  import type { TestClient, TestClientOptions } from '../test-client/index.js'
11
11
 
12
12
  export interface TestServerOptions
13
- extends Omit<BaseServerOptions, 'subprotocol' | 'supports'> {
13
+ extends Omit<BaseServerOptions, 'minSubprotocol' | 'subprotocol'> {
14
14
  /**
15
15
  * Disable built-in auth.
16
16
  */
@@ -21,9 +21,9 @@ export interface TestServerOptions
21
21
  */
22
22
  logger?: Logger | LoggerOptions
23
23
 
24
- subprotocol?: string
24
+ minSubprotocol?: number
25
25
 
26
- supports?: string
26
+ subprotocol?: number
27
27
  }
28
28
 
29
29
  /**
@@ -46,8 +46,21 @@ export interface TestServerOptions
46
46
  * ```
47
47
  */
48
48
  export class TestServer<
49
- Headers extends object = {}
49
+ Headers extends object = unknown
50
50
  > extends BaseServer<Headers> {
51
+ /**
52
+ * fetch() compatible API to test HTTP endpoints.
53
+ *
54
+ * ```js
55
+ * server.http('GET', '/version', (req, res) => {
56
+ * res.end('1.0.0')
57
+ * })
58
+ * let res = await server.fetch()
59
+ * expect(await res.text()).toEqual('1.0.0')
60
+ * ```
61
+ */
62
+ fetch: typeof fetch
63
+
51
64
  /**
52
65
  * Server actions log, with methods to check actions inside.
53
66
  *
@@ -93,7 +106,7 @@ export class TestServer<
93
106
  *
94
107
  * @param test Callback with subscripting or action sending.
95
108
  */
96
- expectDenied(test: () => any | Promise<any>): Promise<void>
109
+ expectDenied(test: () => unknown): Promise<void>
97
110
 
98
111
  /**
99
112
  * Call callback and throw an error if there was no error during
@@ -102,10 +115,7 @@ export class TestServer<
102
115
  * @param text RegExp or string of error message.
103
116
  * @param test Callback with subscripting or action sending.
104
117
  */
105
- expectError(
106
- text: RegExp | string,
107
- test: () => any | Promise<any>
108
- ): Promise<void>
118
+ expectError(text: RegExp | string, test: () => unknown): Promise<void>
109
119
 
110
120
  /**
111
121
  * Call callback and throw an error if there was no `logux/undo` in return
@@ -120,7 +130,7 @@ export class TestServer<
120
130
  * @param reason The reason in undo action.
121
131
  * @param test Callback with subscripting or action sending.
122
132
  */
123
- expectUndo(reason: string, test: () => any | Promise<any>): Promise<void>
133
+ expectUndo(reason: string, test: () => unknown): Promise<void>
124
134
 
125
135
  /**
126
136
  * Try to connect client and throw an error is client didn’t received
@@ -1,4 +1,5 @@
1
1
  import { TestTime } from '@logux/core'
2
+ import { createServer } from 'node:http'
2
3
 
3
4
  import { BaseServer } from '../base-server/index.js'
4
5
  import { createReporter } from '../create-reporter/index.js'
@@ -13,8 +14,8 @@ export class TestServer extends BaseServer {
13
14
  opts.time.lastId += 1
14
15
  super({
15
16
  id: `${opts.time.lastId}`,
16
- subprotocol: '0.0.0',
17
- supports: '0.0.0',
17
+ minSubprotocol: 0,
18
+ subprotocol: 0,
18
19
  ...opts
19
20
  })
20
21
  if (opts.logger) {
@@ -31,6 +32,8 @@ export class TestServer extends BaseServer {
31
32
  if (opts.auth !== false) this.auth(() => true)
32
33
  this.testUsers = {}
33
34
 
35
+ this.fetch = this.fetch.bind(this)
36
+
34
37
  this.on('fatal', () => {
35
38
  this.destroy()
36
39
  })
@@ -88,4 +91,20 @@ export class TestServer extends BaseServer {
88
91
  }
89
92
  }
90
93
  }
94
+
95
+ async fetch(path, init) {
96
+ let server = createServer(async (req, res) => {
97
+ await this.processHttp(req, res)
98
+ server.close()
99
+ })
100
+ await new Promise(resolve => {
101
+ server.listen(0, resolve)
102
+ })
103
+ let { port } = server.address()
104
+ return fetch(`http://localhost:${port}${path}`, init)
105
+ }
106
+
107
+ isBruteforce() {
108
+ return false
109
+ }
91
110
  }