@logux/server 0.14.0 → 0.16.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 +208 -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 +8 -1
- package/index.js +1 -0
- package/package.json +20 -22
- package/postgres-store/index.d.ts +149 -0
- package/postgres-store/index.js +444 -0
- 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,47 @@ 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(app, targets, settled) {
|
|
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
|
+
if (settled > 0) {
|
|
35
|
+
for (let client of app.connected.values()) {
|
|
36
|
+
if (!targets.has(client) && client.node.lastAddedCache < settled) {
|
|
37
|
+
client.node.lastAddedCache = settled
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
targets.clear()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function maxAdded(settled, meta) {
|
|
45
|
+
if (typeof meta.added === 'undefined' || meta.added < settled) return settled
|
|
46
|
+
return meta.added
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
function optionError(msg) {
|
|
20
50
|
let error = new Error(msg)
|
|
21
51
|
error.logux = true
|
|
@@ -110,7 +140,7 @@ export class BaseServer {
|
|
|
110
140
|
if (!this.options.host) this.options.host = '127.0.0.1'
|
|
111
141
|
}
|
|
112
142
|
|
|
113
|
-
this.nodeId = `server:${this.options.id || nanoid(
|
|
143
|
+
this.nodeId = `server:${this.options.id || nanoid(6)}`
|
|
114
144
|
|
|
115
145
|
if (this.options.fileUrl) {
|
|
116
146
|
this.options.root = dirname(fileURLToPath(this.options.fileUrl))
|
|
@@ -159,89 +189,64 @@ export class BaseServer {
|
|
|
159
189
|
}
|
|
160
190
|
this.replaceResendShortcuts(meta)
|
|
161
191
|
})
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
// Reports are per action, `clean` for a reason-less action is emitted
|
|
193
|
+
// by the log before `batch` event
|
|
194
|
+
this.on('add', (action, meta) => {
|
|
164
195
|
if (meta.reasons.length === 0) {
|
|
165
196
|
cleaned[meta.id] = true
|
|
166
197
|
this.emitter.emit('report', 'addClean', { action, meta })
|
|
167
198
|
} else {
|
|
168
199
|
this.emitter.emit('report', 'add', { action, meta })
|
|
169
200
|
}
|
|
201
|
+
})
|
|
202
|
+
this.on('batch', entries => {
|
|
203
|
+
let start = Date.now()
|
|
204
|
+
let ready = []
|
|
170
205
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (action.type === 'logux/subscribe') {
|
|
176
|
-
if (meta.server === this.nodeId) {
|
|
177
|
-
this.subscribeAction(action, meta, start)
|
|
206
|
+
for (let [action, meta] of entries) {
|
|
207
|
+
if (this.destroying && !this.actionToQueue.has(meta.id)) {
|
|
208
|
+
continue
|
|
178
209
|
}
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
210
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
if (action.type === 'logux/subscribe') {
|
|
212
|
+
if (meta.server === this.nodeId) {
|
|
213
|
+
void this.subscribeAction(action, meta, start)
|
|
214
|
+
}
|
|
215
|
+
continue
|
|
185
216
|
}
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
217
|
|
|
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]
|
|
218
|
+
if (action.type === 'logux/unsubscribe') {
|
|
219
|
+
if (meta.server === this.nodeId) {
|
|
220
|
+
this.unsubscribeAction(action, meta)
|
|
212
221
|
}
|
|
213
|
-
|
|
214
|
-
meta = { ...meta, ...diff }
|
|
222
|
+
continue
|
|
215
223
|
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (this.isUseless(action, meta)) {
|
|
219
|
-
this.emitter.emit('report', 'useless', { action, meta })
|
|
220
|
-
}
|
|
221
224
|
|
|
222
|
-
|
|
225
|
+
let processor = this.getProcessor(action.type)
|
|
226
|
+
if (processor && processor.resend && meta.status === 'waiting') {
|
|
227
|
+
// Resending is asynchronous, so the action can’t wait for others
|
|
228
|
+
void this.resendAction(action, meta, processor, start)
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
223
231
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.internalUnknownType(action, meta)
|
|
227
|
-
return
|
|
232
|
+
if (this.isUseless(action, meta)) {
|
|
233
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
228
234
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
action,
|
|
237
|
-
meta
|
|
238
|
-
)
|
|
239
|
-
this.markAsProcessed(meta)
|
|
235
|
+
ready.push([action, meta, processor])
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (ready.length === 0) return
|
|
239
|
+
let runProcessors = () => {
|
|
240
|
+
for (let [action, meta, processor] of ready) {
|
|
241
|
+
this.runProcessor(action, meta, processor, start)
|
|
240
242
|
}
|
|
241
|
-
} else {
|
|
242
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
243
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
244
243
|
}
|
|
244
|
+
// Actions are processed only after they were sent to the clients
|
|
245
|
+
void Promise.resolve(this.sendBatch(ready)).then(runProcessors, error => {
|
|
246
|
+
// Do not stop action processing on a broken channel filter
|
|
247
|
+
runProcessors()
|
|
248
|
+
throw error
|
|
249
|
+
})
|
|
245
250
|
})
|
|
246
251
|
this.on('clean', (action, meta) => {
|
|
247
252
|
if (cleaned[meta.id]) {
|
|
@@ -445,9 +450,7 @@ export class BaseServer {
|
|
|
445
450
|
normalizeChannelCallbacks(`Channel ${pattern}`, callbacks)
|
|
446
451
|
let channel = Object.assign({}, callbacks)
|
|
447
452
|
if (typeof pattern === 'string') {
|
|
448
|
-
channel.pattern =
|
|
449
|
-
segmentValueCharset: '^/'
|
|
450
|
-
})
|
|
453
|
+
channel.pattern = createPattern(pattern)
|
|
451
454
|
} else {
|
|
452
455
|
channel.regexp = pattern
|
|
453
456
|
}
|
|
@@ -496,6 +499,12 @@ export class BaseServer {
|
|
|
496
499
|
return Promise.all(this.unbind.map(i => i()))
|
|
497
500
|
}
|
|
498
501
|
|
|
502
|
+
drain(clientId) {
|
|
503
|
+
let client = this.clientIds.get(clientId)
|
|
504
|
+
if (!client) return Promise.resolve(false)
|
|
505
|
+
return client.drain()
|
|
506
|
+
}
|
|
507
|
+
|
|
499
508
|
finally(processor, ctx, action, meta) {
|
|
500
509
|
this.contexts.delete(action)
|
|
501
510
|
if (processor && processor.finally) {
|
|
@@ -522,6 +531,11 @@ export class BaseServer {
|
|
|
522
531
|
return undefined
|
|
523
532
|
}
|
|
524
533
|
|
|
534
|
+
handleClient(ws, req) {
|
|
535
|
+
ws.upgradeReq = req
|
|
536
|
+
this.addClient(new ServerConnection(ws))
|
|
537
|
+
}
|
|
538
|
+
|
|
525
539
|
http(method, url, listener) {
|
|
526
540
|
if (this.options.disableHttpServer) {
|
|
527
541
|
throw new Error(
|
|
@@ -624,10 +638,7 @@ export class BaseServer {
|
|
|
624
638
|
await readFile(join(import.meta.dirname, '..', 'package.json'))
|
|
625
639
|
)
|
|
626
640
|
|
|
627
|
-
this.ws.on('connection', (ws, req) =>
|
|
628
|
-
ws.upgradeReq = req
|
|
629
|
-
this.addClient(new ServerConnection(ws))
|
|
630
|
-
})
|
|
641
|
+
this.ws.on('connection', (ws, req) => this.handleClient(ws, req))
|
|
631
642
|
this.emitter.emit('report', 'listen', {
|
|
632
643
|
cert: !!this.options.cert,
|
|
633
644
|
environment: this.env,
|
|
@@ -655,7 +666,12 @@ export class BaseServer {
|
|
|
655
666
|
}
|
|
656
667
|
|
|
657
668
|
on(event, listener) {
|
|
658
|
-
if (
|
|
669
|
+
if (
|
|
670
|
+
event === 'preadd' ||
|
|
671
|
+
event === 'add' ||
|
|
672
|
+
event === 'batch' ||
|
|
673
|
+
event === 'clean'
|
|
674
|
+
) {
|
|
659
675
|
return this.log.emitter.on(event, listener)
|
|
660
676
|
} else {
|
|
661
677
|
return this.emitter.on(event, listener)
|
|
@@ -668,11 +684,7 @@ export class BaseServer {
|
|
|
668
684
|
throw new Error('Callbacks for unknown channel are already defined')
|
|
669
685
|
}
|
|
670
686
|
let channel = Object.assign({}, callbacks)
|
|
671
|
-
channel.pattern =
|
|
672
|
-
match(name) {
|
|
673
|
-
return [name]
|
|
674
|
-
}
|
|
675
|
-
}
|
|
687
|
+
channel.pattern = name => [name]
|
|
676
688
|
this.otherSubscriber = channel
|
|
677
689
|
}
|
|
678
690
|
|
|
@@ -685,6 +697,7 @@ export class BaseServer {
|
|
|
685
697
|
}
|
|
686
698
|
|
|
687
699
|
performUnsubscribe(clientNodeId, action, meta) {
|
|
700
|
+
if (action.channel === '__proto__' || clientNodeId === '__proto__') return
|
|
688
701
|
if (this.subscribers[action.channel]) {
|
|
689
702
|
let subscriber = this.subscribers[action.channel][clientNodeId]
|
|
690
703
|
if (subscriber) {
|
|
@@ -806,7 +819,42 @@ export class BaseServer {
|
|
|
806
819
|
}
|
|
807
820
|
}
|
|
808
821
|
|
|
809
|
-
async
|
|
822
|
+
async resendAction(action, meta, processor, start) {
|
|
823
|
+
let ctx = this.createContext(action, meta)
|
|
824
|
+
let resend
|
|
825
|
+
try {
|
|
826
|
+
resend = await processor.resend(ctx, action, meta)
|
|
827
|
+
} catch (e) {
|
|
828
|
+
this.undo(action, meta, 'error')
|
|
829
|
+
this.emitter.emit('error', e, action, meta)
|
|
830
|
+
this.finally(processor, ctx, action, meta)
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
if (resend) {
|
|
834
|
+
if (typeof resend === 'string') {
|
|
835
|
+
resend = { channels: [resend] }
|
|
836
|
+
} else if (Array.isArray(resend)) {
|
|
837
|
+
resend = { channels: resend }
|
|
838
|
+
} else {
|
|
839
|
+
this.replaceResendShortcuts(resend)
|
|
840
|
+
}
|
|
841
|
+
let diff = {}
|
|
842
|
+
for (let i of RESEND_META) {
|
|
843
|
+
if (resend[i]) diff[i] = resend[i]
|
|
844
|
+
}
|
|
845
|
+
await this.log.changeMeta(meta.id, diff)
|
|
846
|
+
meta = { ...meta, ...diff }
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (this.isUseless(action, meta)) {
|
|
850
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
await this.sendAction(action, meta)
|
|
854
|
+
this.runProcessor(action, meta, processor, start)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
resolveTargets(action, meta, targets) {
|
|
810
858
|
let from = parseId(meta.id).clientId
|
|
811
859
|
let ignoreClients = new Set(meta.excludeClients || [])
|
|
812
860
|
ignoreClients.add(from)
|
|
@@ -816,7 +864,7 @@ export class BaseServer {
|
|
|
816
864
|
let client = this.nodeIds.get(id)
|
|
817
865
|
if (client) {
|
|
818
866
|
ignoreClients.add(client.clientId)
|
|
819
|
-
client
|
|
867
|
+
addTarget(targets, client, action, meta)
|
|
820
868
|
}
|
|
821
869
|
}
|
|
822
870
|
}
|
|
@@ -826,7 +874,7 @@ export class BaseServer {
|
|
|
826
874
|
if (this.clientIds.has(id)) {
|
|
827
875
|
let client = this.clientIds.get(id)
|
|
828
876
|
ignoreClients.add(client.clientId)
|
|
829
|
-
client
|
|
877
|
+
addTarget(targets, client, action, meta)
|
|
830
878
|
}
|
|
831
879
|
}
|
|
832
880
|
}
|
|
@@ -838,32 +886,38 @@ export class BaseServer {
|
|
|
838
886
|
for (let client of users) {
|
|
839
887
|
if (!ignoreClients.has(client.clientId)) {
|
|
840
888
|
ignoreClients.add(client.clientId)
|
|
841
|
-
client
|
|
889
|
+
addTarget(targets, client, action, meta)
|
|
842
890
|
}
|
|
843
891
|
}
|
|
844
892
|
}
|
|
845
893
|
}
|
|
846
894
|
}
|
|
847
895
|
|
|
848
|
-
if (meta.channels)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
896
|
+
if (!meta.channels) return undefined
|
|
897
|
+
|
|
898
|
+
let waiting = []
|
|
899
|
+
for (let channel of meta.channels) {
|
|
900
|
+
if (this.subscribers[channel]) {
|
|
901
|
+
for (let nodeId in this.subscribers[channel]) {
|
|
902
|
+
let clientId = parseId(nodeId).clientId
|
|
903
|
+
if (!ignoreClients.has(clientId)) {
|
|
904
|
+
let subscriber = this.subscribers[channel][nodeId]
|
|
905
|
+
if (subscriber) {
|
|
906
|
+
let ctx = this.createContext(action, meta)
|
|
907
|
+
let client = this.clientIds.get(clientId)
|
|
908
|
+
for (let filter of Object.values(subscriber.filters)) {
|
|
909
|
+
if (typeof filter === 'function') {
|
|
910
|
+
waiting.push(
|
|
911
|
+
Promise.resolve(filter(ctx, action, meta)).then(result => {
|
|
912
|
+
if (result && client && !ignoreClients.has(clientId)) {
|
|
913
|
+
ignoreClients.add(clientId)
|
|
914
|
+
addTarget(targets, client, action, meta)
|
|
915
|
+
}
|
|
916
|
+
})
|
|
917
|
+
)
|
|
918
|
+
} else if (filter && client) {
|
|
919
|
+
ignoreClients.add(clientId)
|
|
920
|
+
addTarget(targets, client, action, meta)
|
|
867
921
|
}
|
|
868
922
|
}
|
|
869
923
|
}
|
|
@@ -871,6 +925,51 @@ export class BaseServer {
|
|
|
871
925
|
}
|
|
872
926
|
}
|
|
873
927
|
}
|
|
928
|
+
return waiting.length > 0 ? Promise.all(waiting) : undefined
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
runProcessor(action, meta, processor, start) {
|
|
932
|
+
if (meta.status === 'waiting') {
|
|
933
|
+
if (!processor) {
|
|
934
|
+
this.internalUnknownType(action, meta)
|
|
935
|
+
return
|
|
936
|
+
}
|
|
937
|
+
if (processor.process) {
|
|
938
|
+
void this.processAction(processor, action, meta, start)
|
|
939
|
+
} else {
|
|
940
|
+
this.emitter.emit('processed', action, meta, 0)
|
|
941
|
+
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
942
|
+
this.markAsProcessed(meta)
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
this.emitter.emit('processed', action, meta, 0)
|
|
946
|
+
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async sendAction(action, meta) {
|
|
951
|
+
let targets = new Map()
|
|
952
|
+
let waiting = this.resolveTargets(action, meta, targets)
|
|
953
|
+
if (waiting) await waiting
|
|
954
|
+
sendTargets(this, targets, maxAdded(0, meta))
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
sendBatch(entries) {
|
|
958
|
+
let targets = new Map()
|
|
959
|
+
let waiting = []
|
|
960
|
+
let settled = 0
|
|
961
|
+
for (let [action, meta] of entries) {
|
|
962
|
+
settled = maxAdded(settled, meta)
|
|
963
|
+
let promise = this.resolveTargets(action, meta, targets)
|
|
964
|
+
if (promise) waiting.push(promise)
|
|
965
|
+
}
|
|
966
|
+
if (waiting.length === 0) {
|
|
967
|
+
sendTargets(this, targets, settled)
|
|
968
|
+
return undefined
|
|
969
|
+
}
|
|
970
|
+
return Promise.all(waiting).then(() => {
|
|
971
|
+
sendTargets(this, targets, settled)
|
|
972
|
+
})
|
|
874
973
|
}
|
|
875
974
|
|
|
876
975
|
sendOnConnect(loader) {
|
|
@@ -887,6 +986,7 @@ export class BaseServer {
|
|
|
887
986
|
}
|
|
888
987
|
|
|
889
988
|
subscribe(nodeId, channel) {
|
|
989
|
+
if (channel === '__proto__' || nodeId === '__proto__') return
|
|
890
990
|
if (!this.subscribers[channel] || !this.subscribers[channel][nodeId]) {
|
|
891
991
|
if (!this.subscribers[channel]) {
|
|
892
992
|
this.subscribers[channel] = {}
|
|
@@ -897,7 +997,7 @@ export class BaseServer {
|
|
|
897
997
|
}
|
|
898
998
|
|
|
899
999
|
async subscribeAction(action, meta, start) {
|
|
900
|
-
if (typeof action.channel !== 'string') {
|
|
1000
|
+
if (typeof action.channel !== 'string' || action.channel === '__proto__') {
|
|
901
1001
|
this.wrongChannel(action, meta)
|
|
902
1002
|
return
|
|
903
1003
|
}
|
|
@@ -910,7 +1010,7 @@ export class BaseServer {
|
|
|
910
1010
|
let match
|
|
911
1011
|
for (let channel of channels) {
|
|
912
1012
|
if (channel.pattern) {
|
|
913
|
-
match = channel.pattern
|
|
1013
|
+
match = channel.pattern(action.channel)
|
|
914
1014
|
} else {
|
|
915
1015
|
match = action.channel.match(channel.regexp)
|
|
916
1016
|
}
|
|
@@ -918,6 +1018,7 @@ export class BaseServer {
|
|
|
918
1018
|
let subscribed = false
|
|
919
1019
|
if (match) {
|
|
920
1020
|
let ctx = this.createContext(action, meta)
|
|
1021
|
+
if (ctx.nodeId === '__proto__') return
|
|
921
1022
|
ctx.params = match
|
|
922
1023
|
try {
|
|
923
1024
|
let access = await channel.access(ctx, action, meta)
|
|
@@ -967,16 +1068,9 @@ export class BaseServer {
|
|
|
967
1068
|
subscribed = true
|
|
968
1069
|
|
|
969
1070
|
if (channel.load) {
|
|
1071
|
+
// Actions from a single `load()` are sent in a single message
|
|
970
1072
|
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
|
-
}
|
|
1073
|
+
if (sendBack) await ctx.sendBack(sendBack)
|
|
980
1074
|
}
|
|
981
1075
|
this.emitter.emit('subscribed', action, meta, Date.now() - start)
|
|
982
1076
|
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) {
|