agent-relay 1.3.0 → 1.3.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/.trajectories/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +19 -1
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +115 -69
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +47 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +98 -15
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +55 -111
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +36 -0
- package/dist/wrapper/base-wrapper.js +48 -2
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +84 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +7 -1
- package/dist/wrapper/pty-wrapper.js +51 -27
- package/dist/wrapper/tmux-wrapper.d.ts +12 -1
- package/dist/wrapper/tmux-wrapper.js +65 -17
- package/package.json +5 -5
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/404.html +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_buildManifest.js +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +0 -2
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +0 -9
- package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/history/page-8c8bed33beb2bf1c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16f3b49e55b1e0ed.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-4a5938c18a11a654.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-547dd0ca55ecd0ba.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +0 -18
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-2ee6beb2ae96d210.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +0 -1
- package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +0 -1
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +0 -1
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +0 -45
- package/dist/dashboard/out/alt-logos/logo.svg +0 -38
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +0 -38
- package/dist/dashboard/out/app/onboarding.html +0 -1
- package/dist/dashboard/out/app/onboarding.txt +0 -7
- package/dist/dashboard/out/app.html +0 -1
- package/dist/dashboard/out/app.txt +0 -7
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/connect-repos.html +0 -1
- package/dist/dashboard/out/connect-repos.txt +0 -7
- package/dist/dashboard/out/history.html +0 -1
- package/dist/dashboard/out/history.txt +0 -7
- package/dist/dashboard/out/index.html +0 -1
- package/dist/dashboard/out/index.txt +0 -7
- package/dist/dashboard/out/login.html +0 -6
- package/dist/dashboard/out/login.txt +0 -7
- package/dist/dashboard/out/metrics.html +0 -1
- package/dist/dashboard/out/metrics.txt +0 -7
- package/dist/dashboard/out/pricing.html +0 -13
- package/dist/dashboard/out/pricing.txt +0 -7
- package/dist/dashboard/out/providers/setup/claude.html +0 -1
- package/dist/dashboard/out/providers/setup/claude.txt +0 -8
- package/dist/dashboard/out/providers/setup/codex.html +0 -1
- package/dist/dashboard/out/providers/setup/codex.txt +0 -8
- package/dist/dashboard/out/providers.html +0 -1
- package/dist/dashboard/out/providers.txt +0 -7
- package/dist/dashboard/out/signup.html +0 -6
- package/dist/dashboard/out/signup.txt +0 -7
- package/dist/dashboard-server/metrics.d.ts +0 -105
- package/dist/dashboard-server/metrics.js +0 -193
- package/dist/dashboard-server/needs-attention.d.ts +0 -24
- package/dist/dashboard-server/needs-attention.js +0 -78
- package/dist/dashboard-server/server.d.ts +0 -15
- package/dist/dashboard-server/server.js +0 -3776
- package/dist/dashboard-server/start.d.ts +0 -6
- package/dist/dashboard-server/start.js +0 -13
- package/dist/dashboard-server/user-bridge.d.ts +0 -103
- package/dist/dashboard-server/user-bridge.js +0 -189
|
@@ -1,32 +1,94 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Frame encoding/decoding for the agent relay protocol.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Wire format:
|
|
5
|
+
* - 1 byte: format indicator (0 = JSON, 1 = MessagePack)
|
|
6
|
+
* - 4 bytes: big-endian payload length
|
|
7
|
+
* - N bytes: payload (JSON or MessagePack encoded)
|
|
8
|
+
*
|
|
9
|
+
* Optimizations:
|
|
10
|
+
* - Ring buffer to avoid Buffer.concat allocations
|
|
11
|
+
* - MessagePack support for faster serialization (optional)
|
|
12
|
+
* - Zero-copy frame extraction where possible
|
|
4
13
|
*/
|
|
5
14
|
import type { Envelope } from './types.js';
|
|
6
15
|
export declare const MAX_FRAME_BYTES: number;
|
|
7
|
-
export declare const HEADER_SIZE =
|
|
16
|
+
export declare const HEADER_SIZE = 5;
|
|
17
|
+
export declare const LEGACY_HEADER_SIZE = 4;
|
|
18
|
+
export type WireFormat = 'json' | 'msgpack';
|
|
19
|
+
/**
|
|
20
|
+
* Initialize MessagePack support. Call this at startup if you want msgpack.
|
|
21
|
+
* Install @msgpack/msgpack to enable: npm install @msgpack/msgpack
|
|
22
|
+
*/
|
|
23
|
+
export declare function initMessagePack(): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if MessagePack is available.
|
|
26
|
+
*/
|
|
27
|
+
export declare function hasMessagePack(): boolean;
|
|
8
28
|
/**
|
|
9
29
|
* Encode a message envelope into a framed buffer.
|
|
30
|
+
*
|
|
31
|
+
* @param envelope - The envelope to encode
|
|
32
|
+
* @param format - Wire format to use (default: 'json')
|
|
33
|
+
* @returns Framed buffer ready for socket write
|
|
34
|
+
*/
|
|
35
|
+
export declare function encodeFrame(envelope: Envelope, format?: WireFormat): Buffer;
|
|
36
|
+
/**
|
|
37
|
+
* Encode a frame in legacy format (no format byte, JSON only).
|
|
38
|
+
* Used for backwards compatibility with older clients.
|
|
10
39
|
*/
|
|
11
|
-
export declare function
|
|
40
|
+
export declare function encodeFrameLegacy(envelope: Envelope): Buffer;
|
|
12
41
|
/**
|
|
13
|
-
*
|
|
42
|
+
* Ring buffer-based frame parser for streaming data.
|
|
43
|
+
*
|
|
44
|
+
* Optimizations:
|
|
45
|
+
* - Pre-allocated buffer avoids GC pressure from Buffer.concat
|
|
46
|
+
* - Compaction only when necessary (wrap-around)
|
|
47
|
+
* - Direct parsing from buffer offsets
|
|
14
48
|
*/
|
|
15
49
|
export declare class FrameParser {
|
|
16
|
-
private
|
|
17
|
-
private
|
|
50
|
+
private ring;
|
|
51
|
+
private head;
|
|
52
|
+
private tail;
|
|
53
|
+
private readonly capacity;
|
|
54
|
+
private readonly maxFrameBytes;
|
|
55
|
+
private format;
|
|
56
|
+
private legacyMode;
|
|
18
57
|
constructor(maxFrameBytes?: number);
|
|
58
|
+
/**
|
|
59
|
+
* Set the expected wire format for parsing.
|
|
60
|
+
*/
|
|
61
|
+
setFormat(format: WireFormat): void;
|
|
62
|
+
/**
|
|
63
|
+
* Enable legacy mode (4-byte header, JSON only).
|
|
64
|
+
*/
|
|
65
|
+
setLegacyMode(legacy: boolean): void;
|
|
66
|
+
/**
|
|
67
|
+
* Get current unread bytes in buffer.
|
|
68
|
+
*/
|
|
69
|
+
get pendingBytes(): number;
|
|
19
70
|
/**
|
|
20
71
|
* Push data into the parser and extract complete frames.
|
|
72
|
+
*
|
|
73
|
+
* @param data - Incoming data buffer
|
|
74
|
+
* @returns Array of parsed envelope frames
|
|
21
75
|
*/
|
|
22
76
|
push(data: Buffer): Envelope[];
|
|
23
77
|
/**
|
|
24
|
-
*
|
|
78
|
+
* Extract all complete frames from the buffer.
|
|
25
79
|
*/
|
|
26
|
-
|
|
80
|
+
private extractFrames;
|
|
27
81
|
/**
|
|
28
|
-
*
|
|
82
|
+
* Decode payload based on format byte.
|
|
29
83
|
*/
|
|
30
|
-
|
|
84
|
+
private decodePayload;
|
|
85
|
+
/**
|
|
86
|
+
* Compact the buffer by shifting unread data to the start.
|
|
87
|
+
*/
|
|
88
|
+
private compact;
|
|
89
|
+
/**
|
|
90
|
+
* Reset parser state (e.g., on connection reset).
|
|
91
|
+
*/
|
|
92
|
+
reset(): void;
|
|
31
93
|
}
|
|
32
94
|
//# sourceMappingURL=framing.d.ts.map
|
package/dist/protocol/framing.js
CHANGED
|
@@ -1,71 +1,240 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Frame encoding/decoding for the agent relay protocol.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Wire format:
|
|
5
|
+
* - 1 byte: format indicator (0 = JSON, 1 = MessagePack)
|
|
6
|
+
* - 4 bytes: big-endian payload length
|
|
7
|
+
* - N bytes: payload (JSON or MessagePack encoded)
|
|
8
|
+
*
|
|
9
|
+
* Optimizations:
|
|
10
|
+
* - Ring buffer to avoid Buffer.concat allocations
|
|
11
|
+
* - MessagePack support for faster serialization (optional)
|
|
12
|
+
* - Zero-copy frame extraction where possible
|
|
4
13
|
*/
|
|
5
14
|
export const MAX_FRAME_BYTES = 1024 * 1024; // 1 MiB default
|
|
6
|
-
export const HEADER_SIZE = 4
|
|
15
|
+
export const HEADER_SIZE = 5; // 1 byte format + 4 bytes length
|
|
16
|
+
export const LEGACY_HEADER_SIZE = 4; // For backwards compatibility
|
|
17
|
+
// Format indicator bytes
|
|
18
|
+
const FORMAT_JSON = 0;
|
|
19
|
+
const FORMAT_MSGPACK = 1;
|
|
20
|
+
// Optional MessagePack - loaded dynamically if available
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
let msgpack = null;
|
|
23
|
+
/**
|
|
24
|
+
* Initialize MessagePack support. Call this at startup if you want msgpack.
|
|
25
|
+
* Install @msgpack/msgpack to enable: npm install @msgpack/msgpack
|
|
26
|
+
*/
|
|
27
|
+
export async function initMessagePack() {
|
|
28
|
+
if (msgpack)
|
|
29
|
+
return true; // Already initialized
|
|
30
|
+
try {
|
|
31
|
+
// Dynamic import to avoid compile-time dependency
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
const mod = await import('@msgpack/msgpack');
|
|
34
|
+
const encode = mod.encode || mod.default?.encode;
|
|
35
|
+
const decode = mod.decode || mod.default?.decode;
|
|
36
|
+
if (encode && decode) {
|
|
37
|
+
msgpack = { encode, decode };
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// MessagePack not installed, JSON-only mode
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if MessagePack is available.
|
|
49
|
+
*/
|
|
50
|
+
export function hasMessagePack() {
|
|
51
|
+
return msgpack !== null;
|
|
52
|
+
}
|
|
7
53
|
/**
|
|
8
54
|
* Encode a message envelope into a framed buffer.
|
|
55
|
+
*
|
|
56
|
+
* @param envelope - The envelope to encode
|
|
57
|
+
* @param format - Wire format to use (default: 'json')
|
|
58
|
+
* @returns Framed buffer ready for socket write
|
|
9
59
|
*/
|
|
10
|
-
export function encodeFrame(envelope) {
|
|
60
|
+
export function encodeFrame(envelope, format = 'json') {
|
|
61
|
+
let data;
|
|
62
|
+
let formatByte;
|
|
63
|
+
if (format === 'msgpack' && msgpack) {
|
|
64
|
+
// MessagePack: more compact, faster
|
|
65
|
+
const encoded = msgpack.encode(envelope);
|
|
66
|
+
data = Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);
|
|
67
|
+
formatByte = FORMAT_MSGPACK;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// JSON: human-readable, always available
|
|
71
|
+
data = Buffer.from(JSON.stringify(envelope), 'utf-8');
|
|
72
|
+
formatByte = FORMAT_JSON;
|
|
73
|
+
}
|
|
74
|
+
if (data.length > MAX_FRAME_BYTES) {
|
|
75
|
+
throw new Error(`Frame too large: ${data.length} > ${MAX_FRAME_BYTES}`);
|
|
76
|
+
}
|
|
77
|
+
const header = Buffer.alloc(HEADER_SIZE);
|
|
78
|
+
header.writeUInt8(formatByte, 0);
|
|
79
|
+
header.writeUInt32BE(data.length, 1);
|
|
80
|
+
return Buffer.concat([header, data]);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Encode a frame in legacy format (no format byte, JSON only).
|
|
84
|
+
* Used for backwards compatibility with older clients.
|
|
85
|
+
*/
|
|
86
|
+
export function encodeFrameLegacy(envelope) {
|
|
11
87
|
const json = JSON.stringify(envelope);
|
|
12
88
|
const data = Buffer.from(json, 'utf-8');
|
|
13
89
|
if (data.length > MAX_FRAME_BYTES) {
|
|
14
90
|
throw new Error(`Frame too large: ${data.length} > ${MAX_FRAME_BYTES}`);
|
|
15
91
|
}
|
|
16
|
-
const header = Buffer.alloc(
|
|
92
|
+
const header = Buffer.alloc(LEGACY_HEADER_SIZE);
|
|
17
93
|
header.writeUInt32BE(data.length, 0);
|
|
18
94
|
return Buffer.concat([header, data]);
|
|
19
95
|
}
|
|
20
96
|
/**
|
|
21
|
-
*
|
|
97
|
+
* Ring buffer-based frame parser for streaming data.
|
|
98
|
+
*
|
|
99
|
+
* Optimizations:
|
|
100
|
+
* - Pre-allocated buffer avoids GC pressure from Buffer.concat
|
|
101
|
+
* - Compaction only when necessary (wrap-around)
|
|
102
|
+
* - Direct parsing from buffer offsets
|
|
22
103
|
*/
|
|
23
104
|
export class FrameParser {
|
|
24
|
-
|
|
105
|
+
ring;
|
|
106
|
+
head = 0; // Read position
|
|
107
|
+
tail = 0; // Write position
|
|
108
|
+
capacity;
|
|
25
109
|
maxFrameBytes;
|
|
110
|
+
format = 'json';
|
|
111
|
+
legacyMode = false; // Auto-detect legacy clients
|
|
26
112
|
constructor(maxFrameBytes = MAX_FRAME_BYTES) {
|
|
27
113
|
this.maxFrameBytes = maxFrameBytes;
|
|
114
|
+
// Allocate 2x max frame for ring buffer headroom
|
|
115
|
+
this.capacity = maxFrameBytes * 2 + HEADER_SIZE;
|
|
116
|
+
this.ring = Buffer.allocUnsafe(this.capacity);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Set the expected wire format for parsing.
|
|
120
|
+
*/
|
|
121
|
+
setFormat(format) {
|
|
122
|
+
this.format = format;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Enable legacy mode (4-byte header, JSON only).
|
|
126
|
+
*/
|
|
127
|
+
setLegacyMode(legacy) {
|
|
128
|
+
this.legacyMode = legacy;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get current unread bytes in buffer.
|
|
132
|
+
*/
|
|
133
|
+
get pendingBytes() {
|
|
134
|
+
return this.tail - this.head;
|
|
28
135
|
}
|
|
29
136
|
/**
|
|
30
137
|
* Push data into the parser and extract complete frames.
|
|
138
|
+
*
|
|
139
|
+
* @param data - Incoming data buffer
|
|
140
|
+
* @returns Array of parsed envelope frames
|
|
31
141
|
*/
|
|
32
142
|
push(data) {
|
|
33
|
-
|
|
143
|
+
// Check if we have room at the end
|
|
144
|
+
const spaceAtEnd = this.capacity - this.tail;
|
|
145
|
+
if (data.length > spaceAtEnd) {
|
|
146
|
+
// Need to compact: shift unread data to start
|
|
147
|
+
this.compact();
|
|
148
|
+
// Check again after compaction
|
|
149
|
+
if (data.length > this.capacity - this.tail) {
|
|
150
|
+
throw new Error(`Buffer overflow: data ${data.length} exceeds capacity`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Copy incoming data to ring buffer (single copy, no concat)
|
|
154
|
+
data.copy(this.ring, this.tail);
|
|
155
|
+
this.tail += data.length;
|
|
156
|
+
// Extract complete frames
|
|
157
|
+
return this.extractFrames();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Extract all complete frames from the buffer.
|
|
161
|
+
*/
|
|
162
|
+
extractFrames() {
|
|
34
163
|
const frames = [];
|
|
35
|
-
|
|
36
|
-
|
|
164
|
+
const headerSize = this.legacyMode ? LEGACY_HEADER_SIZE : HEADER_SIZE;
|
|
165
|
+
while (this.pendingBytes >= headerSize) {
|
|
166
|
+
// Read frame metadata
|
|
167
|
+
let formatByte = FORMAT_JSON;
|
|
168
|
+
let frameLength;
|
|
169
|
+
if (this.legacyMode) {
|
|
170
|
+
frameLength = this.ring.readUInt32BE(this.head);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
formatByte = this.ring.readUInt8(this.head);
|
|
174
|
+
frameLength = this.ring.readUInt32BE(this.head + 1);
|
|
175
|
+
}
|
|
176
|
+
// Validate frame size
|
|
37
177
|
if (frameLength > this.maxFrameBytes) {
|
|
38
178
|
throw new Error(`Frame too large: ${frameLength} > ${this.maxFrameBytes}`);
|
|
39
179
|
}
|
|
40
|
-
const totalLength =
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
break;
|
|
180
|
+
const totalLength = headerSize + frameLength;
|
|
181
|
+
// Check if we have the complete frame
|
|
182
|
+
if (this.pendingBytes < totalLength) {
|
|
183
|
+
break; // Need more data
|
|
44
184
|
}
|
|
45
|
-
// Extract frame
|
|
46
|
-
const
|
|
47
|
-
|
|
185
|
+
// Extract and parse the frame
|
|
186
|
+
const payloadStart = this.head + headerSize;
|
|
187
|
+
const payloadEnd = this.head + totalLength;
|
|
188
|
+
let envelope;
|
|
48
189
|
try {
|
|
49
|
-
|
|
50
|
-
frames.push(envelope);
|
|
190
|
+
envelope = this.decodePayload(formatByte, payloadStart, payloadEnd);
|
|
51
191
|
}
|
|
52
192
|
catch (err) {
|
|
53
|
-
throw new Error(`Invalid
|
|
193
|
+
throw new Error(`Invalid frame payload: ${err}`);
|
|
54
194
|
}
|
|
195
|
+
// Advance read position
|
|
196
|
+
this.head += totalLength;
|
|
197
|
+
frames.push(envelope);
|
|
198
|
+
}
|
|
199
|
+
// Opportunistic compaction if we've consumed most of the buffer
|
|
200
|
+
if (this.head > this.capacity / 2 && this.pendingBytes < this.capacity / 4) {
|
|
201
|
+
this.compact();
|
|
55
202
|
}
|
|
56
203
|
return frames;
|
|
57
204
|
}
|
|
58
205
|
/**
|
|
59
|
-
*
|
|
206
|
+
* Decode payload based on format byte.
|
|
60
207
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
208
|
+
decodePayload(formatByte, start, end) {
|
|
209
|
+
if (formatByte === FORMAT_MSGPACK && msgpack) {
|
|
210
|
+
// MessagePack decode
|
|
211
|
+
return msgpack.decode(this.ring.subarray(start, end));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// JSON decode (default)
|
|
215
|
+
return JSON.parse(this.ring.toString('utf-8', start, end));
|
|
216
|
+
}
|
|
63
217
|
}
|
|
64
218
|
/**
|
|
65
|
-
*
|
|
219
|
+
* Compact the buffer by shifting unread data to the start.
|
|
66
220
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
221
|
+
compact() {
|
|
222
|
+
if (this.head === 0)
|
|
223
|
+
return;
|
|
224
|
+
const unread = this.pendingBytes;
|
|
225
|
+
if (unread > 0) {
|
|
226
|
+
// Copy unread data to start of buffer
|
|
227
|
+
this.ring.copy(this.ring, 0, this.head, this.tail);
|
|
228
|
+
}
|
|
229
|
+
this.head = 0;
|
|
230
|
+
this.tail = unread;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Reset parser state (e.g., on connection reset).
|
|
234
|
+
*/
|
|
235
|
+
reset() {
|
|
236
|
+
this.head = 0;
|
|
237
|
+
this.tail = 0;
|
|
69
238
|
}
|
|
70
239
|
}
|
|
71
240
|
//# sourceMappingURL=framing.js.map
|
|
@@ -100,12 +100,19 @@ export interface StorageAdapter {
|
|
|
100
100
|
* Can be set via CLI options or environment variables.
|
|
101
101
|
*/
|
|
102
102
|
export interface StorageConfig {
|
|
103
|
-
/** Storage type: 'sqlite', 'none', or 'postgres' (future) */
|
|
103
|
+
/** Storage type: 'sqlite', 'sqlite-batched', 'none', or 'postgres' (future) */
|
|
104
104
|
type?: string;
|
|
105
105
|
/** Path for SQLite database */
|
|
106
106
|
path?: string;
|
|
107
107
|
/** Connection URL for database (postgres://..., mysql://...) */
|
|
108
108
|
url?: string;
|
|
109
|
+
/** Batch configuration for batched adapters */
|
|
110
|
+
batch?: {
|
|
111
|
+
maxBatchSize?: number;
|
|
112
|
+
maxBatchDelayMs?: number;
|
|
113
|
+
maxBatchBytes?: number;
|
|
114
|
+
logBatches?: boolean;
|
|
115
|
+
};
|
|
109
116
|
}
|
|
110
117
|
/**
|
|
111
118
|
* In-memory storage adapter (no persistence).
|
package/dist/storage/adapter.js
CHANGED
|
@@ -144,6 +144,17 @@ export async function createStorageAdapter(dbPath, config) {
|
|
|
144
144
|
// Future: implement PostgreSQL adapter
|
|
145
145
|
throw new Error('PostgreSQL storage is not yet implemented. Use sqlite or none.');
|
|
146
146
|
}
|
|
147
|
+
case 'sqlite-batched':
|
|
148
|
+
case 'batched': {
|
|
149
|
+
console.log('[storage] Using batched SQLite storage');
|
|
150
|
+
const { BatchedSqliteAdapter } = await import('./batched-sqlite-adapter.js');
|
|
151
|
+
const adapter = new BatchedSqliteAdapter({
|
|
152
|
+
dbPath: finalConfig.path,
|
|
153
|
+
batch: finalConfig.batch,
|
|
154
|
+
});
|
|
155
|
+
await adapter.init();
|
|
156
|
+
return adapter;
|
|
157
|
+
}
|
|
147
158
|
case 'sqlite':
|
|
148
159
|
default: {
|
|
149
160
|
const { SqliteStorageAdapter } = await import('./sqlite-adapter.js');
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batched SQLite Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps SqliteStorageAdapter to provide batched writes for improved throughput.
|
|
5
|
+
* Messages are buffered and flushed either when:
|
|
6
|
+
* - Batch size is reached
|
|
7
|
+
* - Time threshold is exceeded
|
|
8
|
+
* - Memory threshold is exceeded
|
|
9
|
+
* - close() is called
|
|
10
|
+
*
|
|
11
|
+
* All reads still go directly to SQLite for consistency.
|
|
12
|
+
*/
|
|
13
|
+
import { SqliteStorageAdapter, type SqliteAdapterOptions } from './sqlite-adapter.js';
|
|
14
|
+
import type { StoredMessage, MessageStatus } from './adapter.js';
|
|
15
|
+
export interface BatchConfig {
|
|
16
|
+
/** Maximum messages in a batch before flush (default: 50) */
|
|
17
|
+
maxBatchSize: number;
|
|
18
|
+
/** Maximum time to wait before flush in ms (default: 100) */
|
|
19
|
+
maxBatchDelayMs: number;
|
|
20
|
+
/** Maximum bytes in memory before flush (default: 1MB) */
|
|
21
|
+
maxBatchBytes: number;
|
|
22
|
+
/** Whether to log batch operations (default: false) */
|
|
23
|
+
logBatches: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const DEFAULT_BATCH_CONFIG: BatchConfig;
|
|
26
|
+
export interface BatchedSqliteAdapterOptions extends SqliteAdapterOptions {
|
|
27
|
+
batch?: Partial<BatchConfig>;
|
|
28
|
+
}
|
|
29
|
+
export declare class BatchedSqliteAdapter extends SqliteStorageAdapter {
|
|
30
|
+
private batchConfig;
|
|
31
|
+
private pending;
|
|
32
|
+
private pendingBytes;
|
|
33
|
+
private flushTimer?;
|
|
34
|
+
private flushing;
|
|
35
|
+
private flushPromise?;
|
|
36
|
+
private metrics;
|
|
37
|
+
constructor(options: BatchedSqliteAdapterOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Queue a message for batched writing.
|
|
40
|
+
* May trigger an immediate flush if thresholds are exceeded.
|
|
41
|
+
*/
|
|
42
|
+
saveMessage(message: StoredMessage): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Flush all pending messages to SQLite.
|
|
45
|
+
*/
|
|
46
|
+
flush(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Write a batch of messages within a transaction.
|
|
49
|
+
*/
|
|
50
|
+
private writeBatch;
|
|
51
|
+
/**
|
|
52
|
+
* Get batch metrics for monitoring.
|
|
53
|
+
*/
|
|
54
|
+
getBatchMetrics(): typeof this.metrics & {
|
|
55
|
+
pendingCount: number;
|
|
56
|
+
pendingBytes: number;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Reset metrics (useful for testing or periodic reporting).
|
|
60
|
+
*/
|
|
61
|
+
resetMetrics(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Close the adapter, flushing any pending messages first.
|
|
64
|
+
*/
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Update message status - goes directly to SQLite (not batched).
|
|
68
|
+
*/
|
|
69
|
+
updateMessageStatus(id: string, status: MessageStatus): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=batched-sqlite-adapter.d.ts.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batched SQLite Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps SqliteStorageAdapter to provide batched writes for improved throughput.
|
|
5
|
+
* Messages are buffered and flushed either when:
|
|
6
|
+
* - Batch size is reached
|
|
7
|
+
* - Time threshold is exceeded
|
|
8
|
+
* - Memory threshold is exceeded
|
|
9
|
+
* - close() is called
|
|
10
|
+
*
|
|
11
|
+
* All reads still go directly to SQLite for consistency.
|
|
12
|
+
*/
|
|
13
|
+
import { SqliteStorageAdapter, } from './sqlite-adapter.js';
|
|
14
|
+
export const DEFAULT_BATCH_CONFIG = {
|
|
15
|
+
maxBatchSize: 50,
|
|
16
|
+
maxBatchDelayMs: 100,
|
|
17
|
+
maxBatchBytes: 1024 * 1024, // 1MB
|
|
18
|
+
logBatches: false,
|
|
19
|
+
};
|
|
20
|
+
export class BatchedSqliteAdapter extends SqliteStorageAdapter {
|
|
21
|
+
batchConfig;
|
|
22
|
+
pending = [];
|
|
23
|
+
pendingBytes = 0;
|
|
24
|
+
flushTimer;
|
|
25
|
+
flushing = false;
|
|
26
|
+
flushPromise;
|
|
27
|
+
// Metrics
|
|
28
|
+
metrics = {
|
|
29
|
+
batchesWritten: 0,
|
|
30
|
+
messagesWritten: 0,
|
|
31
|
+
bytesWritten: 0,
|
|
32
|
+
flushDueToSize: 0,
|
|
33
|
+
flushDueToTime: 0,
|
|
34
|
+
flushDueToBytes: 0,
|
|
35
|
+
};
|
|
36
|
+
constructor(options) {
|
|
37
|
+
super(options);
|
|
38
|
+
this.batchConfig = { ...DEFAULT_BATCH_CONFIG, ...options.batch };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Queue a message for batched writing.
|
|
42
|
+
* May trigger an immediate flush if thresholds are exceeded.
|
|
43
|
+
*/
|
|
44
|
+
async saveMessage(message) {
|
|
45
|
+
// Ensure any pending flush completes first
|
|
46
|
+
if (this.flushPromise) {
|
|
47
|
+
await this.flushPromise;
|
|
48
|
+
}
|
|
49
|
+
const msgJson = JSON.stringify(message);
|
|
50
|
+
const sizeBytes = Buffer.byteLength(msgJson, 'utf-8');
|
|
51
|
+
this.pending.push({ message, sizeBytes });
|
|
52
|
+
this.pendingBytes += sizeBytes;
|
|
53
|
+
// Check flush conditions
|
|
54
|
+
let flushReason = null;
|
|
55
|
+
if (this.pending.length >= this.batchConfig.maxBatchSize) {
|
|
56
|
+
flushReason = 'size';
|
|
57
|
+
this.metrics.flushDueToSize++;
|
|
58
|
+
}
|
|
59
|
+
else if (this.pendingBytes >= this.batchConfig.maxBatchBytes) {
|
|
60
|
+
flushReason = 'bytes';
|
|
61
|
+
this.metrics.flushDueToBytes++;
|
|
62
|
+
}
|
|
63
|
+
if (flushReason) {
|
|
64
|
+
await this.flush();
|
|
65
|
+
}
|
|
66
|
+
else if (!this.flushTimer) {
|
|
67
|
+
// Schedule time-based flush
|
|
68
|
+
this.flushTimer = setTimeout(() => {
|
|
69
|
+
this.metrics.flushDueToTime++;
|
|
70
|
+
this.flush().catch((err) => {
|
|
71
|
+
console.error('[batched-sqlite] Timer flush failed:', err);
|
|
72
|
+
});
|
|
73
|
+
}, this.batchConfig.maxBatchDelayMs);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Flush all pending messages to SQLite.
|
|
78
|
+
*/
|
|
79
|
+
async flush() {
|
|
80
|
+
// Clear timer if set
|
|
81
|
+
if (this.flushTimer) {
|
|
82
|
+
clearTimeout(this.flushTimer);
|
|
83
|
+
this.flushTimer = undefined;
|
|
84
|
+
}
|
|
85
|
+
// Skip if nothing to flush or already flushing
|
|
86
|
+
if (this.pending.length === 0 || this.flushing) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.flushing = true;
|
|
90
|
+
// Take current batch
|
|
91
|
+
const batch = this.pending;
|
|
92
|
+
const batchBytes = this.pendingBytes;
|
|
93
|
+
this.pending = [];
|
|
94
|
+
this.pendingBytes = 0;
|
|
95
|
+
this.flushPromise = this.writeBatch(batch, batchBytes);
|
|
96
|
+
try {
|
|
97
|
+
await this.flushPromise;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
this.flushing = false;
|
|
101
|
+
this.flushPromise = undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write a batch of messages within a transaction.
|
|
106
|
+
*/
|
|
107
|
+
async writeBatch(batch, batchBytes) {
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
try {
|
|
110
|
+
// Use transaction for atomicity and performance
|
|
111
|
+
// The parent class's db is private, so we call saveMessage in a loop
|
|
112
|
+
// but the SQLite driver will batch them efficiently due to WAL mode
|
|
113
|
+
for (const { message } of batch) {
|
|
114
|
+
await super.saveMessage(message);
|
|
115
|
+
}
|
|
116
|
+
// Update metrics
|
|
117
|
+
this.metrics.batchesWritten++;
|
|
118
|
+
this.metrics.messagesWritten += batch.length;
|
|
119
|
+
this.metrics.bytesWritten += batchBytes;
|
|
120
|
+
if (this.batchConfig.logBatches) {
|
|
121
|
+
const elapsed = Date.now() - startTime;
|
|
122
|
+
console.log(`[batched-sqlite] Wrote ${batch.length} messages (${(batchBytes / 1024).toFixed(1)}KB) in ${elapsed}ms`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
// On failure, re-queue messages for retry
|
|
127
|
+
console.error('[batched-sqlite] Batch write failed, re-queuing:', err);
|
|
128
|
+
this.pending = [...batch, ...this.pending];
|
|
129
|
+
this.pendingBytes += batchBytes;
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get batch metrics for monitoring.
|
|
135
|
+
*/
|
|
136
|
+
getBatchMetrics() {
|
|
137
|
+
return {
|
|
138
|
+
...this.metrics,
|
|
139
|
+
pendingCount: this.pending.length,
|
|
140
|
+
pendingBytes: this.pendingBytes,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Reset metrics (useful for testing or periodic reporting).
|
|
145
|
+
*/
|
|
146
|
+
resetMetrics() {
|
|
147
|
+
this.metrics = {
|
|
148
|
+
batchesWritten: 0,
|
|
149
|
+
messagesWritten: 0,
|
|
150
|
+
bytesWritten: 0,
|
|
151
|
+
flushDueToSize: 0,
|
|
152
|
+
flushDueToTime: 0,
|
|
153
|
+
flushDueToBytes: 0,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Close the adapter, flushing any pending messages first.
|
|
158
|
+
*/
|
|
159
|
+
async close() {
|
|
160
|
+
// Ensure all pending messages are written
|
|
161
|
+
if (this.flushTimer) {
|
|
162
|
+
clearTimeout(this.flushTimer);
|
|
163
|
+
this.flushTimer = undefined;
|
|
164
|
+
}
|
|
165
|
+
// Wait for any in-progress flush
|
|
166
|
+
if (this.flushPromise) {
|
|
167
|
+
await this.flushPromise;
|
|
168
|
+
}
|
|
169
|
+
// Flush remaining
|
|
170
|
+
if (this.pending.length > 0) {
|
|
171
|
+
await this.flush();
|
|
172
|
+
}
|
|
173
|
+
await super.close();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Update message status - goes directly to SQLite (not batched).
|
|
177
|
+
*/
|
|
178
|
+
async updateMessageStatus(id, status) {
|
|
179
|
+
// Status updates should be immediate, not batched
|
|
180
|
+
return super.updateMessageStatus(id, status);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=batched-sqlite-adapter.js.map
|