@dokebi/sse 0.1.0 → 0.4.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/package.json CHANGED
@@ -1,9 +1,16 @@
1
1
  {
2
2
  "name": "@dokebi/sse",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
+ "description": "Server-sent events for @dokebi/web: authenticated streams, rooms, cross-worker fan-out over @dokebi/event-bus, and a browser client",
5
+ "keywords": [
6
+ "dokebi",
7
+ "dokebi-plugin",
8
+ "web",
9
+ "sse",
10
+ "realtime"
11
+ ],
4
12
  "private": false,
5
13
  "type": "module",
6
- "description": "Server-sent events for @dokebi/web: authenticated streams, rooms, cross-worker fan-out over @dokebi/event-bus, and a browser client",
7
14
  "license": "MIT",
8
15
  "main": "./dist/index.js",
9
16
  "types": "./dist/index.d.ts",
@@ -37,14 +44,14 @@
37
44
  "node": ">=24.0.0"
38
45
  },
39
46
  "scripts": {
40
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && vite build -c vite.client.config.ts --logLevel warn",
47
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && vite build -c vite.client.config.ts --logLevel warn && node -e \"import('./dist/options.js').then(async (m) => { const { renderOptionsSchema } = await import('@dokebi/core'); process.stdout.write(renderOptionsSchema(m.options)); })\" > options.schema.json",
41
48
  "typecheck": "tsc -p tsconfig.test.json --noEmit",
42
- "test": "tsc -p tsconfig.test.json && node --experimental-eventsource --test \".tmp/tests/tests/*.test.js\""
49
+ "test": "node -e \"require('node:fs').rmSync('.tmp/tests',{recursive:true,force:true})\" && tsc -p tsconfig.test.json && node --experimental-eventsource --test \".tmp/tests/tests/*.test.js\""
43
50
  },
44
51
  "peerDependencies": {
45
- "@dokebi/core": "^0.2.0",
46
- "@dokebi/web": "^0.2.0",
47
- "@dokebi/event-bus": "^0.1.0",
52
+ "@dokebi/core": "^0.4.0",
53
+ "@dokebi/web": "^0.4.0",
54
+ "@dokebi/event-bus": "^0.4.0",
48
55
  "fastify": "^5.12.7",
49
56
  "react": ">=18",
50
57
  "vue": "^3.3.0"
@@ -61,12 +68,12 @@
61
68
  }
62
69
  },
63
70
  "devDependencies": {
64
- "@dokebi/cluster": "0.1.2",
65
- "@dokebi/config": "0.2.3",
66
- "@dokebi/event-bus": "0.1.2",
67
- "@dokebi/health": "0.1.2",
68
- "@dokebi/nats": "0.1.2",
69
- "@dokebi/web": "0.2.0",
71
+ "@dokebi/cluster": "0.4.0",
72
+ "@dokebi/config": "0.4.0",
73
+ "@dokebi/event-bus": "0.4.0",
74
+ "@dokebi/health": "0.4.0",
75
+ "@dokebi/nats": "0.4.0",
76
+ "@dokebi/web": "0.4.0",
70
77
  "react": "19.3.0",
71
78
  "react-test-renderer": "19.3.0",
72
79
  "vite": "8.3.4",
@@ -75,27 +82,42 @@
75
82
  "dokebi": {
76
83
  "schemaVersion": 2,
77
84
  "id": "dokebi-sse",
78
- "core": "^0.2.0",
85
+ "core": "^0.4.0",
79
86
  "dependencies": [
80
87
  {
81
88
  "name": "dokebi-web",
82
- "version": "^0.2.0"
89
+ "version": "^0.4.0"
83
90
  },
84
91
  {
85
92
  "name": "dokebi-event-bus",
86
- "version": "^0.1.0",
93
+ "version": "^0.4.0",
87
94
  "optional": true
88
95
  },
89
96
  {
90
97
  "name": "dokebi-health",
91
- "version": "^0.1.0",
98
+ "version": "^0.4.0",
92
99
  "optional": true
93
100
  }
94
101
  ],
102
+ "contributes": [
103
+ "dokebi-web:realtime"
104
+ ],
95
105
  "installModule": "dist/install.js",
96
106
  "optionsSchema": "options.schema.json",
97
107
  "documentation": "AI.md"
98
108
  },
109
+ "dokebiCatalog": {
110
+ "kind": "plugin",
111
+ "summary": "Server-sent events (SSE) streams and rooms, delivery across workers, and a browser client",
112
+ "maturity": "preview",
113
+ "provides": [
114
+ "realtime.sse"
115
+ ],
116
+ "doesNot": [
117
+ "No ACK, redelivery or retention (at most once)"
118
+ ],
119
+ "recipes": []
120
+ },
99
121
  "publishConfig": {
100
122
  "access": "public",
101
123
  "registry": "https://registry.npmjs.org/"
package/dist/log.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { LogFields, Logger, LogLevel } from '@dokebi/core';
2
- /**
3
- * The full text of a caught value for the `error` log field (D-0047): name, whole message and
4
- * stack, then each `cause` the same way. Only credentials in URLs (`//user:password@`) are
5
- * removed; a text over 16 KB is cut and marked.
6
- */
7
- export declare function errorText(error: unknown): string;
8
- /** A stable code of an error (hub, ws or errno code), or `fallback`. */
9
- export declare function errorCode(error: unknown, fallback: string): string;
10
- /**
11
- * Logs a repeating event once per interval (D-0047): the first occurrence at once, the following
12
- * ones of the same message and code (and `group`, when given) are counted and reported together
13
- * (`repeated`) when the interval ends. `group` must come from a bounded set (e.g. registered
14
- * handler types), never from client input, since each value keeps an entry for an interval. Hot paths (per message, per frame) can report every failure without a burst
15
- * flooding the log. Field and error text are built only for lines that are written.
16
- */
17
- export declare class LogThrottle {
18
- #private;
19
- readonly logger: Logger;
20
- readonly intervalMs: number;
21
- constructor(logger: Logger, intervalMs?: number);
22
- report(level: LogLevel, message: string, code: string, error?: unknown, fields?: LogFields, group?: string): void;
23
- /** Writes the counts still pending (on close), so a burst at shutdown is not lost. */
24
- flush(): void;
25
- }
package/dist/log.js DELETED
@@ -1,102 +0,0 @@
1
- const CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
2
- /**
3
- * The full text of a caught value for the `error` log field (D-0047): name, whole message and
4
- * stack, then each `cause` the same way. Only credentials in URLs (`//user:password@`) are
5
- * removed; a text over 16 KB is cut and marked.
6
- */
7
- export function errorText(error) {
8
- const parts = [];
9
- let current = error;
10
- for (let depth = 0; current !== undefined && depth < 5; depth++) {
11
- let text;
12
- if (current instanceof Error)
13
- text = current.stack ?? `${current.name}: ${current.message}`;
14
- else if (typeof current === 'string')
15
- text = current;
16
- else {
17
- try {
18
- text = `NonError: ${JSON.stringify(current)}`;
19
- }
20
- catch {
21
- text = `NonError: ${String(current)}`; // not serialisable (cycle, BigInt): its string form
22
- }
23
- }
24
- parts.push(depth ? `Caused by: ${text}` : text);
25
- current = current instanceof Error ? current.cause : undefined;
26
- }
27
- const text = parts.join('\n').replace(/\/\/[^\s/@]*@/g, '//***@');
28
- return text.length > 16_384
29
- ? `${text.slice(0, 16_384)}… [truncated ${text.length - 16_384} characters]`
30
- : text;
31
- }
32
- /** A stable code of an error (hub, ws or errno code), or `fallback`. */
33
- export function errorCode(error, fallback) {
34
- const code = error?.code;
35
- return typeof code === 'string' && CODE.test(code) ? code : fallback;
36
- }
37
- /**
38
- * Logs a repeating event once per interval (D-0047): the first occurrence at once, the following
39
- * ones of the same message and code (and `group`, when given) are counted and reported together
40
- * (`repeated`) when the interval ends. `group` must come from a bounded set (e.g. registered
41
- * handler types), never from client input, since each value keeps an entry for an interval. Hot paths (per message, per frame) can report every failure without a burst
42
- * flooding the log. Field and error text are built only for lines that are written.
43
- */
44
- export class LogThrottle {
45
- logger;
46
- intervalMs;
47
- #entries = new Map();
48
- constructor(logger, intervalMs = 10_000) {
49
- this.logger = logger;
50
- this.intervalMs = intervalMs;
51
- }
52
- report(level, message, code, error, fields, group) {
53
- if (!this.logger.isEnabled(level))
54
- return;
55
- const key = `${level}\u0000${code}\u0000${message}${group === undefined ? '' : `\u0000${group}`}`;
56
- const entry = this.#entries.get(key);
57
- if (entry) {
58
- entry.count++;
59
- entry.error = error;
60
- entry.fields = fields;
61
- return;
62
- }
63
- this.#write(level, message, code, error, fields, 0);
64
- const value = {
65
- count: 0,
66
- error: undefined,
67
- fields: undefined,
68
- timer: setTimeout(() => this.#elapsed(key, value, level, message, code), this.intervalMs),
69
- };
70
- value.timer.unref();
71
- this.#entries.set(key, value);
72
- }
73
- #elapsed(key, entry, level, message, code) {
74
- if (!entry.count) {
75
- this.#entries.delete(key);
76
- return;
77
- }
78
- this.#write(level, message, code, entry.error, entry.fields, entry.count);
79
- entry.count = 0;
80
- entry.timer = setTimeout(() => this.#elapsed(key, entry, level, message, code), this.intervalMs);
81
- entry.timer.unref();
82
- }
83
- #write(level, message, code, error, fields, repeated) {
84
- this.logger.log(level, message, {
85
- ...fields,
86
- code,
87
- ...(error === undefined ? {} : { error: errorText(error) }),
88
- ...(repeated ? { repeated, intervalMs: this.intervalMs } : {}),
89
- });
90
- }
91
- /** Writes the counts still pending (on close), so a burst at shutdown is not lost. */
92
- flush() {
93
- for (const [key, entry] of this.#entries) {
94
- clearTimeout(entry.timer);
95
- if (entry.count) {
96
- const [level, code, message] = key.split('\u0000');
97
- this.#write(level, message, code, entry.error, entry.fields, entry.count);
98
- }
99
- }
100
- this.#entries.clear();
101
- }
102
- }