@logux/server 0.16.3 → 0.17.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/base-server/index.d.ts +65 -39
- package/base-server/index.js +174 -754
- package/context/index.d.ts +13 -1
- package/context/index.js +4 -2
- package/create-reporter/index.js +6 -0
- package/filtered-node/index.js +38 -1
- package/index.d.ts +5 -0
- package/index.js +1 -0
- package/package.json +4 -5
- package/postgres-store/index.d.ts +3 -1
- package/postgres-store/index.js +74 -30
- package/processing/index.js +560 -0
- package/processing-store/index.d.ts +51 -0
- package/processing-store/index.js +44 -0
- package/publishing/index.js +157 -0
- package/queue-manager/index.js +39 -0
- package/server-client/index.d.ts +0 -9
- package/server-client/index.js +25 -134
- package/url-pattern/index.js +9 -3
package/context/index.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export class ConnectContext<Headers extends object = unknown> {
|
|
|
97
97
|
*
|
|
98
98
|
* @param action The action or the array of actions.
|
|
99
99
|
* @param meta Action’s meta.
|
|
100
|
-
* @returns Promise until action was
|
|
100
|
+
* @returns Promise until the action was sent to the client.
|
|
101
101
|
*/
|
|
102
102
|
sendBack<TypeAction extends Action = AnyAction>(
|
|
103
103
|
action:
|
|
@@ -141,6 +141,18 @@ export class Context<
|
|
|
141
141
|
*/
|
|
142
142
|
isServer: boolean
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Signal, which will be aborted when the action will be undone
|
|
146
|
+
* by `queueTimeout`.
|
|
147
|
+
*
|
|
148
|
+
* ```js
|
|
149
|
+
* process (ctx, action) {
|
|
150
|
+
* return fetch(url, { signal: ctx.signal })
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
signal: AbortSignal
|
|
155
|
+
|
|
144
156
|
constructor(server: Server, meta: ServerMeta)
|
|
145
157
|
}
|
|
146
158
|
|
package/context/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseId } from '@logux/core'
|
|
2
2
|
|
|
3
3
|
export class Context {
|
|
4
|
-
constructor(server, meta) {
|
|
4
|
+
constructor(server, meta, task) {
|
|
5
5
|
this.server = server
|
|
6
6
|
this.data = {}
|
|
7
7
|
|
|
@@ -31,6 +31,8 @@ export class Context {
|
|
|
31
31
|
} else {
|
|
32
32
|
this.headers = {}
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
if (task) this.signal = task.controller.signal
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
drain() {
|
|
@@ -38,7 +40,7 @@ export class Context {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
sendBack(actions, meta = {}) {
|
|
41
|
-
let common = { clients: [this.clientId],
|
|
43
|
+
let common = { clients: [this.clientId], ...meta }
|
|
42
44
|
if (Array.isArray(actions)) {
|
|
43
45
|
return this.server.log.add(
|
|
44
46
|
actions.map(item => {
|
package/create-reporter/index.js
CHANGED
|
@@ -79,6 +79,12 @@ const REPORTERS = {
|
|
|
79
79
|
|
|
80
80
|
destroy: () => ({ msg: 'Shutting down Logux server' }),
|
|
81
81
|
|
|
82
|
+
destroyDetached: () => ({
|
|
83
|
+
level: 'warn',
|
|
84
|
+
msg: 'Shutting down, but some actions that exceeded ' +
|
|
85
|
+
'queueTimeout will be killed'
|
|
86
|
+
}),
|
|
87
|
+
|
|
82
88
|
duplicate: () => ({ level: 'warn', msg: 'Action was already received' }),
|
|
83
89
|
|
|
84
90
|
disconnect: () => ({ msg: 'Client was disconnected' }),
|
package/filtered-node/index.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import { ServerNode } from '@logux/core'
|
|
1
|
+
import { idToTime, ServerNode } from '@logux/core'
|
|
2
|
+
|
|
3
|
+
import { Context } from '../context/index.js'
|
|
4
|
+
import { filterMeta } from '../filter-meta/index.js'
|
|
2
5
|
|
|
3
6
|
function has(array, item) {
|
|
4
7
|
return array && array.includes(item)
|
|
5
8
|
}
|
|
6
9
|
|
|
10
|
+
async function loadOnConnect(app, client, lastSynced) {
|
|
11
|
+
let entries = await app.connectLoader(new Context(app, client), lastSynced)
|
|
12
|
+
let added = 0
|
|
13
|
+
for (let entry of entries) {
|
|
14
|
+
// `added` should be taken before `filterMeta()` removes it
|
|
15
|
+
let entryAdded = entry[1].added
|
|
16
|
+
if (entryAdded > added) added = entryAdded
|
|
17
|
+
entry[1] = filterMeta(entry[1])
|
|
18
|
+
// The core needs `added` to set the sync position of every message
|
|
19
|
+
// and removes it before sending the action to the client
|
|
20
|
+
if (typeof entryAdded !== 'undefined') entry[1].added = entryAdded
|
|
21
|
+
}
|
|
22
|
+
return { added, entries }
|
|
23
|
+
}
|
|
24
|
+
|
|
7
25
|
export class FilteredNode extends ServerNode {
|
|
8
26
|
constructor(client, nodeId, log, connection, options) {
|
|
9
27
|
super(nodeId, log, connection, options)
|
|
@@ -24,4 +42,23 @@ export class FilteredNode extends ServerNode {
|
|
|
24
42
|
!has(meta.excludeClients, this.client.clientId)
|
|
25
43
|
)
|
|
26
44
|
}
|
|
45
|
+
|
|
46
|
+
async syncSinceQuery(lastSynced) {
|
|
47
|
+
let app = this.client.app
|
|
48
|
+
let data = app.connectLoader
|
|
49
|
+
? await loadOnConnect(app, this.client, lastSynced)
|
|
50
|
+
: await super.syncSinceQuery(lastSynced)
|
|
51
|
+
|
|
52
|
+
let actions = data.entries.length
|
|
53
|
+
if (actions > 0) {
|
|
54
|
+
let id = app.log.generateId()
|
|
55
|
+
// Entries are ordered from the newest to the oldest one,
|
|
56
|
+
// so the last entry will be sent to the client first
|
|
57
|
+
data.entries.push([
|
|
58
|
+
{ actions, type: 'logux/prepare' },
|
|
59
|
+
{ id, time: idToTime(id) }
|
|
60
|
+
])
|
|
61
|
+
}
|
|
62
|
+
return data
|
|
63
|
+
}
|
|
27
64
|
}
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { BaseServer, wasNot403 } from './base-server/index.js'
|
|
|
9
9
|
export { Context } from './context/index.js'
|
|
10
10
|
export { filterMeta } from './filter-meta/index.js'
|
|
11
11
|
export { PostgresStore } from './postgres-store/index.js'
|
|
12
|
+
export { ProcessingStore } from './processing-store/index.js'
|
|
12
13
|
export { ResponseError } from './request/index.js'
|
|
13
14
|
export { Server } from './server/index.js'
|
|
14
15
|
export { TestClient } from './test-client/index.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logux/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Build own Logux server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"collaborative",
|
|
@@ -24,13 +24,12 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@logux/actions": "^0.
|
|
28
|
-
"@logux/core": "^0.
|
|
27
|
+
"@logux/actions": "^0.8.0",
|
|
28
|
+
"@logux/core": "^0.12.0",
|
|
29
29
|
"cookie": "^2.0.1",
|
|
30
|
-
"fastq": "^1.20.3",
|
|
31
30
|
"nanoevents": "^10.0.0",
|
|
32
31
|
"nanoid": "^6.0.1",
|
|
33
|
-
"ws": "^8.
|
|
32
|
+
"ws": "^8.22.0"
|
|
34
33
|
},
|
|
35
34
|
"engines": {
|
|
36
35
|
"node": "^22.5.0 || ^24.0.0 || >=26.0.0"
|
|
@@ -108,7 +108,7 @@ export class PostgresStore<
|
|
|
108
108
|
opts?: PostgresStoreOptions<Packers>
|
|
109
109
|
)
|
|
110
110
|
|
|
111
|
-
add(
|
|
111
|
+
add(entries: [AnyAction, Meta][]): Promise<(false | Meta)[]>
|
|
112
112
|
|
|
113
113
|
addReason(reasons: string[], criteria: Criteria): Promise<void>
|
|
114
114
|
|
|
@@ -124,6 +124,8 @@ export class PostgresStore<
|
|
|
124
124
|
|
|
125
125
|
getLastSynced(): Promise<LastSynced>
|
|
126
126
|
|
|
127
|
+
has(ids: string[]): Promise<string[]>
|
|
128
|
+
|
|
127
129
|
/**
|
|
128
130
|
* Bring the log tables to the latest version, creating them if they were
|
|
129
131
|
* not created by the application’s migration.
|
package/postgres-store/index.js
CHANGED
|
@@ -76,6 +76,13 @@ function position(meta) {
|
|
|
76
76
|
return toSorted({ ...meta, time: meta.time ?? 0 })
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// A row of the `VALUES` list. Every cell keeps its own parameter with
|
|
80
|
+
// an explicit type, since the drivers send them all as text
|
|
81
|
+
function valuesSql(params, at, cells) {
|
|
82
|
+
let sql = cells.map(([type, value]) => `${params.add(value)}::${type}`)
|
|
83
|
+
return `(${at}, ${sql.join(', ')})`
|
|
84
|
+
}
|
|
85
|
+
|
|
79
86
|
function criteriaSql(criteria, params) {
|
|
80
87
|
let where = []
|
|
81
88
|
if (typeof criteria.id !== 'undefined') {
|
|
@@ -167,48 +174,76 @@ export class PostgresStore {
|
|
|
167
174
|
this.packers = { [zero.type]: zeroPacker, ...opts.packers }
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
async add(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let rows =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
async add(entries) {
|
|
178
|
+
if (entries.length === 0) return []
|
|
179
|
+
|
|
180
|
+
let params = new Params()
|
|
181
|
+
let rows = entries.map(([action, meta], i) => {
|
|
182
|
+
let packed = this.packers[action.type]?.pack(action)
|
|
183
|
+
let rest = { ...meta }
|
|
184
|
+
delete rest.added
|
|
185
|
+
// The place in the call sets the `added` order and keeps the first
|
|
186
|
+
// action among the duplicates of the same call
|
|
187
|
+
return valuesSql(params, i + 1, [
|
|
188
|
+
['text', meta.id],
|
|
189
|
+
['text', position(meta)],
|
|
190
|
+
['bigint', meta.time ?? 0],
|
|
191
|
+
['text', typeof action.type === 'string' ? action.type : ''],
|
|
192
|
+
['text[]', meta.reasons ?? []],
|
|
193
|
+
['text[]', meta.indexes ?? []],
|
|
194
|
+
['jsonb', toJson(rest)],
|
|
195
|
+
['jsonb', toJson(packed ? packed.action : action)],
|
|
196
|
+
['bytea', packed ? packed.blob : null]
|
|
197
|
+
])
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// The action with the same ID is ignored, so it must not spend
|
|
201
|
+
// the next `added` number
|
|
202
|
+
let added = await this.query(
|
|
203
|
+
`WITH "input" (
|
|
204
|
+
"at", "id", "sorted", "time", "type",
|
|
205
|
+
"reasons", "indexes", "meta", "action", "blob"
|
|
206
|
+
) AS (
|
|
207
|
+
VALUES ${rows.join(', ')}
|
|
208
|
+
), "fresh" AS (
|
|
209
|
+
SELECT DISTINCT ON ("id") * FROM "input"
|
|
210
|
+
WHERE NOT EXISTS (
|
|
211
|
+
SELECT 1 FROM "logux_log" WHERE "logux_log"."id" = "input"."id"
|
|
212
|
+
)
|
|
213
|
+
ORDER BY "id", "at"
|
|
214
|
+
), "numbered" AS (
|
|
215
|
+
SELECT *, row_number() OVER (ORDER BY "at") AS "offset" FROM "fresh"
|
|
179
216
|
), "next" AS (
|
|
180
|
-
UPDATE "logux_extra"
|
|
181
|
-
|
|
182
|
-
|
|
217
|
+
UPDATE "logux_extra"
|
|
218
|
+
SET "value" = "value" + (SELECT count(*) FROM "numbered")
|
|
219
|
+
WHERE "key" = 'added' AND EXISTS (SELECT 1 FROM "numbered")
|
|
220
|
+
RETURNING "value" - (SELECT count(*) FROM "numbered") AS "base"
|
|
183
221
|
)
|
|
184
222
|
INSERT INTO "logux_log" (
|
|
185
223
|
"added", "id", "sorted", "time", "type",
|
|
186
224
|
"reasons", "indexes", "meta", "action", "blob"
|
|
187
225
|
)
|
|
188
226
|
SELECT
|
|
189
|
-
"next"."
|
|
190
|
-
|
|
191
|
-
FROM "next"
|
|
227
|
+
"next"."base" + "numbered"."offset", "id", "sorted", "time", "type",
|
|
228
|
+
"reasons", "indexes", "meta", "action", "blob"
|
|
229
|
+
FROM "numbered", "next"
|
|
192
230
|
ON CONFLICT ("id") DO NOTHING
|
|
193
|
-
RETURNING "added"`,
|
|
194
|
-
|
|
195
|
-
meta.id,
|
|
196
|
-
position(meta),
|
|
197
|
-
meta.time ?? 0,
|
|
198
|
-
typeof action.type === 'string' ? action.type : '',
|
|
199
|
-
meta.reasons ?? [],
|
|
200
|
-
meta.indexes ?? [],
|
|
201
|
-
toJson(rest),
|
|
202
|
-
toJson(packed ? packed.action : action),
|
|
203
|
-
packed ? packed.blob : null
|
|
204
|
-
]
|
|
231
|
+
RETURNING "added", "id"`,
|
|
232
|
+
params.values
|
|
205
233
|
)
|
|
206
|
-
|
|
234
|
+
|
|
207
235
|
// Drivers can return `bigint` as a string not to lose the precision above
|
|
208
236
|
// `Number.MAX_SAFE_INTEGER`. Logux keeps `added` in `meta` as a number,
|
|
209
237
|
// so these columns can never go that high
|
|
210
|
-
|
|
211
|
-
return meta
|
|
238
|
+
let numbers = new Map(added.map(i => [i.id, Number(i.added)]))
|
|
239
|
+
return entries.map(([, meta]) => {
|
|
240
|
+
let number = numbers.get(meta.id)
|
|
241
|
+
if (typeof number === 'undefined') return false
|
|
242
|
+
// Only the first action with the ID was inserted
|
|
243
|
+
numbers.delete(meta.id)
|
|
244
|
+
meta.added = number
|
|
245
|
+
return meta
|
|
246
|
+
})
|
|
212
247
|
}
|
|
213
248
|
|
|
214
249
|
async addReason(reasons, criteria) {
|
|
@@ -320,6 +355,15 @@ export class PostgresStore {
|
|
|
320
355
|
return synced
|
|
321
356
|
}
|
|
322
357
|
|
|
358
|
+
async has(ids) {
|
|
359
|
+
if (ids.length === 0) return []
|
|
360
|
+
let rows = await this.query(
|
|
361
|
+
`SELECT "id" FROM "logux_log" WHERE "id" = ANY($1::text[])`,
|
|
362
|
+
[ids]
|
|
363
|
+
)
|
|
364
|
+
return rows.map(i => i.id)
|
|
365
|
+
}
|
|
366
|
+
|
|
323
367
|
async init() {
|
|
324
368
|
if (this.transaction) {
|
|
325
369
|
await this.transaction(query => this.migrate(query, true))
|