@logux/server 0.16.1 → 0.16.3
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/base-server/index.d.ts +8 -0
- package/base-server/index.js +36 -10
- package/create-reporter/index.js +2 -0
- package/package.json +4 -4
- package/server/index.js +11 -0
- package/server-client/index.js +22 -2
- package/vite.config.ts +0 -25
package/base-server/index.d.ts
CHANGED
|
@@ -238,6 +238,13 @@ export interface BaseServerOptions {
|
|
|
238
238
|
*/
|
|
239
239
|
subprotocol?: number
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* How long an action can stay in the processing queue of the client
|
|
243
|
+
* before the server answers `logux/undo` and moves to the next action.
|
|
244
|
+
* `300000` (5 minutes) by default, `0` disables it.
|
|
245
|
+
*/
|
|
246
|
+
queueTimeout?: number
|
|
247
|
+
|
|
241
248
|
/**
|
|
242
249
|
* How many actions could be sent in a single message. `100` by default.
|
|
243
250
|
*
|
|
@@ -602,6 +609,7 @@ interface ReportersArguments {
|
|
|
602
609
|
}
|
|
603
610
|
unsubscribed: SubscriptionReporter
|
|
604
611
|
useless: ActionReporter
|
|
612
|
+
duplicate: ActionReporter
|
|
605
613
|
wrongChannel: SubscriptionReporter
|
|
606
614
|
zombie: {
|
|
607
615
|
nodeId: string
|
package/base-server/index.js
CHANGED
|
@@ -120,6 +120,9 @@ export class BaseServer {
|
|
|
120
120
|
constructor(opts = {}) {
|
|
121
121
|
this.options = opts
|
|
122
122
|
this.env = this.options.env || process.env.NODE_ENV || 'development'
|
|
123
|
+
if (typeof this.options.queueTimeout === 'undefined') {
|
|
124
|
+
this.options.queueTimeout = 5 * 60 * 1000
|
|
125
|
+
}
|
|
123
126
|
|
|
124
127
|
if (typeof this.options.subprotocol === 'undefined') {
|
|
125
128
|
throw optionError('Missed `subprotocol` option in server constructor')
|
|
@@ -326,6 +329,7 @@ export class BaseServer {
|
|
|
326
329
|
this.typeToQueue = new Map()
|
|
327
330
|
this.queues = new Map()
|
|
328
331
|
this.actionToQueue = new Map()
|
|
332
|
+
this.queueTimers = new Map()
|
|
329
333
|
|
|
330
334
|
this.httpListeners = {}
|
|
331
335
|
this.httpAllListeners = []
|
|
@@ -334,6 +338,8 @@ export class BaseServer {
|
|
|
334
338
|
this.listenNotes = {}
|
|
335
339
|
|
|
336
340
|
let end = (actionId, queue, queueKey, ...args) => {
|
|
341
|
+
clearTimeout(this.queueTimers.get(actionId))
|
|
342
|
+
this.queueTimers.delete(actionId)
|
|
337
343
|
this.actionToQueue.delete(actionId)
|
|
338
344
|
if (queue.length() === 0) {
|
|
339
345
|
this.queues.delete(queueKey)
|
|
@@ -346,17 +352,21 @@ export class BaseServer {
|
|
|
346
352
|
for (let task of remainingTasks) {
|
|
347
353
|
this.undo(task.action, task.meta, 'error')
|
|
348
354
|
this.actionToQueue.delete(task.meta.id)
|
|
355
|
+
// The core waits for it to answer `synced`
|
|
356
|
+
task.onReceiveResolve(false)
|
|
349
357
|
}
|
|
350
358
|
}
|
|
351
359
|
queue.killAndDrain()
|
|
352
360
|
}
|
|
361
|
+
this.releaseQueue = (actionId, error) => {
|
|
362
|
+
let queueKey = this.actionToQueue.get(actionId)
|
|
363
|
+
if (!queueKey) return
|
|
364
|
+
let queue = this.queues.get(queueKey)
|
|
365
|
+
if (error) undoRemainingTasks(queue)
|
|
366
|
+
end(actionId, queue, queueKey, error ?? null)
|
|
367
|
+
}
|
|
353
368
|
this.on('error', (e, action, meta) => {
|
|
354
|
-
|
|
355
|
-
if (queueKey) {
|
|
356
|
-
let queue = this.queues.get(queueKey)
|
|
357
|
-
undoRemainingTasks(queue)
|
|
358
|
-
end(meta.id, queue, queueKey, e)
|
|
359
|
-
}
|
|
369
|
+
if (meta) this.releaseQueue(meta.id, e)
|
|
360
370
|
})
|
|
361
371
|
this.on('processed', (action, meta) => {
|
|
362
372
|
if (action.type === 'logux/undo') {
|
|
@@ -654,6 +664,26 @@ export class BaseServer {
|
|
|
654
664
|
})
|
|
655
665
|
}
|
|
656
666
|
|
|
667
|
+
/**
|
|
668
|
+
* The client re-sent the action, which the log already has: the server
|
|
669
|
+
* stored it, but the client did not get the answer. Answer again
|
|
670
|
+
* and release the queue of the client.
|
|
671
|
+
*/
|
|
672
|
+
async resolveDuplicate(action, meta) {
|
|
673
|
+
let [stored, storedMeta] = await this.log.byId(meta.id)
|
|
674
|
+
this.emitter.emit('report', 'duplicate', {
|
|
675
|
+
actionId: meta.id,
|
|
676
|
+
status: storedMeta ? storedMeta.status : undefined
|
|
677
|
+
})
|
|
678
|
+
if (stored && storedMeta.status === 'processed') {
|
|
679
|
+
// The `processed` event of the answer releases the queue
|
|
680
|
+
this.markAsProcessed(storedMeta)
|
|
681
|
+
} else {
|
|
682
|
+
// Still processing or already answered by `logux/undo`
|
|
683
|
+
this.releaseQueue(meta.id)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
657
687
|
markAsProcessed(meta) {
|
|
658
688
|
this.log.changeMeta(meta.id, { status: 'processed' })
|
|
659
689
|
let data = parseId(meta.id)
|
|
@@ -846,10 +876,6 @@ export class BaseServer {
|
|
|
846
876
|
meta = { ...meta, ...diff }
|
|
847
877
|
}
|
|
848
878
|
|
|
849
|
-
if (this.isUseless(action, meta)) {
|
|
850
|
-
this.emitter.emit('report', 'useless', { action, meta })
|
|
851
|
-
}
|
|
852
|
-
|
|
853
879
|
await this.sendAction(action, meta)
|
|
854
880
|
this.runProcessor(action, meta, processor, start)
|
|
855
881
|
}
|
package/create-reporter/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logux/server",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.3",
|
|
4
4
|
"description": "Build own Logux server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"collaborative",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@logux/actions": "^0.7.
|
|
28
|
-
"@logux/core": "^0.11.
|
|
27
|
+
"@logux/actions": "^0.7.3",
|
|
28
|
+
"@logux/core": "^0.11.8",
|
|
29
29
|
"cookie": "^2.0.1",
|
|
30
|
-
"fastq": "^1.20.
|
|
30
|
+
"fastq": "^1.20.3",
|
|
31
31
|
"nanoevents": "^10.0.0",
|
|
32
32
|
"nanoid": "^6.0.1",
|
|
33
33
|
"ws": "^8.21.3"
|
package/server/index.js
CHANGED
|
@@ -87,9 +87,20 @@ export class Server extends BaseServer {
|
|
|
87
87
|
process.exit(0)
|
|
88
88
|
}
|
|
89
89
|
process.on('SIGINT', onExit)
|
|
90
|
+
// Docker, Kubernetes and PaaS stop the process with SIGTERM.
|
|
91
|
+
process.on('SIGTERM', onExit)
|
|
92
|
+
|
|
93
|
+
// Ctrl+C to `pnpm run --parallel` go only to group.
|
|
94
|
+
if (this.env === 'development') {
|
|
95
|
+
process.stdin.on('end', onExit)
|
|
96
|
+
process.stdin.resume()
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
this.unbind.push(() => {
|
|
92
100
|
process.removeListener('SIGINT', onExit)
|
|
101
|
+
process.removeListener('SIGTERM', onExit)
|
|
102
|
+
process.stdin.removeListener('end', onExit)
|
|
103
|
+
process.stdin.pause()
|
|
93
104
|
})
|
|
94
105
|
}
|
|
95
106
|
|
package/server-client/index.js
CHANGED
|
@@ -30,9 +30,21 @@ function denyBack(app, clientId, action, meta) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async function queueWorker(task, next) {
|
|
33
|
-
let { action, app, clientId, meta, onReceiveResolve, queue } = task
|
|
33
|
+
let { action, app, client, clientId, meta, onReceiveResolve, queue } = task
|
|
34
34
|
queue.next = next
|
|
35
35
|
|
|
36
|
+
if (app.options.queueTimeout) {
|
|
37
|
+
let timer = setTimeout(() => {
|
|
38
|
+
let error = new Error(
|
|
39
|
+
`Action "${meta.id}" was not processed in ${app.options.queueTimeout} ms`
|
|
40
|
+
)
|
|
41
|
+
app.undo(action, meta, 'error')
|
|
42
|
+
app.emitter.emit('error', error, action, meta)
|
|
43
|
+
}, app.options.queueTimeout)
|
|
44
|
+
if (timer.unref) timer.unref()
|
|
45
|
+
app.queueTimers.set(meta.id, timer)
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
let type = action.type
|
|
37
49
|
if (type === 'logux/subscribe' || type === 'logux/unsubscribe') {
|
|
38
50
|
return onReceiveResolve([action, meta])
|
|
@@ -56,7 +68,14 @@ async function queueWorker(task, next) {
|
|
|
56
68
|
denyBack(app, clientId, action, meta)
|
|
57
69
|
return onReceiveResolve(false)
|
|
58
70
|
} else {
|
|
59
|
-
|
|
71
|
+
// The queue is released by the `processed` event of the action,
|
|
72
|
+
// which never comes for the action the log already has: `log.add()`
|
|
73
|
+
// returns `false` and no `batch` event fires. The core ignores
|
|
74
|
+
// `false`, so the action is added here to see the result
|
|
75
|
+
if (client.node.received) client.node.received[meta.id] = true
|
|
76
|
+
let added = await app.log.add(action, meta)
|
|
77
|
+
if (added === false) await app.resolveDuplicate(action, meta)
|
|
78
|
+
return onReceiveResolve(false)
|
|
60
79
|
}
|
|
61
80
|
} catch (e) {
|
|
62
81
|
app.undo(action, meta, 'error')
|
|
@@ -315,6 +334,7 @@ export class ServerClient {
|
|
|
315
334
|
queue.push({
|
|
316
335
|
action,
|
|
317
336
|
app: this.app,
|
|
337
|
+
client: this,
|
|
318
338
|
clientId,
|
|
319
339
|
meta,
|
|
320
340
|
onReceiveResolve: result => {
|
package/vite.config.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config'
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
test: {
|
|
5
|
-
coverage: {
|
|
6
|
-
exclude: [
|
|
7
|
-
'node_modules',
|
|
8
|
-
'server/index.js',
|
|
9
|
-
'test/*',
|
|
10
|
-
'**/*.d.ts',
|
|
11
|
-
'**/*.test.ts',
|
|
12
|
-
'*/errors.ts',
|
|
13
|
-
'*/types.ts',
|
|
14
|
-
'*.config.*',
|
|
15
|
-
'human-formatter'
|
|
16
|
-
],
|
|
17
|
-
provider: 'v8',
|
|
18
|
-
thresholds: {
|
|
19
|
-
lines: 100
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
environment: 'node',
|
|
23
|
-
exclude: ['node_modules', 'test/servers']
|
|
24
|
-
}
|
|
25
|
-
})
|