@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/human-formatter/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import os from 'node:os'
|
|
2
2
|
import { stripVTControlCharacters, styleText } from 'node:util'
|
|
3
3
|
|
|
4
|
-
import { mulberry32, onceXmur3 } from './utils.js'
|
|
4
|
+
import { isBinary, mulberry32, onceXmur3 } from './utils.js'
|
|
5
5
|
|
|
6
|
+
const BINARY_MARK = '[binary]'
|
|
6
7
|
const INDENT = ' '
|
|
7
8
|
const PADDING = ' '
|
|
8
9
|
const SEPARATOR = os.EOL + os.EOL
|
|
@@ -119,7 +120,9 @@ function formatNodeId(nodeId) {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
function formatValue(value) {
|
|
122
|
-
if (
|
|
123
|
+
if (isBinary(value)) {
|
|
124
|
+
return styleText('bold', BINARY_MARK)
|
|
125
|
+
} else if (typeof value === 'string') {
|
|
123
126
|
return '"' + styleText('bold', value) + '"'
|
|
124
127
|
} else if (Array.isArray(value)) {
|
|
125
128
|
return formatArray(value)
|
|
@@ -145,10 +148,7 @@ function formatActionId(id) {
|
|
|
145
148
|
if (p.length === 1) {
|
|
146
149
|
return p
|
|
147
150
|
}
|
|
148
|
-
return (
|
|
149
|
-
`${styleText('bold', splitAndColorize(4, p[0]))} ` +
|
|
150
|
-
`${formatNodeId(p[1])} ${styleText('bold', p[2])}`
|
|
151
|
-
)
|
|
151
|
+
return `${styleText('bold', splitAndColorize(4, p[0]))} ${formatNodeId(p[1])}`
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
function formatParams(params, parent) {
|
|
@@ -164,7 +164,12 @@ function formatParams(params, parent) {
|
|
|
164
164
|
|
|
165
165
|
let start = PADDING + rightPag(`${name}: `, maxName + 2)
|
|
166
166
|
|
|
167
|
-
if (
|
|
167
|
+
if (isBinary(value)) {
|
|
168
|
+
return start + styleText('bold', BINARY_MARK)
|
|
169
|
+
} else if (
|
|
170
|
+
name === 'Node ID' ||
|
|
171
|
+
(parent === 'Meta' && name === 'server')
|
|
172
|
+
) {
|
|
168
173
|
return start + formatNodeId(value)
|
|
169
174
|
} else if (
|
|
170
175
|
parent === 'Meta' &&
|
package/human-formatter/utils.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export const BINARY = {
|
|
2
|
+
toJSON() {
|
|
3
|
+
return '[binary]'
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isBinary(value) {
|
|
8
|
+
return (
|
|
9
|
+
value === BINARY ||
|
|
10
|
+
ArrayBuffer.isView(value) ||
|
|
11
|
+
value instanceof ArrayBuffer
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
1
15
|
export function onceXmur3(str) {
|
|
2
16
|
let h = 1779033703 ^ str.length
|
|
3
17
|
for (let i = 0; i < str.length; i++) {
|
package/index.d.ts
CHANGED
|
@@ -16,8 +16,15 @@ export {
|
|
|
16
16
|
ServerMeta,
|
|
17
17
|
wasNot403
|
|
18
18
|
} from './base-server/index.js'
|
|
19
|
-
export {
|
|
19
|
+
export { Context, ConnectContext, ChannelContext } from './context/index.js'
|
|
20
20
|
export { filterMeta } from './filter-meta/index.js'
|
|
21
|
+
export {
|
|
22
|
+
PostgresDriver,
|
|
23
|
+
PostgresQuery,
|
|
24
|
+
PostgresRows,
|
|
25
|
+
PostgresStore,
|
|
26
|
+
PostgresStoreOptions
|
|
27
|
+
} from './postgres-store/index.js'
|
|
21
28
|
export {
|
|
22
29
|
del,
|
|
23
30
|
get,
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { ALLOWED_META } from './allowed-meta/index.js'
|
|
|
8
8
|
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
|
+
export { PostgresStore } from './postgres-store/index.js'
|
|
11
12
|
export { ResponseError } from './request/index.js'
|
|
12
13
|
export { Server } from './server/index.js'
|
|
13
14
|
export { TestClient } from './test-client/index.js'
|
package/package.json
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logux/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Build own Logux server",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"server",
|
|
8
|
-
"framework",
|
|
9
|
-
"websocket",
|
|
10
|
-
"proxy",
|
|
6
|
+
"collaborative",
|
|
11
7
|
"crdt",
|
|
12
|
-
"event sourcing",
|
|
13
8
|
"distributed systems",
|
|
14
|
-
"
|
|
9
|
+
"event sourcing",
|
|
10
|
+
"framework",
|
|
11
|
+
"logux",
|
|
12
|
+
"proxy",
|
|
13
|
+
"server",
|
|
14
|
+
"websocket"
|
|
15
15
|
],
|
|
16
|
-
"author": "Andrey Sitnik <andrey@sitnik.ru>",
|
|
17
|
-
"license": "MIT",
|
|
18
16
|
"homepage": "https://logux.org/",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Andrey Sitnik <andrey@sitnik.es>",
|
|
19
19
|
"repository": "logux/server",
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": "^20.0.0 || >=22.0.0"
|
|
22
|
-
},
|
|
23
20
|
"type": "module",
|
|
24
21
|
"types": "./index.d.ts",
|
|
25
22
|
"exports": {
|
|
@@ -27,14 +24,15 @@
|
|
|
27
24
|
"./package.json": "./package.json"
|
|
28
25
|
},
|
|
29
26
|
"dependencies": {
|
|
30
|
-
"@logux/actions": "^0.
|
|
31
|
-
"@logux/core": "^0.
|
|
32
|
-
"cookie": "^
|
|
33
|
-
"fastq": "^1.
|
|
34
|
-
"nanoevents": "^
|
|
35
|
-
"nanoid": "^
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
"@logux/actions": "^0.7.2",
|
|
28
|
+
"@logux/core": "^0.11.6",
|
|
29
|
+
"cookie": "^2.0.1",
|
|
30
|
+
"fastq": "^1.20.1",
|
|
31
|
+
"nanoevents": "^10.0.0",
|
|
32
|
+
"nanoid": "^6.0.1",
|
|
33
|
+
"ws": "^8.21.3"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": "^22.5.0 || ^24.0.0 || >=26.0.0"
|
|
39
37
|
}
|
|
40
38
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { ActionPackerMap } from '@logux/actions'
|
|
2
|
+
import type {
|
|
3
|
+
Action,
|
|
4
|
+
AnyAction,
|
|
5
|
+
Criteria,
|
|
6
|
+
LogPage,
|
|
7
|
+
LogStore,
|
|
8
|
+
Meta
|
|
9
|
+
} from '@logux/core'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Logux declares these two, but does not export them, so they are taken
|
|
13
|
+
* from the methods not to repeat the definition here.
|
|
14
|
+
*/
|
|
15
|
+
type GetOptions = NonNullable<Parameters<LogStore['get']>[0]>
|
|
16
|
+
type LastSynced = Awaited<ReturnType<LogStore['getLastSynced']>>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Rows of the query. `pg` and PGlite keep them in the result object,
|
|
20
|
+
* `postgres` returns them as an array.
|
|
21
|
+
*/
|
|
22
|
+
export type PostgresRows =
|
|
23
|
+
| { rows: Record<string, unknown>[] }
|
|
24
|
+
| Record<string, unknown>[]
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Minimal database interface for the drivers, which the store does not
|
|
28
|
+
* support out of the box.
|
|
29
|
+
*
|
|
30
|
+
* ```js
|
|
31
|
+
* let query = (sql, params) => driver.run(sql, params)
|
|
32
|
+
* query.transaction = body => {
|
|
33
|
+
* return driver.tx(tx => body((sql, params) => tx.run(sql, params)))
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* let store = new PostgresStore(query)
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export interface PostgresQuery {
|
|
40
|
+
(sql: string, params: unknown[]): Promise<PostgresRows>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run the callback on a single connection inside a transaction.
|
|
44
|
+
*
|
|
45
|
+
* Without it `init()` can not lock the migrations, so two servers
|
|
46
|
+
* starting at the same moment can apply them twice.
|
|
47
|
+
*/
|
|
48
|
+
transaction?: (body: (query: PostgresQuery) => Promise<void>) => Promise<void>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Database driver, which the store can use without any wrapper:
|
|
53
|
+
* `pg`’s `Pool` or `Client`, PGlite, or `postgres`.
|
|
54
|
+
*
|
|
55
|
+
* The parameters are `never[]`, so that the driver’s own stricter types
|
|
56
|
+
* for them still match this type.
|
|
57
|
+
*/
|
|
58
|
+
export type PostgresDriver =
|
|
59
|
+
| {
|
|
60
|
+
begin?(body: (tx: PostgresDriver) => Promise<unknown>): Promise<unknown>
|
|
61
|
+
unsafe(sql: string, params: never[]): Promise<unknown>
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
connect?(): Promise<unknown>
|
|
65
|
+
query(sql: string, params: never[]): Promise<unknown>
|
|
66
|
+
transaction?(
|
|
67
|
+
body: (tx: PostgresDriver) => Promise<unknown>
|
|
68
|
+
): Promise<unknown>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PostgresStoreOptions<Packers = Record<string, never>> {
|
|
72
|
+
/**
|
|
73
|
+
* How many entries to load by a single query in `get()`.
|
|
74
|
+
*/
|
|
75
|
+
pageSize?: number
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Packers to keep the binary parts of the action in the `blob` column
|
|
79
|
+
* instead of Base64 inside JSON. The key must be the `type` of the action.
|
|
80
|
+
*
|
|
81
|
+
* `zeroPacker` is always added, so this is only for custom actions.
|
|
82
|
+
*/
|
|
83
|
+
packers?: Packers
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Log store, which keeps actions in PostgreSQL.
|
|
88
|
+
*
|
|
89
|
+
* It takes the database of `pg`, `postgres`, or PGlite. For any other driver,
|
|
90
|
+
* pass the function to send the query, see {@link PostgresQuery}.
|
|
91
|
+
*
|
|
92
|
+
* ```js
|
|
93
|
+
* import { PostgresStore, Server } from '@logux/server'
|
|
94
|
+
* import { Pool } from 'pg'
|
|
95
|
+
*
|
|
96
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
97
|
+
* const store = new PostgresStore(pool)
|
|
98
|
+
* await store.init()
|
|
99
|
+
*
|
|
100
|
+
* const server = new Server(Server.loadOptions(process, {
|
|
101
|
+
* minSubprotocol: 1,
|
|
102
|
+
* subprotocol: 1,
|
|
103
|
+
* root: import.meta.dirname,
|
|
104
|
+
* store
|
|
105
|
+
* }))
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export class PostgresStore<
|
|
109
|
+
Packers extends ActionPackerMap<Packers> = Record<string, never>
|
|
110
|
+
>
|
|
111
|
+
implements LogStore
|
|
112
|
+
{
|
|
113
|
+
constructor(
|
|
114
|
+
db: PostgresDriver | PostgresQuery,
|
|
115
|
+
opts?: PostgresStoreOptions<Packers>
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
add(action: AnyAction, meta: Meta): Promise<false | Meta>
|
|
119
|
+
|
|
120
|
+
addReason(reasons: string[], criteria: Criteria): Promise<void>
|
|
121
|
+
|
|
122
|
+
byId(id: string): Promise<[Action, Meta] | [null, null]>
|
|
123
|
+
|
|
124
|
+
changeMeta(id: string, diff: Partial<Meta>): Promise<boolean>
|
|
125
|
+
|
|
126
|
+
clean(): Promise<void>
|
|
127
|
+
|
|
128
|
+
get(opts?: GetOptions): Promise<LogPage>
|
|
129
|
+
|
|
130
|
+
getLastAdded(): Promise<number>
|
|
131
|
+
|
|
132
|
+
getLastSynced(): Promise<LastSynced>
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Bring the log tables to the latest version, creating them if they were
|
|
136
|
+
* not created by the application’s migration.
|
|
137
|
+
*/
|
|
138
|
+
init(): Promise<void>
|
|
139
|
+
|
|
140
|
+
remove(id: string): Promise<[Action, Meta] | false>
|
|
141
|
+
|
|
142
|
+
removeReason(
|
|
143
|
+
reasons: string[],
|
|
144
|
+
criteria: Criteria,
|
|
145
|
+
callback: (action: Action, meta: Meta) => void
|
|
146
|
+
): Promise<void>
|
|
147
|
+
|
|
148
|
+
setLastSynced(values: Partial<LastSynced>): Promise<void>
|
|
149
|
+
}
|