@rljson/server 0.0.8 → 0.0.10

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/README.public.md CHANGED
@@ -164,6 +164,9 @@ This is implemented with `IoMulti` and `BsMulti` internally, but the public API
164
164
  - `connector` – Connector wired to the route and socket (available when route was provided)
165
165
  - `route` – the Route passed to the constructor
166
166
  - `logger` – the `ServerLogger` instance (defaults to `noopLogger`)
167
+ - `isConnected` – whether the socket is currently connected (tracks `disconnect`/`connect` events)
168
+ - `onDisconnect(callback)` – registers a callback that fires when the socket disconnects (receives the reason string)
169
+ - `onReconnect(callback)` – registers a callback that fires when the socket reconnects after a previous disconnect
167
170
 
168
171
  ### Server API
169
172
 
@@ -216,6 +219,60 @@ The same pattern is used for Bs (blob storage).
216
219
  - Peer initialization is guarded by a configurable timeout (`peerInitTimeoutMs`, default 30 s) on both server and client. On the server it prevents `addSocket()` from hanging on unresponsive clients; on the client it prevents `init()` from hanging when the server is unreachable.
217
220
  - Logging is opt-in via `{ logger }` options. Use `ConsoleLogger` for development, `BufferedLogger` for testing, `FilteredLogger` for production. Default is `NoopLogger` (zero overhead).
218
221
 
222
+ ## Reconnect handling
223
+
224
+ The `Client` class tracks socket connection state and provides hooks for upper layers to react to disconnects and reconnections.
225
+
226
+ ### How it works
227
+
228
+ Socket.IO auto-reconnects at the transport level by default. When the connection drops and is restored:
229
+
230
+ 1. The client-side Socket.IO socket emits `'disconnect'` then (later) `'connect'`.
231
+ 2. The `Client` class listens for these events and updates `isConnected`.
232
+ 3. Registered `onDisconnect` / `onReconnect` callbacks fire.
233
+ 4. The server auto-detects the dropped socket (`removeSocket`) and re-registers the reconnected socket via a new `'connection'` event.
234
+ 5. The server sends a bootstrap ref to the reconnected client, triggering a re-sync via the Connector's bootstrap handler.
235
+
236
+ ### Usage
237
+
238
+ ```ts
239
+ import { Client } from '@rljson/server';
240
+
241
+ const client = new Client(socket, io, bs, route, {
242
+ logger: new ConsoleLogger(),
243
+ });
244
+ await client.init();
245
+
246
+ // Track connection state
247
+ console.log(client.isConnected); // true
248
+
249
+ // React to disconnects
250
+ client.onDisconnect((reason) => {
251
+ console.warn(`Disconnected: ${reason}`);
252
+ // Pause user-facing sync indicators, queue local changes, etc.
253
+ });
254
+
255
+ // React to reconnections
256
+ client.onReconnect(() => {
257
+ console.info('Reconnected to server');
258
+ // Resume sync, trigger re-fetch, update UI, etc.
259
+ });
260
+ ```
261
+
262
+ ### What survives a reconnect
263
+
264
+ | Layer | Survives? | Details |
265
+ | ----------------- | --------- | ----------------------------------------------------------------- |
266
+ | Socket.IO | ✅ | Auto-reconnects, reuses the same client-side socket object |
267
+ | SocketIoBridge | ✅ | Wraps the same socket — event listeners are preserved |
268
+ | IoPeer / BsPeer | ✅ | Listeners remain on the client socket; server re-creates its side |
269
+ | Connector | ✅ | Listeners remain; server bootstrap re-syncs state |
270
+ | IoMulti / BsMulti | ✅ | Multi layers are unaffected; local Io/Bs always available |
271
+
272
+ ### Cleanup
273
+
274
+ All connection handlers are cleaned up automatically on `tearDown()`. After tearDown, no callbacks will fire.
275
+
219
276
  ## Logging
220
277
 
221
278
  Both `Server` and `Client` support structured logging via an injectable `ServerLogger` interface. Logging is opt-in — by default a zero-overhead `NoopLogger` is used.
@@ -164,6 +164,9 @@ This is implemented with `IoMulti` and `BsMulti` internally, but the public API
164
164
  - `connector` – Connector wired to the route and socket (available when route was provided)
165
165
  - `route` – the Route passed to the constructor
166
166
  - `logger` – the `ServerLogger` instance (defaults to `noopLogger`)
167
+ - `isConnected` – whether the socket is currently connected (tracks `disconnect`/`connect` events)
168
+ - `onDisconnect(callback)` – registers a callback that fires when the socket disconnects (receives the reason string)
169
+ - `onReconnect(callback)` – registers a callback that fires when the socket reconnects after a previous disconnect
167
170
 
168
171
  ### Server API
169
172
 
@@ -216,6 +219,60 @@ The same pattern is used for Bs (blob storage).
216
219
  - Peer initialization is guarded by a configurable timeout (`peerInitTimeoutMs`, default 30 s) on both server and client. On the server it prevents `addSocket()` from hanging on unresponsive clients; on the client it prevents `init()` from hanging when the server is unreachable.
217
220
  - Logging is opt-in via `{ logger }` options. Use `ConsoleLogger` for development, `BufferedLogger` for testing, `FilteredLogger` for production. Default is `NoopLogger` (zero overhead).
218
221
 
222
+ ## Reconnect handling
223
+
224
+ The `Client` class tracks socket connection state and provides hooks for upper layers to react to disconnects and reconnections.
225
+
226
+ ### How it works
227
+
228
+ Socket.IO auto-reconnects at the transport level by default. When the connection drops and is restored:
229
+
230
+ 1. The client-side Socket.IO socket emits `'disconnect'` then (later) `'connect'`.
231
+ 2. The `Client` class listens for these events and updates `isConnected`.
232
+ 3. Registered `onDisconnect` / `onReconnect` callbacks fire.
233
+ 4. The server auto-detects the dropped socket (`removeSocket`) and re-registers the reconnected socket via a new `'connection'` event.
234
+ 5. The server sends a bootstrap ref to the reconnected client, triggering a re-sync via the Connector's bootstrap handler.
235
+
236
+ ### Usage
237
+
238
+ ```ts
239
+ import { Client } from '@rljson/server';
240
+
241
+ const client = new Client(socket, io, bs, route, {
242
+ logger: new ConsoleLogger(),
243
+ });
244
+ await client.init();
245
+
246
+ // Track connection state
247
+ console.log(client.isConnected); // true
248
+
249
+ // React to disconnects
250
+ client.onDisconnect((reason) => {
251
+ console.warn(`Disconnected: ${reason}`);
252
+ // Pause user-facing sync indicators, queue local changes, etc.
253
+ });
254
+
255
+ // React to reconnections
256
+ client.onReconnect(() => {
257
+ console.info('Reconnected to server');
258
+ // Resume sync, trigger re-fetch, update UI, etc.
259
+ });
260
+ ```
261
+
262
+ ### What survives a reconnect
263
+
264
+ | Layer | Survives? | Details |
265
+ | ----------------- | --------- | ----------------------------------------------------------------- |
266
+ | Socket.IO | ✅ | Auto-reconnects, reuses the same client-side socket object |
267
+ | SocketIoBridge | ✅ | Wraps the same socket — event listeners are preserved |
268
+ | IoPeer / BsPeer | ✅ | Listeners remain on the client socket; server re-creates its side |
269
+ | Connector | ✅ | Listeners remain; server bootstrap re-syncs state |
270
+ | IoMulti / BsMulti | ✅ | Multi layers are unaffected; local Io/Bs always available |
271
+
272
+ ### Cleanup
273
+
274
+ All connection handlers are cleaned up automatically on `tearDown()`. After tearDown, no callbacks will fire.
275
+
219
276
  ## Logging
220
277
 
221
278
  Both `Server` and `Client` support structured logging via an injectable `ServerLogger` interface. Logging is opt-in — by default a zero-overhead `NoopLogger` is used.
package/dist/client.d.ts CHANGED
@@ -45,6 +45,10 @@ export declare class Client extends BaseNode {
45
45
  private _syncConfig?;
46
46
  private _clientIdentity?;
47
47
  private _peerInitTimeoutMs;
48
+ private _isConnected;
49
+ private _disconnectCallbacks;
50
+ private _reconnectCallbacks;
51
+ private _connectionCleanup?;
48
52
  /**
49
53
  * Creates a Client instance
50
54
  * @param _socketToServer - Socket or namespace bundle to connect to server
@@ -91,11 +95,34 @@ export declare class Client extends BaseNode {
91
95
  * Returns the logger instance.
92
96
  */
93
97
  get logger(): ServerLogger;
98
+ /**
99
+ * Whether the client is currently connected to the server.
100
+ * Tracks socket-level connection state via `disconnect` and `connect` events.
101
+ */
102
+ get isConnected(): boolean;
103
+ /**
104
+ * Registers a callback that fires when the socket disconnects.
105
+ * The callback receives the disconnect reason string.
106
+ * @param callback - Invoked with the disconnect reason
107
+ */
108
+ onDisconnect(callback: (reason: string) => void): void;
109
+ /**
110
+ * Registers a callback that fires when the socket reconnects
111
+ * after a previous disconnect.
112
+ * @param callback - Invoked on reconnection
113
+ */
114
+ onReconnect(callback: () => void): void;
94
115
  /**
95
116
  * Creates Db and Connector from the route and IoMulti.
96
117
  * Called during init() when a route was provided.
97
118
  */
98
119
  private _setupDbAndConnector;
120
+ /**
121
+ * Registers socket-level disconnect/connect listeners.
122
+ * Logs state transitions and invokes registered callbacks.
123
+ * The `connect` callback only fires on RE-connections (not the initial connect).
124
+ */
125
+ private _registerConnectionHandlers;
99
126
  /**
100
127
  * Builds the Io multi with local and peer layers.
101
128
  */
@@ -0,0 +1,63 @@
1
+ import { ServerLogger } from './logger.ts';
2
+ /**
3
+ * Options for creating a FileLogger.
4
+ */
5
+ export interface FileLoggerOptions {
6
+ /**
7
+ * Absolute path to the log file.
8
+ * Parent directories are created automatically if they don't exist.
9
+ */
10
+ filePath: string;
11
+ /**
12
+ * When `true`, log entries are also written to console
13
+ * (info/traffic → console.log, warn → console.warn, error → console.error).
14
+ * Defaults to `false`.
15
+ */
16
+ echo?: boolean;
17
+ }
18
+ /**
19
+ * Appends log entries to a file. Optionally echoes to console.
20
+ *
21
+ * Each line is a self-contained JSON object for easy parsing:
22
+ * ```
23
+ * {"ts":"2026-02-25T12:00:00.000Z","level":"info","source":"Server","message":"initialized","data":{"port":3000}}
24
+ * ```
25
+ *
26
+ * Parent directories are created on construction if they don't exist.
27
+ * Uses synchronous writes (`appendFileSync`) to guarantee ordering
28
+ * and avoid lost entries on crash.
29
+ */
30
+ export declare class FileLogger implements ServerLogger {
31
+ private readonly _filePath;
32
+ private readonly _echo;
33
+ constructor(options: FileLoggerOptions);
34
+ /** The file path this logger writes to. */
35
+ get filePath(): string;
36
+ /** Whether console echo is enabled. */
37
+ get echo(): boolean;
38
+ info(source: string, message: string, data?: Record<string, unknown>): void;
39
+ warn(source: string, message: string, data?: Record<string, unknown>): void;
40
+ error(source: string, message: string, error?: unknown, data?: Record<string, unknown>): void;
41
+ traffic(direction: 'in' | 'out', source: string, event: string, data?: Record<string, unknown>): void;
42
+ /**
43
+ * Build a standard log entry object.
44
+ * @param level - Log level string
45
+ * @param source - Component identifier
46
+ * @param message - Human-readable message
47
+ * @param data - Optional structured context
48
+ */
49
+ private _entry;
50
+ /**
51
+ * Append a JSON line to the log file.
52
+ * @param entry - The log entry object to serialize
53
+ */
54
+ private _write;
55
+ /**
56
+ * Format a human-readable console line.
57
+ * @param level - Display level label (e.g. 'INFO')
58
+ * @param source - Component identifier
59
+ * @param message - Human-readable message
60
+ * @param data - Optional structured context
61
+ */
62
+ private _format;
63
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { Client } from './client.ts';
2
2
  export type { ClientOptions } from './client.ts';
3
+ export { FileLogger } from './file-logger.ts';
4
+ export type { FileLoggerOptions } from './file-logger.ts';
3
5
  export { BufferedLogger, ConsoleLogger, FilteredLogger, NoopLogger, noopLogger, } from './logger.ts';
4
6
  export type { LogEntry, ServerLogger } from './logger.ts';
5
7
  export { Server } from './server.ts';
package/dist/server.js CHANGED
@@ -2,6 +2,8 @@ import { hshBuffer } from "@rljson/hash";
2
2
  import { Readable } from "node:stream";
3
3
  import { Db, Connector } from "@rljson/db";
4
4
  import { IoPeerBridge, IoMulti, IoPeer, IoServer, IoMem, SocketMock } from "@rljson/io";
5
+ import { existsSync, mkdirSync, appendFileSync } from "node:fs";
6
+ import { dirname } from "node:path";
5
7
  import { syncEvents, Route } from "@rljson/rljson";
6
8
  import { syncEvents as syncEvents2 } from "@rljson/rljson";
7
9
  class BsMem {
@@ -1157,6 +1159,11 @@ class Client extends BaseNode {
1157
1159
  _syncConfig;
1158
1160
  _clientIdentity;
1159
1161
  _peerInitTimeoutMs;
1162
+ // Connection state
1163
+ _isConnected = true;
1164
+ _disconnectCallbacks = [];
1165
+ _reconnectCallbacks = [];
1166
+ _connectionCleanup;
1160
1167
  /**
1161
1168
  * Initializes Io and Bs multis and their peer bridges.
1162
1169
  * @returns The initialized Io implementation.
@@ -1169,6 +1176,7 @@ class Client extends BaseNode {
1169
1176
  if (this._route) {
1170
1177
  this._setupDbAndConnector();
1171
1178
  }
1179
+ this._registerConnectionHandlers();
1172
1180
  await this.ready();
1173
1181
  this._logger.info("Client", "Client initialized successfully", {
1174
1182
  hasRoute: !!this._route,
@@ -1194,6 +1202,12 @@ class Client extends BaseNode {
1194
1202
  */
1195
1203
  async tearDown() {
1196
1204
  this._logger.info("Client", "Tearing down client");
1205
+ if (this._connectionCleanup) {
1206
+ this._connectionCleanup();
1207
+ this._connectionCleanup = void 0;
1208
+ }
1209
+ this._disconnectCallbacks = [];
1210
+ this._reconnectCallbacks = [];
1197
1211
  if (this._ioMulti && this._ioMulti.isOpen) {
1198
1212
  this._ioMulti.close();
1199
1213
  }
@@ -1242,6 +1256,29 @@ class Client extends BaseNode {
1242
1256
  get logger() {
1243
1257
  return this._logger;
1244
1258
  }
1259
+ /**
1260
+ * Whether the client is currently connected to the server.
1261
+ * Tracks socket-level connection state via `disconnect` and `connect` events.
1262
+ */
1263
+ get isConnected() {
1264
+ return this._isConnected;
1265
+ }
1266
+ /**
1267
+ * Registers a callback that fires when the socket disconnects.
1268
+ * The callback receives the disconnect reason string.
1269
+ * @param callback - Invoked with the disconnect reason
1270
+ */
1271
+ onDisconnect(callback) {
1272
+ this._disconnectCallbacks.push(callback);
1273
+ }
1274
+ /**
1275
+ * Registers a callback that fires when the socket reconnects
1276
+ * after a previous disconnect.
1277
+ * @param callback - Invoked on reconnection
1278
+ */
1279
+ onReconnect(callback) {
1280
+ this._reconnectCallbacks.push(callback);
1281
+ }
1245
1282
  /**
1246
1283
  * Creates Db and Connector from the route and IoMulti.
1247
1284
  * Called during init() when a route was provided.
@@ -1261,6 +1298,44 @@ class Client extends BaseNode {
1261
1298
  );
1262
1299
  this._logger.info("Client", "Db and Connector created");
1263
1300
  }
1301
+ /**
1302
+ * Registers socket-level disconnect/connect listeners.
1303
+ * Logs state transitions and invokes registered callbacks.
1304
+ * The `connect` callback only fires on RE-connections (not the initial connect).
1305
+ */
1306
+ _registerConnectionHandlers() {
1307
+ const sockets = normalizeSocketBundle(this._socketToServer);
1308
+ const socket = sockets.ioUp;
1309
+ const disconnectHandler = (...args) => {
1310
+ const reason = typeof args[0] === "string" ? args[0] : "unknown";
1311
+ this._isConnected = false;
1312
+ this._logger.warn("Client", "Disconnected from server", { reason });
1313
+ for (const cb of this._disconnectCallbacks) {
1314
+ try {
1315
+ cb(reason);
1316
+ } catch {
1317
+ }
1318
+ }
1319
+ };
1320
+ const reconnectHandler = () => {
1321
+ if (!this._isConnected) {
1322
+ this._isConnected = true;
1323
+ this._logger.info("Client", "Reconnected to server");
1324
+ for (const cb of this._reconnectCallbacks) {
1325
+ try {
1326
+ cb();
1327
+ } catch {
1328
+ }
1329
+ }
1330
+ }
1331
+ };
1332
+ socket.on("disconnect", disconnectHandler);
1333
+ socket.on("connect", reconnectHandler);
1334
+ this._connectionCleanup = () => {
1335
+ socket.off("disconnect", disconnectHandler);
1336
+ socket.off("connect", reconnectHandler);
1337
+ };
1338
+ }
1264
1339
  /**
1265
1340
  * Builds the Io multi with local and peer layers.
1266
1341
  */
@@ -1399,6 +1474,116 @@ class Client extends BaseNode {
1399
1474
  );
1400
1475
  }
1401
1476
  }
1477
+ class FileLogger {
1478
+ _filePath;
1479
+ _echo;
1480
+ constructor(options) {
1481
+ this._filePath = options.filePath;
1482
+ this._echo = options.echo ?? false;
1483
+ const dir = dirname(this._filePath);
1484
+ if (!existsSync(dir)) {
1485
+ mkdirSync(dir, { recursive: true });
1486
+ }
1487
+ }
1488
+ /** The file path this logger writes to. */
1489
+ get filePath() {
1490
+ return this._filePath;
1491
+ }
1492
+ /** Whether console echo is enabled. */
1493
+ get echo() {
1494
+ return this._echo;
1495
+ }
1496
+ info(source, message, data) {
1497
+ const entry = this._entry("info", source, message, data);
1498
+ this._write(entry);
1499
+ if (this._echo) {
1500
+ console.log(this._format("INFO", source, message, data));
1501
+ }
1502
+ }
1503
+ warn(source, message, data) {
1504
+ const entry = this._entry("warn", source, message, data);
1505
+ this._write(entry);
1506
+ if (this._echo) {
1507
+ console.warn(this._format("WARN", source, message, data));
1508
+ }
1509
+ }
1510
+ error(source, message, error, data) {
1511
+ const entry = {
1512
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1513
+ level: "error",
1514
+ source,
1515
+ message
1516
+ };
1517
+ if (error !== void 0) {
1518
+ entry["error"] = error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error);
1519
+ }
1520
+ if (data !== void 0) {
1521
+ entry["data"] = data;
1522
+ }
1523
+ this._write(entry);
1524
+ if (this._echo) {
1525
+ const errStr = error ? ` ${error}` : "";
1526
+ const dataStr = data ? " " + JSON.stringify(data) : "";
1527
+ console.error(`[ERROR] [${source}] ${message}${errStr}${dataStr}`);
1528
+ }
1529
+ }
1530
+ traffic(direction, source, event, data) {
1531
+ const entry = {
1532
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1533
+ level: "traffic",
1534
+ direction,
1535
+ source,
1536
+ event
1537
+ };
1538
+ if (data !== void 0) {
1539
+ entry["data"] = data;
1540
+ }
1541
+ this._write(entry);
1542
+ if (this._echo) {
1543
+ const arrow = direction === "in" ? "⬅" : "➡";
1544
+ const dataStr = data ? " " + JSON.stringify(data) : "";
1545
+ console.log(`[TRAFFIC] ${arrow} [${source}] ${event}${dataStr}`);
1546
+ }
1547
+ }
1548
+ // ...........................................................................
1549
+ /**
1550
+ * Build a standard log entry object.
1551
+ * @param level - Log level string
1552
+ * @param source - Component identifier
1553
+ * @param message - Human-readable message
1554
+ * @param data - Optional structured context
1555
+ */
1556
+ _entry(level, source, message, data) {
1557
+ const entry = {
1558
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1559
+ level,
1560
+ source,
1561
+ message
1562
+ };
1563
+ if (data !== void 0) {
1564
+ entry["data"] = data;
1565
+ }
1566
+ return entry;
1567
+ }
1568
+ /**
1569
+ * Append a JSON line to the log file.
1570
+ * @param entry - The log entry object to serialize
1571
+ */
1572
+ _write(entry) {
1573
+ appendFileSync(this._filePath, JSON.stringify(entry) + "\n", "utf-8");
1574
+ }
1575
+ /**
1576
+ * Format a human-readable console line.
1577
+ * @param level - Display level label (e.g. 'INFO')
1578
+ * @param source - Component identifier
1579
+ * @param message - Human-readable message
1580
+ * @param data - Optional structured context
1581
+ */
1582
+ _format(level, source, message, data) {
1583
+ const dataStr = data ? " " + JSON.stringify(data) : "";
1584
+ return `[${level}] [${source}] ${message}${dataStr}`;
1585
+ }
1586
+ }
1402
1587
  class Server extends BaseNode {
1403
1588
  constructor(_route, _localIo, _localBs, options) {
1404
1589
  super(_localIo);
@@ -2157,6 +2342,7 @@ export {
2157
2342
  BufferedLogger,
2158
2343
  Client,
2159
2344
  ConsoleLogger,
2345
+ FileLogger,
2160
2346
  FilteredLogger,
2161
2347
  NoopLogger,
2162
2348
  Server,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/server",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Rljson server description",
5
5
  "homepage": "https://github.com/rljson/server",
6
6
  "bugs": "https://github.com/rljson/server/issues",
@@ -20,21 +20,21 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "devDependencies": {
23
- "@types/node": "^25.2.3",
24
- "@typescript-eslint/eslint-plugin": "^8.56.0",
25
- "@typescript-eslint/parser": "^8.56.0",
23
+ "@types/node": "^25.3.0",
24
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
25
+ "@typescript-eslint/parser": "^8.56.1",
26
26
  "@vitest/coverage-v8": "^4.0.18",
27
27
  "cross-env": "^10.1.0",
28
28
  "eslint": "~9.39.2",
29
- "eslint-plugin-jsdoc": "^62.5.5",
30
- "eslint-plugin-tsdoc": "^0.5.0",
29
+ "eslint-plugin-jsdoc": "^62.7.1",
30
+ "eslint-plugin-tsdoc": "^0.5.1",
31
31
  "globals": "^17.3.0",
32
32
  "jsdoc": "^4.0.5",
33
33
  "read-pkg": "^10.1.0",
34
34
  "socket.io": "^4.8.3",
35
35
  "socket.io-client": "^4.8.3",
36
36
  "typescript": "~5.9.3",
37
- "typescript-eslint": "^8.56.0",
37
+ "typescript-eslint": "^8.56.1",
38
38
  "vite": "^7.3.1",
39
39
  "vite-node": "^5.3.0",
40
40
  "vite-plugin-dts": "^4.5.4",
@@ -44,11 +44,11 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@rljson/bs": "^0.0.21",
47
- "@rljson/db": "^0.0.14",
47
+ "@rljson/db": "^0.0.15",
48
48
  "@rljson/hash": "^0.0.18",
49
49
  "@rljson/io": "^0.0.66",
50
50
  "@rljson/json": "^0.0.23",
51
- "@rljson/rljson": "^0.0.76"
51
+ "@rljson/rljson": "^0.0.78"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "pnpm exec vite build && tsc && node scripts/copy-readme-to-dist.js",