svelte-adapter-uws-extensions 0.1.2
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 +21 -0
- package/README.md +697 -0
- package/package.json +99 -0
- package/postgres/index.d.ts +26 -0
- package/postgres/index.js +88 -0
- package/postgres/notify.d.ts +33 -0
- package/postgres/notify.js +214 -0
- package/postgres/replay.d.ts +56 -0
- package/postgres/replay.js +279 -0
- package/redis/cursor.d.ts +65 -0
- package/redis/cursor.js +365 -0
- package/redis/groups.d.ts +64 -0
- package/redis/groups.js +394 -0
- package/redis/index.d.ts +30 -0
- package/redis/index.js +100 -0
- package/redis/presence.d.ts +62 -0
- package/redis/presence.js +631 -0
- package/redis/pubsub.d.ts +30 -0
- package/redis/pubsub.js +153 -0
- package/redis/ratelimit.d.ts +39 -0
- package/redis/ratelimit.js +223 -0
- package/redis/replay.d.ts +47 -0
- package/redis/replay.js +156 -0
- package/shared/errors.js +33 -0
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "svelte-adapter-uws-extensions",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Redis and Postgres extensions for svelte-adapter-uws - distributed pub/sub, replay buffers, presence tracking, rate limiting, groups, and DB change notifications",
|
|
5
|
+
"author": "Kevin Radziszewski",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/lanteanio/svelte-adapter-uws-extensions.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/lanteanio/svelte-adapter-uws-extensions#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lanteanio/svelte-adapter-uws-extensions/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
"./redis": {
|
|
18
|
+
"types": "./redis/index.d.ts",
|
|
19
|
+
"default": "./redis/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./redis/pubsub": {
|
|
22
|
+
"types": "./redis/pubsub.d.ts",
|
|
23
|
+
"default": "./redis/pubsub.js"
|
|
24
|
+
},
|
|
25
|
+
"./redis/replay": {
|
|
26
|
+
"types": "./redis/replay.d.ts",
|
|
27
|
+
"default": "./redis/replay.js"
|
|
28
|
+
},
|
|
29
|
+
"./redis/presence": {
|
|
30
|
+
"types": "./redis/presence.d.ts",
|
|
31
|
+
"default": "./redis/presence.js"
|
|
32
|
+
},
|
|
33
|
+
"./postgres": {
|
|
34
|
+
"types": "./postgres/index.d.ts",
|
|
35
|
+
"default": "./postgres/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./postgres/replay": {
|
|
38
|
+
"types": "./postgres/replay.d.ts",
|
|
39
|
+
"default": "./postgres/replay.js"
|
|
40
|
+
},
|
|
41
|
+
"./postgres/notify": {
|
|
42
|
+
"types": "./postgres/notify.d.ts",
|
|
43
|
+
"default": "./postgres/notify.js"
|
|
44
|
+
},
|
|
45
|
+
"./redis/ratelimit": {
|
|
46
|
+
"types": "./redis/ratelimit.d.ts",
|
|
47
|
+
"default": "./redis/ratelimit.js"
|
|
48
|
+
},
|
|
49
|
+
"./redis/groups": {
|
|
50
|
+
"types": "./redis/groups.d.ts",
|
|
51
|
+
"default": "./redis/groups.js"
|
|
52
|
+
},
|
|
53
|
+
"./redis/cursor": {
|
|
54
|
+
"types": "./redis/cursor.d.ts",
|
|
55
|
+
"default": "./redis/cursor.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"redis",
|
|
60
|
+
"postgres",
|
|
61
|
+
"shared",
|
|
62
|
+
"LICENSE",
|
|
63
|
+
"README.md"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=20.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"svelte-adapter-uws": ">=0.2.0"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"ioredis": "^5.0.0"
|
|
77
|
+
},
|
|
78
|
+
"optionalDependencies": {
|
|
79
|
+
"pg": "^8.0.0"
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"vitest": "^4.0.18"
|
|
83
|
+
},
|
|
84
|
+
"keywords": [
|
|
85
|
+
"svelte",
|
|
86
|
+
"sveltekit",
|
|
87
|
+
"uwebsockets",
|
|
88
|
+
"redis",
|
|
89
|
+
"postgres",
|
|
90
|
+
"pubsub",
|
|
91
|
+
"websocket",
|
|
92
|
+
"presence",
|
|
93
|
+
"replay",
|
|
94
|
+
"ratelimit",
|
|
95
|
+
"groups",
|
|
96
|
+
"cursor",
|
|
97
|
+
"notify"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Pool, PoolConfig, QueryResult, Client } from 'pg';
|
|
2
|
+
|
|
3
|
+
export interface PgClientOptions {
|
|
4
|
+
/** Postgres connection string. Required. */
|
|
5
|
+
connectionString: string;
|
|
6
|
+
/** Listen for `sveltekit:shutdown` and disconnect. @default true */
|
|
7
|
+
autoShutdown?: boolean;
|
|
8
|
+
/** Extra pg Pool options. */
|
|
9
|
+
options?: PoolConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PgClient {
|
|
13
|
+
/** The underlying pg Pool. */
|
|
14
|
+
readonly pool: Pool;
|
|
15
|
+
/** Run a query. */
|
|
16
|
+
query(text: string, values?: any[]): Promise<QueryResult>;
|
|
17
|
+
/** Create a standalone pg.Client with the same connection config (not from the pool). */
|
|
18
|
+
createClient(): Client;
|
|
19
|
+
/** Gracefully close the pool. */
|
|
20
|
+
end(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a Postgres client with lifecycle management.
|
|
25
|
+
*/
|
|
26
|
+
export function createPgClient(options: PgClientOptions): PgClient;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres client factory for svelte-adapter-uws-extensions.
|
|
3
|
+
*
|
|
4
|
+
* Wraps pg Pool with lifecycle management and graceful shutdown
|
|
5
|
+
* via the SvelteKit `sveltekit:shutdown` event.
|
|
6
|
+
*
|
|
7
|
+
* @module svelte-adapter-uws-extensions/postgres
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import pg from 'pg';
|
|
11
|
+
import { ConnectionError } from '../shared/errors.js';
|
|
12
|
+
|
|
13
|
+
const { Pool, Client } = pg;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} PgClientOptions
|
|
17
|
+
* @property {string} connectionString - Postgres connection string
|
|
18
|
+
* @property {boolean} [autoShutdown=true] - Listen for `sveltekit:shutdown` and disconnect
|
|
19
|
+
* @property {import('pg').PoolConfig} [options] - Extra pg Pool options
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} PgClient
|
|
24
|
+
* @property {import('pg').Pool} pool - The underlying pg Pool
|
|
25
|
+
* @property {(text: string, values?: any[]) => Promise<import('pg').QueryResult>} query - Run a query
|
|
26
|
+
* @property {() => Promise<void>} end - Gracefully close the pool
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a Postgres client.
|
|
31
|
+
*
|
|
32
|
+
* @param {PgClientOptions} opts
|
|
33
|
+
* @returns {PgClient}
|
|
34
|
+
*/
|
|
35
|
+
export function createPgClient(opts) {
|
|
36
|
+
if (!opts || !opts.connectionString) {
|
|
37
|
+
throw new ConnectionError('postgres', 'connectionString is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const autoShutdown = opts.autoShutdown !== false;
|
|
41
|
+
|
|
42
|
+
let pool;
|
|
43
|
+
try {
|
|
44
|
+
pool = new Pool({
|
|
45
|
+
connectionString: opts.connectionString,
|
|
46
|
+
...(opts.options || {})
|
|
47
|
+
});
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new ConnectionError('postgres', 'failed to create pool', err);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pool.on('error', (err) => {
|
|
53
|
+
// Idle client errors should not crash the process.
|
|
54
|
+
// pg Pool handles reconnection automatically.
|
|
55
|
+
console.error('postgres: idle client error', err.message);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let ended = false;
|
|
59
|
+
|
|
60
|
+
async function end() {
|
|
61
|
+
if (ended) return;
|
|
62
|
+
ended = true;
|
|
63
|
+
await pool.end();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (autoShutdown && typeof process !== 'undefined') {
|
|
67
|
+
process.once('sveltekit:shutdown', end);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const connectionConfig = {
|
|
71
|
+
connectionString: opts.connectionString,
|
|
72
|
+
...(opts.options || {})
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
pool,
|
|
77
|
+
|
|
78
|
+
query(text, values) {
|
|
79
|
+
return pool.query(text, values);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
createClient() {
|
|
83
|
+
return new Client(connectionConfig);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Platform } from 'svelte-adapter-uws';
|
|
2
|
+
import type { PgClient } from './index.js';
|
|
3
|
+
|
|
4
|
+
export interface NotifyBridgeOptions {
|
|
5
|
+
/** Postgres LISTEN channel name. Required. */
|
|
6
|
+
channel: string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse the notification payload into a publish call.
|
|
10
|
+
* Return null to skip the notification.
|
|
11
|
+
* Defaults to JSON.parse expecting `{ topic, event, data }`.
|
|
12
|
+
*/
|
|
13
|
+
parse?: (payload: string, channel: string) => { topic: string; event: string; data?: unknown } | null;
|
|
14
|
+
|
|
15
|
+
/** Reconnect on connection loss. @default true */
|
|
16
|
+
autoReconnect?: boolean;
|
|
17
|
+
|
|
18
|
+
/** ms between reconnect attempts. @default 3000 */
|
|
19
|
+
reconnectInterval?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NotifyBridge {
|
|
23
|
+
/** Start listening. Forwards notifications to platform.publish(). Idempotent. */
|
|
24
|
+
activate(platform: Platform): Promise<void>;
|
|
25
|
+
|
|
26
|
+
/** Stop listening and release the connection. */
|
|
27
|
+
deactivate(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a Postgres LISTEN/NOTIFY bridge.
|
|
32
|
+
*/
|
|
33
|
+
export function createNotifyBridge(client: PgClient, options: NotifyBridgeOptions): NotifyBridge;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres LISTEN/NOTIFY bridge for svelte-adapter-uws.
|
|
3
|
+
*
|
|
4
|
+
* Listens on a Postgres channel for notifications and forwards them to
|
|
5
|
+
* platform.publish(). The user is responsible for setting up the trigger
|
|
6
|
+
* that calls pg_notify() -- this module only handles the listening side.
|
|
7
|
+
*
|
|
8
|
+
* Uses a dedicated connection (not from the pool) since LISTEN requires
|
|
9
|
+
* a persistent connection that cannot be shared.
|
|
10
|
+
*
|
|
11
|
+
* @module svelte-adapter-uws-extensions/postgres/notify
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} NotifyBridgeOptions
|
|
16
|
+
* @property {string} channel - Postgres LISTEN channel name (required)
|
|
17
|
+
* @property {(payload: string, channel: string) => { topic: string, event: string, data?: unknown } | null} [parse] -
|
|
18
|
+
* Parse the notification payload into a publish call. Return null to skip.
|
|
19
|
+
* Defaults to JSON.parse expecting { topic, event, data }.
|
|
20
|
+
* @property {boolean} [autoReconnect=true] - Reconnect on connection loss
|
|
21
|
+
* @property {number} [reconnectInterval=3000] - ms between reconnect attempts
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} NotifyBridge
|
|
26
|
+
* @property {(platform: import('svelte-adapter-uws').Platform) => Promise<void>} activate -
|
|
27
|
+
* Start listening. Forwards notifications to platform.publish().
|
|
28
|
+
* @property {() => Promise<void>} deactivate -
|
|
29
|
+
* Stop listening and release the connection.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a Postgres LISTEN/NOTIFY bridge.
|
|
34
|
+
*
|
|
35
|
+
* @param {import('./index.js').PgClient} client
|
|
36
|
+
* @param {NotifyBridgeOptions} options
|
|
37
|
+
* @returns {NotifyBridge}
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```js
|
|
41
|
+
* import { pg } from './pg.js';
|
|
42
|
+
* import { createNotifyBridge } from 'svelte-adapter-uws-extensions/postgres/notify';
|
|
43
|
+
*
|
|
44
|
+
* const bridge = createNotifyBridge(pg, {
|
|
45
|
+
* channel: 'table_changes',
|
|
46
|
+
* parse: (payload) => {
|
|
47
|
+
* const row = JSON.parse(payload);
|
|
48
|
+
* return { topic: row.table, event: row.op, data: row.data };
|
|
49
|
+
* }
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // In your open hook (once):
|
|
53
|
+
* bridge.activate(platform);
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* Then create a trigger on your table:
|
|
57
|
+
* ```sql
|
|
58
|
+
* CREATE OR REPLACE FUNCTION notify_table_change() RETURNS trigger AS $$
|
|
59
|
+
* BEGIN
|
|
60
|
+
* PERFORM pg_notify('table_changes', json_build_object(
|
|
61
|
+
* 'table', TG_TABLE_NAME,
|
|
62
|
+
* 'op', lower(TG_OP),
|
|
63
|
+
* 'data', CASE TG_OP
|
|
64
|
+
* WHEN 'DELETE' THEN row_to_json(OLD)
|
|
65
|
+
* ELSE row_to_json(NEW)
|
|
66
|
+
* END
|
|
67
|
+
* )::text);
|
|
68
|
+
* RETURN COALESCE(NEW, OLD);
|
|
69
|
+
* END;
|
|
70
|
+
* $$ LANGUAGE plpgsql;
|
|
71
|
+
*
|
|
72
|
+
* CREATE TRIGGER messages_notify
|
|
73
|
+
* AFTER INSERT OR UPDATE OR DELETE ON messages
|
|
74
|
+
* FOR EACH ROW EXECUTE FUNCTION notify_table_change();
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function createNotifyBridge(client, options) {
|
|
78
|
+
if (!options || typeof options.channel !== 'string' || options.channel.length === 0) {
|
|
79
|
+
throw new Error('notify bridge: channel must be a non-empty string');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const channel = options.channel;
|
|
83
|
+
const autoReconnect = options.autoReconnect !== false;
|
|
84
|
+
const reconnectInterval = options.reconnectInterval || 3000;
|
|
85
|
+
const parse = options.parse || defaultParse;
|
|
86
|
+
|
|
87
|
+
/** @type {import('pg').Client | null} */
|
|
88
|
+
let conn = null;
|
|
89
|
+
/** @type {import('svelte-adapter-uws').Platform | null} */
|
|
90
|
+
let activePlatform = null;
|
|
91
|
+
let active = false;
|
|
92
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
93
|
+
let reconnectTimer = null;
|
|
94
|
+
|
|
95
|
+
function defaultParse(payload) {
|
|
96
|
+
try {
|
|
97
|
+
const obj = JSON.parse(payload);
|
|
98
|
+
if (!obj.topic || !obj.event) return null;
|
|
99
|
+
return { topic: obj.topic, event: obj.event, data: obj.data };
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function onNotification(msg) {
|
|
106
|
+
if (msg.channel !== channel) return;
|
|
107
|
+
try {
|
|
108
|
+
const result = parse(msg.payload, msg.channel);
|
|
109
|
+
if (result && activePlatform) {
|
|
110
|
+
// relay: false -- in clustered mode each worker has its own
|
|
111
|
+
// LISTEN connection, so relaying would duplicate delivery.
|
|
112
|
+
activePlatform.publish(result.topic, result.event, result.data, { relay: false });
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Parse errors are non-fatal -- skip the notification
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function connect() {
|
|
120
|
+
try {
|
|
121
|
+
// Use a standalone Client instead of pool.connect() to avoid
|
|
122
|
+
// permanently holding a pool connection. LISTEN needs a persistent
|
|
123
|
+
// connection that stays open for the lifetime of the bridge --
|
|
124
|
+
// borrowing from the pool would reduce available connections for queries.
|
|
125
|
+
conn = client.createClient();
|
|
126
|
+
conn.on('notification', onNotification);
|
|
127
|
+
conn.on('error', handleError);
|
|
128
|
+
await conn.connect();
|
|
129
|
+
|
|
130
|
+
// pg requires channel name to be a valid identifier or quoted
|
|
131
|
+
// Use double-quoting to handle any channel name safely
|
|
132
|
+
await conn.query(`LISTEN "${channel.replace(/"/g, '""')}"`);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (conn) {
|
|
135
|
+
try { await conn.end(); } catch { /* ignore */ }
|
|
136
|
+
}
|
|
137
|
+
conn = null;
|
|
138
|
+
if (active && autoReconnect) {
|
|
139
|
+
scheduleReconnect();
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleError() {
|
|
146
|
+
cleanup();
|
|
147
|
+
if (active && autoReconnect) {
|
|
148
|
+
scheduleReconnect();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function scheduleReconnect() {
|
|
153
|
+
if (reconnectTimer) return;
|
|
154
|
+
reconnectTimer = setTimeout(async () => {
|
|
155
|
+
reconnectTimer = null;
|
|
156
|
+
if (!active) return;
|
|
157
|
+
try {
|
|
158
|
+
await connect();
|
|
159
|
+
} catch {
|
|
160
|
+
// connect() already schedules another retry on failure
|
|
161
|
+
}
|
|
162
|
+
}, reconnectInterval);
|
|
163
|
+
if (reconnectTimer.unref) reconnectTimer.unref();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cleanup() {
|
|
167
|
+
if (conn) {
|
|
168
|
+
conn.removeListener('notification', onNotification);
|
|
169
|
+
conn.removeListener('error', handleError);
|
|
170
|
+
conn.end().catch(() => { /* already closed */ });
|
|
171
|
+
conn = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
async activate(platform) {
|
|
177
|
+
// Always update the platform reference so notifications
|
|
178
|
+
// are forwarded through the latest platform, even if a
|
|
179
|
+
// previous activate() already started the listener.
|
|
180
|
+
activePlatform = platform;
|
|
181
|
+
if (active) return;
|
|
182
|
+
active = true;
|
|
183
|
+
try {
|
|
184
|
+
await connect();
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (!autoReconnect) {
|
|
187
|
+
// Without autoReconnect, reset so activate() can be retried
|
|
188
|
+
active = false;
|
|
189
|
+
activePlatform = null;
|
|
190
|
+
}
|
|
191
|
+
// With autoReconnect, connect() already scheduled a retry
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async deactivate() {
|
|
197
|
+
if (!active) return;
|
|
198
|
+
active = false;
|
|
199
|
+
activePlatform = null;
|
|
200
|
+
if (reconnectTimer) {
|
|
201
|
+
clearTimeout(reconnectTimer);
|
|
202
|
+
reconnectTimer = null;
|
|
203
|
+
}
|
|
204
|
+
if (conn) {
|
|
205
|
+
try {
|
|
206
|
+
await conn.query(`UNLISTEN "${channel.replace(/"/g, '""')}"`);
|
|
207
|
+
} catch {
|
|
208
|
+
// Connection may already be dead
|
|
209
|
+
}
|
|
210
|
+
cleanup();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Platform } from 'svelte-adapter-uws';
|
|
2
|
+
import type { PgClient } from './index.js';
|
|
3
|
+
|
|
4
|
+
export interface PgReplayOptions {
|
|
5
|
+
/** Table name. @default 'ws_replay' */
|
|
6
|
+
table?: string;
|
|
7
|
+
/** Max messages per topic. @default 1000 */
|
|
8
|
+
size?: number;
|
|
9
|
+
/** TTL in seconds (0 = no expiry). @default 0 */
|
|
10
|
+
ttl?: number;
|
|
11
|
+
/** Auto-create table on first use. @default true */
|
|
12
|
+
autoMigrate?: boolean;
|
|
13
|
+
/** Cleanup interval in ms (0 to disable). @default 60000 */
|
|
14
|
+
cleanupInterval?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BufferedMessage {
|
|
18
|
+
seq: number;
|
|
19
|
+
topic: string;
|
|
20
|
+
event: string;
|
|
21
|
+
data: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PgReplayBuffer {
|
|
25
|
+
/**
|
|
26
|
+
* Publish a message through the buffer. Stores it in Postgres with a
|
|
27
|
+
* sequence number, then calls platform.publish() as normal.
|
|
28
|
+
*/
|
|
29
|
+
publish(platform: Platform, topic: string, event: string, data?: unknown): Promise<boolean>;
|
|
30
|
+
|
|
31
|
+
/** Get the current sequence number for a topic. Returns 0 if unknown. */
|
|
32
|
+
seq(topic: string): Promise<number>;
|
|
33
|
+
|
|
34
|
+
/** Get all buffered messages after a given sequence number. */
|
|
35
|
+
since(topic: string, since: number): Promise<BufferedMessage[]>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Send buffered messages to a single connection. Sends each missed
|
|
39
|
+
* message on `__replay:{topic}`, then an end marker.
|
|
40
|
+
*/
|
|
41
|
+
replay(ws: any, topic: string, sinceSeq: number, platform: Platform): Promise<void>;
|
|
42
|
+
|
|
43
|
+
/** Clear all replay data. */
|
|
44
|
+
clear(): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/** Clear replay data for a single topic. */
|
|
47
|
+
clearTopic(topic: string): Promise<void>;
|
|
48
|
+
|
|
49
|
+
/** Stop the cleanup timer. */
|
|
50
|
+
destroy(): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a Postgres-backed replay buffer.
|
|
55
|
+
*/
|
|
56
|
+
export function createReplay(client: PgClient, options?: PgReplayOptions): PgReplayBuffer;
|