@logux/server 0.13.1 → 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/README.md CHANGED
@@ -27,38 +27,18 @@ This repository contains Logux server with:
27
27
  [cult]: http://cultofmartians.com/done.html
28
28
 
29
29
 
30
- ### Logux Server as Proxy
31
-
32
- ```js
33
- import { fileURLToPath } from 'url'
34
-
35
- const server = new Server(
36
- Server.loadOptions(process, {
37
- controlSecret: 'secret',
38
- subprotocol: '1.0.0',
39
- supports: '0.6.2',
40
- backend: 'http://localhost:3000/logux',
41
- fileUrl: import.meta.url
42
- })
43
- )
44
-
45
- server.listen()
46
- ```
47
-
48
-
49
30
  ### Logux Server as Framework
50
31
 
51
32
  ```js
52
- import { fileURLToPath } from 'url'
53
33
  import { isFirstOlder } from '@logux/core'
54
34
  import { dirname } from 'path'
55
35
  import { Server } from '@logux/server'
56
36
 
57
37
  const server = new Server(
58
38
  Server.loadOptions(process, {
59
- subprotocol: '1.0.0',
60
- supports: '1.x',
61
- fileUrl: import.meta.url
39
+ subprotocol: 1,
40
+ minSubprotocol: 1,
41
+ root: import.meta.dirname
62
42
  })
63
43
  )
64
44
 
@@ -0,0 +1,24 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+
4
+ let hello
5
+ async function readHello() {
6
+ if (!hello) {
7
+ hello = await readFile(join(import.meta.dirname, 'hello.html'))
8
+ }
9
+ return hello
10
+ }
11
+
12
+ export function addHttpPages(server) {
13
+ if (!server.options.disableHttpServer) {
14
+ server.http('GET', '/', async (req, res) => {
15
+ let data = await readHello()
16
+ res.writeHead(200, { 'Content-Type': 'text/html' })
17
+ res.end(data)
18
+ })
19
+ server.http('GET', '/health', (req, res) => {
20
+ res.writeHead(200, { 'Content-Type': 'text/plain' })
21
+ res.end('Logux Server: OK\n')
22
+ })
23
+ }
24
+ }
@@ -19,11 +19,18 @@ import type {
19
19
  IncomingMessage,
20
20
  ServerResponse
21
21
  } from 'node:http'
22
- import type { LogFn } from 'pino'
23
22
 
24
- import type { ChannelContext, Context } from '../context/index.js'
23
+ import type {
24
+ ChannelContext,
25
+ ConnectContext,
26
+ Context
27
+ } from '../context/index.js'
25
28
  import type { ServerClient } from '../server-client/index.js'
26
29
 
30
+ interface LogFn {
31
+ (...objs: unknown[]): void
32
+ }
33
+
27
34
  interface TypeOptions {
28
35
  /**
29
36
  * Name of the queue that will be used to process actions
@@ -40,6 +47,23 @@ interface ChannelOptions {
40
47
  queue?: string
41
48
  }
42
49
 
50
+ interface ConnectLoader<Headers extends object = unknown> {
51
+ (
52
+ ctx: ConnectContext<Headers>,
53
+ lastSynced: number
54
+ ):
55
+ | [Action, ServerMeta][]
56
+ | Promise<
57
+ [
58
+ Action,
59
+ Partial<Pick<ServerMeta, 'subprotocol'>> &
60
+ Pick<ServerMeta, 'id' | 'time'>
61
+ ][]
62
+ >
63
+ }
64
+
65
+ type ServerNodeConstructor = new (...args: any[]) => ServerNode
66
+
43
67
  export interface ServerMeta extends Meta {
44
68
  /**
45
69
  * All nodes subscribed to channel will receive the action.
@@ -98,12 +122,6 @@ export interface ServerMeta extends Meta {
98
122
  }
99
123
 
100
124
  export interface BaseServerOptions {
101
- /**
102
- * URL to PHP, Ruby on Rails, or other backend to process actions and
103
- * authentication.
104
- */
105
- backend?: string
106
-
107
125
  /**
108
126
  * SSL certificate or path to it. Path could be relative from server
109
127
  * root. It is required in production mode, because WSS is highly
@@ -119,17 +137,7 @@ export interface BaseServerOptions {
119
137
  cleanFromLog?: RegExp
120
138
 
121
139
  /**
122
- * CIDR masks for IP address, where control requests could came from.
123
- */
124
- controlMask?: string
125
-
126
- /**
127
- * Secret to control the server.
128
- */
129
- controlSecret?: string
130
-
131
- /**
132
- * Disable health check endpoint, control HTTP API, {@link Server#http}.
140
+ * Disable health check endpoint, {@link Server#http}.
133
141
  *
134
142
  * The server will process only WebSocket connection and ignore all other
135
143
  * HTTP request (so they can be processed by other HTTP server).
@@ -144,8 +152,8 @@ export interface BaseServerOptions {
144
152
  env?: 'development' | 'production'
145
153
 
146
154
  /**
147
- * URL of main JS file in the root dir. Shortcut to set `root` in ES modules
148
- * without `fileURLToPath`.
155
+ * URL of main JS file in the root dir for the cases where you can’t use
156
+ * `import.meta.dirname`.
149
157
  *
150
158
  * ```
151
159
  * fileUrl: import.meta.url
@@ -169,6 +177,16 @@ export interface BaseServerOptions {
169
177
  */
170
178
  key?: { pem: string } | string
171
179
 
180
+ /**
181
+ * The version requirements for client subprotocol version.
182
+ */
183
+ minSubprotocol?: number
184
+
185
+ /**
186
+ * Replace class for ServerNode.
187
+ */
188
+ Node?: ServerNodeConstructor
189
+
172
190
  /**
173
191
  * Process ID, to display in logs.
174
192
  */
@@ -184,7 +202,7 @@ export interface BaseServerOptions {
184
202
  * Port to bind server. It will create HTTP server manually to connect
185
203
  * WebSocket server to it. Default is `31337`.
186
204
  */
187
- port?: number
205
+ port?: number | string
188
206
 
189
207
  /**
190
208
  * URL to Redis for Logux Server Pro scaling.
@@ -196,7 +214,7 @@ export interface BaseServerOptions {
196
214
  * Default is `process.cwd()`.
197
215
  *
198
216
  * ```js
199
- * root: __dirname
217
+ * root: import.meta.dirname
200
218
  * ```
201
219
  */
202
220
  root?: string
@@ -215,14 +233,9 @@ export interface BaseServerOptions {
215
233
  store?: LogStore
216
234
 
217
235
  /**
218
- * Server current application subprotocol version in SemVer format.
219
- */
220
- subprotocol?: string
221
-
222
- /**
223
- * npm’s version requirements for client subprotocol version.
236
+ * Server current application subprotocol version.
224
237
  */
225
- supports?: string
238
+ subprotocol?: number
226
239
 
227
240
  /**
228
241
  * Test time to test server.
@@ -352,7 +365,7 @@ interface ActionFinally<
352
365
  */
353
366
  interface ChannelFilter<Headers extends object> {
354
367
  (
355
- ctx: Context<{}, Headers>,
368
+ ctx: Context<unknown, Headers>,
356
369
  action: Readonly<Action>,
357
370
  meta: Readonly<ServerMeta>
358
371
  ): boolean | Promise<boolean>
@@ -551,20 +564,17 @@ interface ReportersArguments {
551
564
  nodeId?: string
552
565
  }
553
566
  listen: {
554
- backend: string
555
567
  cert: boolean
556
- controlMask: string
557
- controlSecret: string
558
568
  environment: 'development' | 'production'
559
569
  host: string
560
570
  loguxServer: string
571
+ minSubprotocol: number
561
572
  nodeId: string
562
573
  notes: object
563
574
  port: string
564
575
  redis: string
565
576
  server: boolean
566
- subprotocol: string
567
- supports: string
577
+ subprotocol: number
568
578
  }
569
579
  processed: {
570
580
  actionId: ID
@@ -607,29 +617,13 @@ export type Resend =
607
617
  | string[]
608
618
 
609
619
  export interface Logger {
620
+ debug(details: object, message: string): void
610
621
  error(details: object, message: string): void
611
622
  fatal(details: object, message: string): void
612
623
  info(details: object, message: string): void
613
624
  warn(details: object, message: string): void
614
625
  }
615
626
 
616
- interface Response {
617
- body: string
618
- header?: {
619
- [name: string]: string
620
- }
621
- }
622
-
623
- interface GetProcessor {
624
- request(request: object): Promise<Response> | Response
625
- safe?: boolean
626
- }
627
-
628
- interface PostProcessor {
629
- command(command: object, request: object): Promise<void>
630
- isValid(command: object): boolean
631
- }
632
-
633
627
  /**
634
628
  * Return `false` if `cb()` got response error with 403.
635
629
  *
@@ -651,7 +645,7 @@ export function wasNot403(cb: () => Promise<void>): Promise<boolean>
651
645
  * Base server class to extend.
652
646
  */
653
647
  export class BaseServer<
654
- Headers extends object = {},
648
+ Headers extends object = unknown,
655
649
  ServerLog extends Log = Log<ServerMeta>
656
650
  > {
657
651
  /**
@@ -673,13 +667,6 @@ export class BaseServer<
673
667
  */
674
668
  connected: Map<string, ServerClient>
675
669
 
676
- /**
677
- * Add callback to internal HTTP server.
678
- */
679
- controls: {
680
- [path: string]: GetProcessor | PostProcessor
681
- }
682
-
683
670
  /**
684
671
  * Production or development mode.
685
672
  *
@@ -755,7 +742,7 @@ export class BaseServer<
755
742
  subscribers: {
756
743
  [channel: string]: {
757
744
  [nodeId: string]: {
758
- filters: Record<string, ChannelFilter<{}> | true>
745
+ filters: Record<string, ChannelFilter<unknown> | true>
759
746
  unsubscribe?: (action: LoguxUnsubscribeAction, meta: ServerMeta) => void
760
747
  }
761
748
  }
@@ -827,15 +814,14 @@ export class BaseServer<
827
814
  * @param options Additional options
828
815
  */
829
816
  channel<
830
- ChannelParams extends object = {},
831
- Data extends object = {},
817
+ ChannelParams extends object = unknown,
818
+ Data extends object = unknown,
832
819
  SubscribeAction extends LoguxSubscribeAction = LoguxSubscribeAction
833
820
  >(
834
821
  pattern: string,
835
822
  callbacks: ChannelCallbacks<SubscribeAction, Data, ChannelParams, Headers>,
836
823
  options?: ChannelOptions
837
824
  ): void
838
-
839
825
  /**
840
826
  * @param pattern Regular expression for channel name.
841
827
  * @param callbacks Callback during subscription process.
@@ -843,7 +829,7 @@ export class BaseServer<
843
829
  */
844
830
  channel<
845
831
  ChannelParams extends string[] = string[],
846
- Data extends object = {},
832
+ Data extends object = unknown,
847
833
  SubscribeAction extends LoguxSubscribeAction = LoguxSubscribeAction
848
834
  >(
849
835
  pattern: RegExp,
@@ -881,21 +867,32 @@ export class BaseServer<
881
867
  * Add non-WebSocket HTTP request processor.
882
868
  *
883
869
  * ```js
884
- * server.http((req, res) => {
885
- * if (req.url === '/auth') {
886
- * let token = signIn(req)
887
- * if (token) {
888
- * res.setHeader('Set-Cookie', `token=${token}; Secure; HttpOnly`)
889
- * res.end()
890
- * } else {
891
- * res.statusCode = 400
892
- * res.end('Wrong user or password')
893
- * }
870
+ * server.http('GET', '/auth', (req, res) => {
871
+ * let token = signIn(req)
872
+ * if (token) {
873
+ * res.setHeader('Set-Cookie', `token=${token}; Secure; HttpOnly`)
874
+ * res.end()
875
+ * } else {
876
+ * res.statusCode = 400
877
+ * res.end('Wrong user or password')
894
878
  * }
895
879
  * })
896
880
  * ```
897
881
  */
898
- http(listener: (req: IncomingMessage, res: ServerResponse) => void): void
882
+ http(
883
+ method: string,
884
+ url: string,
885
+ listener: (
886
+ req: IncomingMessage,
887
+ res: ServerResponse
888
+ ) => Promise<void> | void
889
+ ): void
890
+ http(
891
+ listener: (
892
+ req: IncomingMessage,
893
+ res: ServerResponse
894
+ ) => boolean | Promise<boolean>
895
+ ): void
899
896
 
900
897
  /**
901
898
  * Start WebSocket server and listen for clients.
@@ -909,7 +906,6 @@ export class BaseServer<
909
906
  * @param listener Event listener.
910
907
  */
911
908
  on(event: 'subscriptionCancelled', listener: () => void): Unsubscribe
912
-
913
909
  /**
914
910
  * @param event The event name.
915
911
  * @param listener Subscription listener.
@@ -918,29 +914,26 @@ export class BaseServer<
918
914
  event: 'subscribing',
919
915
  listener: (action: LoguxSubscribeAction, meta: Readonly<ServerMeta>) => void
920
916
  ): Unsubscribe
921
-
922
917
  /**
923
918
  * @param event The event name.
924
919
  * @param listener Processing listener.
925
920
  */
926
921
  on(
927
- event: 'backendGranted' | 'backendProcessed' | 'processed',
922
+ event: 'processed',
928
923
  listener: (
929
924
  action: Action,
930
925
  meta: Readonly<ServerMeta>,
931
926
  latencyMilliseconds: number
932
927
  ) => void
933
928
  ): Unsubscribe
934
-
935
929
  /**
936
930
  * @param event The event name.
937
931
  * @param listener Action listener.
938
932
  */
939
933
  on(
940
- event: 'add' | 'backendSent' | 'clean',
934
+ event: 'add' | 'clean',
941
935
  listener: (action: Action, meta: Readonly<ServerMeta>) => void
942
936
  ): Unsubscribe
943
-
944
937
  /**
945
938
  * @param event The event name.
946
939
  * @param listener Client listener.
@@ -949,7 +942,6 @@ export class BaseServer<
949
942
  event: 'connected' | 'disconnected',
950
943
  listener: (client: ServerClient) => void
951
944
  ): Unsubscribe
952
-
953
945
  /**
954
946
  * Subscribe for synchronization events. It implements nanoevents API.
955
947
  * Supported events:
@@ -985,7 +977,6 @@ export class BaseServer<
985
977
  event: 'clientError' | 'fatal',
986
978
  listener: (err: Error) => void
987
979
  ): Unsubscribe
988
-
989
980
  /**
990
981
  * @param event The event name.
991
982
  * @param listener Error listener.
@@ -994,7 +985,6 @@ export class BaseServer<
994
985
  event: 'error',
995
986
  listener: (err: Error, action: Action, meta: Readonly<ServerMeta>) => void
996
987
  ): Unsubscribe
997
-
998
988
  /**
999
989
  * @param event The event name.
1000
990
  * @param listener Client listener.
@@ -1003,7 +993,6 @@ export class BaseServer<
1003
993
  event: 'authenticated' | 'unauthenticated',
1004
994
  listener: (client: ServerClient, latencyMilliseconds: number) => void
1005
995
  ): Unsubscribe
1006
-
1007
996
  /**
1008
997
  * @param event The event name.
1009
998
  * @param listener Action listener.
@@ -1012,7 +1001,6 @@ export class BaseServer<
1012
1001
  event: 'preadd',
1013
1002
  listener: (action: Action, meta: ServerMeta) => void
1014
1003
  ): Unsubscribe
1015
-
1016
1004
  /**
1017
1005
  * @param event The event name.
1018
1006
  * @param listener Subscription listener.
@@ -1025,7 +1013,6 @@ export class BaseServer<
1025
1013
  latencyMilliseconds: number
1026
1014
  ) => void
1027
1015
  ): Unsubscribe
1028
-
1029
1016
  /**
1030
1017
  * @param event The event name.
1031
1018
  * @param listener Subscription listener.
@@ -1038,7 +1025,6 @@ export class BaseServer<
1038
1025
  clientNodeId: string
1039
1026
  ) => void
1040
1027
  ): Unsubscribe
1041
-
1042
1028
  /**
1043
1029
  * @param event The event name.
1044
1030
  * @param listener Report listener.
@@ -1064,7 +1050,7 @@ export class BaseServer<
1064
1050
  *
1065
1051
  * @param callbacks Callback during subscription process.
1066
1052
  */
1067
- otherChannel<Data extends object = {}>(
1053
+ otherChannel<Data extends object = unknown>(
1068
1054
  callbacks: ChannelCallbacks<LoguxSubscribeAction, Data, [string], Headers>
1069
1055
  ): void
1070
1056
 
@@ -1094,7 +1080,7 @@ export class BaseServer<
1094
1080
  *
1095
1081
  * @param callbacks Callbacks for actions with this type.
1096
1082
  */
1097
- otherType<Data extends object = {}>(
1083
+ otherType<Data extends object = unknown>(
1098
1084
  callbacks: ActionCallbacks<Action, Data, Headers>
1099
1085
  ): void
1100
1086
 
@@ -1131,6 +1117,19 @@ export class BaseServer<
1131
1117
  */
1132
1118
  sendAction(action: Action, meta: ServerMeta): Promise<void> | void
1133
1119
 
1120
+ /**
1121
+ * Change a way how server loads actions history for the client.
1122
+ *
1123
+ * ```js
1124
+ * server.sendOnConnect(async (ctx, lastSynced) => {
1125
+ * return db.loadActions({ user: ctx.userId, after: lastSynced })
1126
+ * })
1127
+ * ```
1128
+ *
1129
+ * @param loader Callback which loads list of actions and meta.
1130
+ */
1131
+ sendOnConnect(loader: ConnectLoader<Headers>)
1132
+
1134
1133
  /**
1135
1134
  * Send `logux/subscribed` if client was not already subscribed.
1136
1135
  *
@@ -1148,12 +1147,11 @@ export class BaseServer<
1148
1147
  * @param callbacks Callbacks for action created by creator.
1149
1148
  * @param options Additional options
1150
1149
  */
1151
- type<Creator extends AbstractActionCreator, Data extends object = {}>(
1150
+ type<Creator extends AbstractActionCreator, Data extends object = unknown>(
1152
1151
  actionCreator: Creator,
1153
1152
  callbacks: ActionCallbacks<ReturnType<Creator>, Data, Headers>,
1154
1153
  options?: TypeOptions
1155
1154
  ): void
1156
-
1157
1155
  /**
1158
1156
  * Define action type’s callbacks.
1159
1157
  *
@@ -1177,7 +1175,7 @@ export class BaseServer<
1177
1175
  * @param callbacks Callbacks for actions with this type.
1178
1176
  * @param options Additional options
1179
1177
  */
1180
- type<TypeAction extends Action = AnyAction, Data extends object = {}>(
1178
+ type<TypeAction extends Action = AnyAction, Data extends object = unknown>(
1181
1179
  name: RegExp | TypeAction['type'],
1182
1180
  callbacks: ActionCallbacks<TypeAction, Data, Headers>,
1183
1181
  options?: TypeOptions