@logux/server 0.14.0 → 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.
- package/LICENSE +1 -1
- package/README.md +15 -18
- package/add-http-pages/hello.html +34 -26
- package/add-sync-map/index.d.ts +1 -3
- package/add-sync-map/index.js +4 -10
- package/base-server/index.d.ts +88 -30
- package/base-server/index.js +194 -114
- package/context/index.d.ts +35 -3
- package/context/index.js +17 -6
- package/create-reporter/index.js +40 -32
- package/human-formatter/index.js +12 -7
- package/human-formatter/utils.js +14 -0
- package/index.d.ts +1 -1
- package/package.json +20 -22
- package/server/index.d.ts +9 -5
- package/server/index.js +16 -7
- package/server-client/index.d.ts +25 -1
- package/server-client/index.js +42 -9
- package/test-client/index.d.ts +5 -2
- package/test-client/index.js +8 -8
- package/test-server/index.d.ts +9 -6
- package/test-server/index.js +4 -3
- package/url-pattern/index.js +28 -0
package/base-server/index.js
CHANGED
|
@@ -5,17 +5,35 @@ import { nanoid } from 'nanoid'
|
|
|
5
5
|
import { readFile } from 'node:fs/promises'
|
|
6
6
|
import { dirname, join } from 'node:path'
|
|
7
7
|
import { fileURLToPath } from 'node:url'
|
|
8
|
-
import UrlPattern from 'url-pattern'
|
|
9
8
|
import { WebSocketServer } from 'ws'
|
|
10
9
|
|
|
11
10
|
import { addHttpPages } from '../add-http-pages/index.js'
|
|
12
11
|
import { Context } from '../context/index.js'
|
|
13
12
|
import { createHttpServer } from '../create-http-server/index.js'
|
|
14
13
|
import { ServerClient } from '../server-client/index.js'
|
|
14
|
+
import { createPattern } from '../url-pattern/index.js'
|
|
15
15
|
|
|
16
16
|
const SKIP_PROCESS = Symbol('skipProcess')
|
|
17
17
|
const RESEND_META = ['channels', 'users', 'clients', 'nodes']
|
|
18
18
|
|
|
19
|
+
function addTarget(targets, client, action, meta) {
|
|
20
|
+
let entries = targets.get(client)
|
|
21
|
+
if (entries) {
|
|
22
|
+
entries.push([action, meta])
|
|
23
|
+
} else {
|
|
24
|
+
targets.set(client, [[action, meta]])
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sendTargets(targets) {
|
|
29
|
+
for (let [client, entries] of targets) {
|
|
30
|
+
let sending = client.node.onAdd(entries)
|
|
31
|
+
// Only `ServerClient` tracks sending for `Server#drain()`
|
|
32
|
+
if (client.track) client.track(sending)
|
|
33
|
+
}
|
|
34
|
+
targets.clear()
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
function optionError(msg) {
|
|
20
38
|
let error = new Error(msg)
|
|
21
39
|
error.logux = true
|
|
@@ -110,7 +128,7 @@ export class BaseServer {
|
|
|
110
128
|
if (!this.options.host) this.options.host = '127.0.0.1'
|
|
111
129
|
}
|
|
112
130
|
|
|
113
|
-
this.nodeId = `server:${this.options.id || nanoid(
|
|
131
|
+
this.nodeId = `server:${this.options.id || nanoid(6)}`
|
|
114
132
|
|
|
115
133
|
if (this.options.fileUrl) {
|
|
116
134
|
this.options.root = dirname(fileURLToPath(this.options.fileUrl))
|
|
@@ -159,89 +177,64 @@ export class BaseServer {
|
|
|
159
177
|
}
|
|
160
178
|
this.replaceResendShortcuts(meta)
|
|
161
179
|
})
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
// Reports are per action, `clean` for a reason-less action is emitted
|
|
181
|
+
// by the log before `batch` event
|
|
182
|
+
this.on('add', (action, meta) => {
|
|
164
183
|
if (meta.reasons.length === 0) {
|
|
165
184
|
cleaned[meta.id] = true
|
|
166
185
|
this.emitter.emit('report', 'addClean', { action, meta })
|
|
167
186
|
} else {
|
|
168
187
|
this.emitter.emit('report', 'add', { action, meta })
|
|
169
188
|
}
|
|
189
|
+
})
|
|
190
|
+
this.on('batch', entries => {
|
|
191
|
+
let start = Date.now()
|
|
192
|
+
let ready = []
|
|
170
193
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (action.type === 'logux/subscribe') {
|
|
176
|
-
if (meta.server === this.nodeId) {
|
|
177
|
-
this.subscribeAction(action, meta, start)
|
|
194
|
+
for (let [action, meta] of entries) {
|
|
195
|
+
if (this.destroying && !this.actionToQueue.has(meta.id)) {
|
|
196
|
+
continue
|
|
178
197
|
}
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
198
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
if (action.type === 'logux/subscribe') {
|
|
200
|
+
if (meta.server === this.nodeId) {
|
|
201
|
+
void this.subscribeAction(action, meta, start)
|
|
202
|
+
}
|
|
203
|
+
continue
|
|
185
204
|
}
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
205
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let resend
|
|
193
|
-
try {
|
|
194
|
-
resend = await processor.resend(ctx, action, meta)
|
|
195
|
-
} catch (e) {
|
|
196
|
-
this.undo(action, meta, 'error')
|
|
197
|
-
this.emitter.emit('error', e, action, meta)
|
|
198
|
-
this.finally(processor, ctx, action, meta)
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
if (resend) {
|
|
202
|
-
if (typeof resend === 'string') {
|
|
203
|
-
resend = { channels: [resend] }
|
|
204
|
-
} else if (Array.isArray(resend)) {
|
|
205
|
-
resend = { channels: resend }
|
|
206
|
-
} else {
|
|
207
|
-
this.replaceResendShortcuts(resend)
|
|
208
|
-
}
|
|
209
|
-
let diff = {}
|
|
210
|
-
for (let i of RESEND_META) {
|
|
211
|
-
if (resend[i]) diff[i] = resend[i]
|
|
206
|
+
if (action.type === 'logux/unsubscribe') {
|
|
207
|
+
if (meta.server === this.nodeId) {
|
|
208
|
+
this.unsubscribeAction(action, meta)
|
|
212
209
|
}
|
|
213
|
-
|
|
214
|
-
meta = { ...meta, ...diff }
|
|
210
|
+
continue
|
|
215
211
|
}
|
|
216
|
-
}
|
|
217
212
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
213
|
+
let processor = this.getProcessor(action.type)
|
|
214
|
+
if (processor && processor.resend && meta.status === 'waiting') {
|
|
215
|
+
// Resending is asynchronous, so the action can’t wait for others
|
|
216
|
+
void this.resendAction(action, meta, processor, start)
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
223
219
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.internalUnknownType(action, meta)
|
|
227
|
-
return
|
|
220
|
+
if (this.isUseless(action, meta)) {
|
|
221
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
228
222
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
action,
|
|
237
|
-
meta
|
|
238
|
-
)
|
|
239
|
-
this.markAsProcessed(meta)
|
|
223
|
+
ready.push([action, meta, processor])
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (ready.length === 0) return
|
|
227
|
+
let runProcessors = () => {
|
|
228
|
+
for (let [action, meta, processor] of ready) {
|
|
229
|
+
this.runProcessor(action, meta, processor, start)
|
|
240
230
|
}
|
|
241
|
-
} else {
|
|
242
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
243
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
244
231
|
}
|
|
232
|
+
// Actions are processed only after they were sent to the clients
|
|
233
|
+
void Promise.resolve(this.sendBatch(ready)).then(runProcessors, error => {
|
|
234
|
+
// Do not stop action processing on a broken channel filter
|
|
235
|
+
runProcessors()
|
|
236
|
+
throw error
|
|
237
|
+
})
|
|
245
238
|
})
|
|
246
239
|
this.on('clean', (action, meta) => {
|
|
247
240
|
if (cleaned[meta.id]) {
|
|
@@ -445,9 +438,7 @@ export class BaseServer {
|
|
|
445
438
|
normalizeChannelCallbacks(`Channel ${pattern}`, callbacks)
|
|
446
439
|
let channel = Object.assign({}, callbacks)
|
|
447
440
|
if (typeof pattern === 'string') {
|
|
448
|
-
channel.pattern =
|
|
449
|
-
segmentValueCharset: '^/'
|
|
450
|
-
})
|
|
441
|
+
channel.pattern = createPattern(pattern)
|
|
451
442
|
} else {
|
|
452
443
|
channel.regexp = pattern
|
|
453
444
|
}
|
|
@@ -496,6 +487,12 @@ export class BaseServer {
|
|
|
496
487
|
return Promise.all(this.unbind.map(i => i()))
|
|
497
488
|
}
|
|
498
489
|
|
|
490
|
+
drain(clientId) {
|
|
491
|
+
let client = this.clientIds.get(clientId)
|
|
492
|
+
if (!client) return Promise.resolve(false)
|
|
493
|
+
return client.drain()
|
|
494
|
+
}
|
|
495
|
+
|
|
499
496
|
finally(processor, ctx, action, meta) {
|
|
500
497
|
this.contexts.delete(action)
|
|
501
498
|
if (processor && processor.finally) {
|
|
@@ -522,6 +519,11 @@ export class BaseServer {
|
|
|
522
519
|
return undefined
|
|
523
520
|
}
|
|
524
521
|
|
|
522
|
+
handleClient(ws, req) {
|
|
523
|
+
ws.upgradeReq = req
|
|
524
|
+
this.addClient(new ServerConnection(ws))
|
|
525
|
+
}
|
|
526
|
+
|
|
525
527
|
http(method, url, listener) {
|
|
526
528
|
if (this.options.disableHttpServer) {
|
|
527
529
|
throw new Error(
|
|
@@ -624,10 +626,7 @@ export class BaseServer {
|
|
|
624
626
|
await readFile(join(import.meta.dirname, '..', 'package.json'))
|
|
625
627
|
)
|
|
626
628
|
|
|
627
|
-
this.ws.on('connection', (ws, req) =>
|
|
628
|
-
ws.upgradeReq = req
|
|
629
|
-
this.addClient(new ServerConnection(ws))
|
|
630
|
-
})
|
|
629
|
+
this.ws.on('connection', (ws, req) => this.handleClient(ws, req))
|
|
631
630
|
this.emitter.emit('report', 'listen', {
|
|
632
631
|
cert: !!this.options.cert,
|
|
633
632
|
environment: this.env,
|
|
@@ -655,7 +654,12 @@ export class BaseServer {
|
|
|
655
654
|
}
|
|
656
655
|
|
|
657
656
|
on(event, listener) {
|
|
658
|
-
if (
|
|
657
|
+
if (
|
|
658
|
+
event === 'preadd' ||
|
|
659
|
+
event === 'add' ||
|
|
660
|
+
event === 'batch' ||
|
|
661
|
+
event === 'clean'
|
|
662
|
+
) {
|
|
659
663
|
return this.log.emitter.on(event, listener)
|
|
660
664
|
} else {
|
|
661
665
|
return this.emitter.on(event, listener)
|
|
@@ -668,11 +672,7 @@ export class BaseServer {
|
|
|
668
672
|
throw new Error('Callbacks for unknown channel are already defined')
|
|
669
673
|
}
|
|
670
674
|
let channel = Object.assign({}, callbacks)
|
|
671
|
-
channel.pattern =
|
|
672
|
-
match(name) {
|
|
673
|
-
return [name]
|
|
674
|
-
}
|
|
675
|
-
}
|
|
675
|
+
channel.pattern = name => [name]
|
|
676
676
|
this.otherSubscriber = channel
|
|
677
677
|
}
|
|
678
678
|
|
|
@@ -685,6 +685,7 @@ export class BaseServer {
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
performUnsubscribe(clientNodeId, action, meta) {
|
|
688
|
+
if (action.channel === '__proto__' || clientNodeId === '__proto__') return
|
|
688
689
|
if (this.subscribers[action.channel]) {
|
|
689
690
|
let subscriber = this.subscribers[action.channel][clientNodeId]
|
|
690
691
|
if (subscriber) {
|
|
@@ -806,7 +807,42 @@ export class BaseServer {
|
|
|
806
807
|
}
|
|
807
808
|
}
|
|
808
809
|
|
|
809
|
-
async
|
|
810
|
+
async resendAction(action, meta, processor, start) {
|
|
811
|
+
let ctx = this.createContext(action, meta)
|
|
812
|
+
let resend
|
|
813
|
+
try {
|
|
814
|
+
resend = await processor.resend(ctx, action, meta)
|
|
815
|
+
} catch (e) {
|
|
816
|
+
this.undo(action, meta, 'error')
|
|
817
|
+
this.emitter.emit('error', e, action, meta)
|
|
818
|
+
this.finally(processor, ctx, action, meta)
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
if (resend) {
|
|
822
|
+
if (typeof resend === 'string') {
|
|
823
|
+
resend = { channels: [resend] }
|
|
824
|
+
} else if (Array.isArray(resend)) {
|
|
825
|
+
resend = { channels: resend }
|
|
826
|
+
} else {
|
|
827
|
+
this.replaceResendShortcuts(resend)
|
|
828
|
+
}
|
|
829
|
+
let diff = {}
|
|
830
|
+
for (let i of RESEND_META) {
|
|
831
|
+
if (resend[i]) diff[i] = resend[i]
|
|
832
|
+
}
|
|
833
|
+
await this.log.changeMeta(meta.id, diff)
|
|
834
|
+
meta = { ...meta, ...diff }
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (this.isUseless(action, meta)) {
|
|
838
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
await this.sendAction(action, meta)
|
|
842
|
+
this.runProcessor(action, meta, processor, start)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
resolveTargets(action, meta, targets) {
|
|
810
846
|
let from = parseId(meta.id).clientId
|
|
811
847
|
let ignoreClients = new Set(meta.excludeClients || [])
|
|
812
848
|
ignoreClients.add(from)
|
|
@@ -816,7 +852,7 @@ export class BaseServer {
|
|
|
816
852
|
let client = this.nodeIds.get(id)
|
|
817
853
|
if (client) {
|
|
818
854
|
ignoreClients.add(client.clientId)
|
|
819
|
-
client
|
|
855
|
+
addTarget(targets, client, action, meta)
|
|
820
856
|
}
|
|
821
857
|
}
|
|
822
858
|
}
|
|
@@ -826,7 +862,7 @@ export class BaseServer {
|
|
|
826
862
|
if (this.clientIds.has(id)) {
|
|
827
863
|
let client = this.clientIds.get(id)
|
|
828
864
|
ignoreClients.add(client.clientId)
|
|
829
|
-
client
|
|
865
|
+
addTarget(targets, client, action, meta)
|
|
830
866
|
}
|
|
831
867
|
}
|
|
832
868
|
}
|
|
@@ -838,32 +874,38 @@ export class BaseServer {
|
|
|
838
874
|
for (let client of users) {
|
|
839
875
|
if (!ignoreClients.has(client.clientId)) {
|
|
840
876
|
ignoreClients.add(client.clientId)
|
|
841
|
-
client
|
|
877
|
+
addTarget(targets, client, action, meta)
|
|
842
878
|
}
|
|
843
879
|
}
|
|
844
880
|
}
|
|
845
881
|
}
|
|
846
882
|
}
|
|
847
883
|
|
|
848
|
-
if (meta.channels)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
884
|
+
if (!meta.channels) return undefined
|
|
885
|
+
|
|
886
|
+
let waiting = []
|
|
887
|
+
for (let channel of meta.channels) {
|
|
888
|
+
if (this.subscribers[channel]) {
|
|
889
|
+
for (let nodeId in this.subscribers[channel]) {
|
|
890
|
+
let clientId = parseId(nodeId).clientId
|
|
891
|
+
if (!ignoreClients.has(clientId)) {
|
|
892
|
+
let subscriber = this.subscribers[channel][nodeId]
|
|
893
|
+
if (subscriber) {
|
|
894
|
+
let ctx = this.createContext(action, meta)
|
|
895
|
+
let client = this.clientIds.get(clientId)
|
|
896
|
+
for (let filter of Object.values(subscriber.filters)) {
|
|
897
|
+
if (typeof filter === 'function') {
|
|
898
|
+
waiting.push(
|
|
899
|
+
Promise.resolve(filter(ctx, action, meta)).then(result => {
|
|
900
|
+
if (result && client && !ignoreClients.has(clientId)) {
|
|
901
|
+
ignoreClients.add(clientId)
|
|
902
|
+
addTarget(targets, client, action, meta)
|
|
903
|
+
}
|
|
904
|
+
})
|
|
905
|
+
)
|
|
906
|
+
} else if (filter && client) {
|
|
907
|
+
ignoreClients.add(clientId)
|
|
908
|
+
addTarget(targets, client, action, meta)
|
|
867
909
|
}
|
|
868
910
|
}
|
|
869
911
|
}
|
|
@@ -871,6 +913,49 @@ export class BaseServer {
|
|
|
871
913
|
}
|
|
872
914
|
}
|
|
873
915
|
}
|
|
916
|
+
return waiting.length > 0 ? Promise.all(waiting) : undefined
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
runProcessor(action, meta, processor, start) {
|
|
920
|
+
if (meta.status === 'waiting') {
|
|
921
|
+
if (!processor) {
|
|
922
|
+
this.internalUnknownType(action, meta)
|
|
923
|
+
return
|
|
924
|
+
}
|
|
925
|
+
if (processor.process) {
|
|
926
|
+
void this.processAction(processor, action, meta, start)
|
|
927
|
+
} else {
|
|
928
|
+
this.emitter.emit('processed', action, meta, 0)
|
|
929
|
+
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
930
|
+
this.markAsProcessed(meta)
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
this.emitter.emit('processed', action, meta, 0)
|
|
934
|
+
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
async sendAction(action, meta) {
|
|
939
|
+
let targets = new Map()
|
|
940
|
+
let waiting = this.resolveTargets(action, meta, targets)
|
|
941
|
+
if (waiting) await waiting
|
|
942
|
+
sendTargets(targets)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
sendBatch(entries) {
|
|
946
|
+
let targets = new Map()
|
|
947
|
+
let waiting = []
|
|
948
|
+
for (let [action, meta] of entries) {
|
|
949
|
+
let promise = this.resolveTargets(action, meta, targets)
|
|
950
|
+
if (promise) waiting.push(promise)
|
|
951
|
+
}
|
|
952
|
+
if (waiting.length === 0) {
|
|
953
|
+
sendTargets(targets)
|
|
954
|
+
return undefined
|
|
955
|
+
}
|
|
956
|
+
return Promise.all(waiting).then(() => {
|
|
957
|
+
sendTargets(targets)
|
|
958
|
+
})
|
|
874
959
|
}
|
|
875
960
|
|
|
876
961
|
sendOnConnect(loader) {
|
|
@@ -887,6 +972,7 @@ export class BaseServer {
|
|
|
887
972
|
}
|
|
888
973
|
|
|
889
974
|
subscribe(nodeId, channel) {
|
|
975
|
+
if (channel === '__proto__' || nodeId === '__proto__') return
|
|
890
976
|
if (!this.subscribers[channel] || !this.subscribers[channel][nodeId]) {
|
|
891
977
|
if (!this.subscribers[channel]) {
|
|
892
978
|
this.subscribers[channel] = {}
|
|
@@ -897,7 +983,7 @@ export class BaseServer {
|
|
|
897
983
|
}
|
|
898
984
|
|
|
899
985
|
async subscribeAction(action, meta, start) {
|
|
900
|
-
if (typeof action.channel !== 'string') {
|
|
986
|
+
if (typeof action.channel !== 'string' || action.channel === '__proto__') {
|
|
901
987
|
this.wrongChannel(action, meta)
|
|
902
988
|
return
|
|
903
989
|
}
|
|
@@ -910,7 +996,7 @@ export class BaseServer {
|
|
|
910
996
|
let match
|
|
911
997
|
for (let channel of channels) {
|
|
912
998
|
if (channel.pattern) {
|
|
913
|
-
match = channel.pattern
|
|
999
|
+
match = channel.pattern(action.channel)
|
|
914
1000
|
} else {
|
|
915
1001
|
match = action.channel.match(channel.regexp)
|
|
916
1002
|
}
|
|
@@ -918,6 +1004,7 @@ export class BaseServer {
|
|
|
918
1004
|
let subscribed = false
|
|
919
1005
|
if (match) {
|
|
920
1006
|
let ctx = this.createContext(action, meta)
|
|
1007
|
+
if (ctx.nodeId === '__proto__') return
|
|
921
1008
|
ctx.params = match
|
|
922
1009
|
try {
|
|
923
1010
|
let access = await channel.access(ctx, action, meta)
|
|
@@ -967,16 +1054,9 @@ export class BaseServer {
|
|
|
967
1054
|
subscribed = true
|
|
968
1055
|
|
|
969
1056
|
if (channel.load) {
|
|
1057
|
+
// Actions from a single `load()` are sent in a single message
|
|
970
1058
|
let sendBack = await channel.load(ctx, action, meta)
|
|
971
|
-
if (
|
|
972
|
-
await Promise.all(
|
|
973
|
-
sendBack.map(i => {
|
|
974
|
-
return Array.isArray(i) ? ctx.sendBack(...i) : ctx.sendBack(i)
|
|
975
|
-
})
|
|
976
|
-
)
|
|
977
|
-
} else if (sendBack) {
|
|
978
|
-
await ctx.sendBack(sendBack)
|
|
979
|
-
}
|
|
1059
|
+
if (sendBack) await ctx.sendBack(sendBack)
|
|
980
1060
|
}
|
|
981
1061
|
this.emitter.emit('subscribed', action, meta, Date.now() - start)
|
|
982
1062
|
this.markAsProcessed(meta)
|
package/context/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyAction } from '@logux/core'
|
|
1
|
+
import type { Action, AnyAction } from '@logux/core'
|
|
2
2
|
|
|
3
3
|
import type { ServerMeta } from '../base-server/index.js'
|
|
4
4
|
import type { ServerClient } from '../server-client/index.js'
|
|
@@ -59,6 +59,24 @@ export class ConnectContext<Headers extends object = unknown> {
|
|
|
59
59
|
|
|
60
60
|
constructor(server: Server, client: ServerClient)
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Wait until the client will confirm all actions, which were sent to it.
|
|
64
|
+
*
|
|
65
|
+
* Use it to send a long history without loading it all into the memory:
|
|
66
|
+
* the client’s speed will limit how fast you read the database.
|
|
67
|
+
*
|
|
68
|
+
* ```js
|
|
69
|
+
* while (await ctx.drain()) {
|
|
70
|
+
* let page = await cursor.next(100)
|
|
71
|
+
* if (!page.length) break
|
|
72
|
+
* ctx.sendBack(page.map(i => i.action))
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @returns Promise with `false` if the client was disconnected.
|
|
77
|
+
*/
|
|
78
|
+
drain(): Promise<boolean>
|
|
79
|
+
|
|
62
80
|
/**
|
|
63
81
|
* Send action back to the client.
|
|
64
82
|
*
|
|
@@ -66,13 +84,27 @@ export class ConnectContext<Headers extends object = unknown> {
|
|
|
66
84
|
* ctx.sendBack({ type: 'login/success', token })
|
|
67
85
|
* ```
|
|
68
86
|
*
|
|
87
|
+
* An array of actions will be sent in a single message. Use it to send
|
|
88
|
+
* a big history page by page instead of a message per action.
|
|
89
|
+
*
|
|
90
|
+
* ```js
|
|
91
|
+
* ctx.sendBack(page.map(i => i.action))
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* Every action in the array can have own meta as `[action, meta]`.
|
|
95
|
+
*
|
|
69
96
|
* Action will not be processed by server’s callbacks from `Server#type`.
|
|
70
97
|
*
|
|
71
|
-
* @param action The action.
|
|
98
|
+
* @param action The action or the array of actions.
|
|
72
99
|
* @param meta Action’s meta.
|
|
73
100
|
* @returns Promise until action was added to the server log.
|
|
74
101
|
*/
|
|
75
|
-
sendBack
|
|
102
|
+
sendBack<TypeAction extends Action = AnyAction>(
|
|
103
|
+
action:
|
|
104
|
+
| (TypeAction | [TypeAction, Partial<ServerMeta>])[]
|
|
105
|
+
| TypeAction,
|
|
106
|
+
meta?: Partial<ServerMeta>
|
|
107
|
+
): Promise<void>
|
|
76
108
|
}
|
|
77
109
|
|
|
78
110
|
/**
|
package/context/index.js
CHANGED
|
@@ -33,11 +33,22 @@ export class Context {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
return this.server.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
36
|
+
drain() {
|
|
37
|
+
return this.server.drain(this.clientId)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sendBack(actions, meta = {}) {
|
|
41
|
+
let common = { clients: [this.clientId], status: 'processed', ...meta }
|
|
42
|
+
if (Array.isArray(actions)) {
|
|
43
|
+
return this.server.log.add(
|
|
44
|
+
actions.map(item => {
|
|
45
|
+
// Every action can have own meta as `[action, meta]`
|
|
46
|
+
return Array.isArray(item)
|
|
47
|
+
? [item[0], { ...common, ...item[1] }]
|
|
48
|
+
: [item, { ...common }]
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
return this.server.log.add(actions, common)
|
|
42
53
|
}
|
|
43
54
|
}
|
package/create-reporter/index.js
CHANGED
|
@@ -2,13 +2,13 @@ import os from 'node:os'
|
|
|
2
2
|
import { sep } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import humanFormatter from '../human-formatter/index.js'
|
|
5
|
+
import { BINARY, isBinary } from '../human-formatter/utils.js'
|
|
5
6
|
|
|
6
7
|
const ERROR_CODES = {
|
|
7
8
|
EACCES: (e, environment) => {
|
|
8
9
|
let wayToFix = {
|
|
9
|
-
development:
|
|
10
|
-
|
|
11
|
-
production: '$ su - `<username>`\n' + `$ npm start -p ${e.port}`
|
|
10
|
+
development: 'In dev mode it can be done with sudo:\n$ sudo npm start',
|
|
11
|
+
production: `$ su - \`<username>\`\n$ npm start -p ${e.port}`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
return {
|
|
@@ -22,7 +22,7 @@ const ERROR_CODES = {
|
|
|
22
22
|
EADDRINUSE: e => {
|
|
23
23
|
let port = String(e.port)
|
|
24
24
|
let wayToFix = {
|
|
25
|
-
darwin: `$ sudo lsof -i ${e.port}\n
|
|
25
|
+
darwin: `$ sudo lsof -i ${e.port}\n$ sudo kill -9 \`<processid>\``,
|
|
26
26
|
linux:
|
|
27
27
|
'$ su - root\n' +
|
|
28
28
|
`# netstat -nlp | grep ${e.port}\n` +
|
|
@@ -176,23 +176,46 @@ const REPORTERS = {
|
|
|
176
176
|
function cleanFromKeys(obj, regexp, seen) {
|
|
177
177
|
let result = {}
|
|
178
178
|
for (let key in obj) {
|
|
179
|
-
|
|
180
|
-
if (typeof v === 'string') {
|
|
181
|
-
result[key] = v.replace(regexp, '[SECRET]')
|
|
182
|
-
} else if (typeof v === 'object' && !Array.isArray(v) && v !== null) {
|
|
183
|
-
if (seen.includes(v)) {
|
|
184
|
-
throw new Error('Circular reference in action')
|
|
185
|
-
}
|
|
186
|
-
seen.push(v)
|
|
187
|
-
result[key] = cleanFromKeys(v, regexp, seen)
|
|
188
|
-
seen.pop()
|
|
189
|
-
} else {
|
|
190
|
-
result[key] = v
|
|
191
|
-
}
|
|
179
|
+
result[key] = cleanValue(obj[key], regexp, seen)
|
|
192
180
|
}
|
|
193
181
|
return result
|
|
194
182
|
}
|
|
195
183
|
|
|
184
|
+
function cleanValue(value, regexp, seen) {
|
|
185
|
+
if (typeof value === 'string') {
|
|
186
|
+
return value.replace(regexp, '[SECRET]')
|
|
187
|
+
} else if (isBinary(value)) {
|
|
188
|
+
return BINARY
|
|
189
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
190
|
+
if (seen.includes(value)) {
|
|
191
|
+
throw new Error('Circular reference in action')
|
|
192
|
+
}
|
|
193
|
+
seen.push(value)
|
|
194
|
+
let cleaned = Array.isArray(value)
|
|
195
|
+
? value.map(i => cleanValue(i, regexp, seen))
|
|
196
|
+
: cleanFromKeys(value, regexp, seen)
|
|
197
|
+
seen.pop()
|
|
198
|
+
return cleaned
|
|
199
|
+
} else {
|
|
200
|
+
return value
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createRecord(level, details, msg) {
|
|
205
|
+
/* c8 ignore next 4 */
|
|
206
|
+
if (typeof details === 'string') {
|
|
207
|
+
msg = details
|
|
208
|
+
details = {}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
level,
|
|
212
|
+
time: new Date().toISOString(),
|
|
213
|
+
pid: process.pid,
|
|
214
|
+
...details,
|
|
215
|
+
msg
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
196
219
|
export function createReporter(options) {
|
|
197
220
|
let cleanFromLog = options.cleanFromLog || /Bearer [^\s"]+/g
|
|
198
221
|
function reporter(type, details) {
|
|
@@ -218,21 +241,6 @@ export function createReporter(options) {
|
|
|
218
241
|
}
|
|
219
242
|
let stream = options.logger?.stream ?? process.stderr
|
|
220
243
|
|
|
221
|
-
function createRecord(level, details, msg) {
|
|
222
|
-
/* c8 ignore next 4 */
|
|
223
|
-
if (typeof details === 'string') {
|
|
224
|
-
msg = details
|
|
225
|
-
details = {}
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
level,
|
|
229
|
-
time: new Date().toISOString(),
|
|
230
|
-
pid: process.pid,
|
|
231
|
-
...details,
|
|
232
|
-
msg
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
244
|
reporter.logger = {
|
|
237
245
|
/* c8 ignore next 3 */
|
|
238
246
|
debug(details, msg) {
|