@agent-relay/storage 0.1.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/dist/adapter.d.ts +156 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +167 -0
- package/dist/adapter.js.map +1 -0
- package/dist/batched-sqlite-adapter.d.ts +75 -0
- package/dist/batched-sqlite-adapter.d.ts.map +1 -0
- package/dist/batched-sqlite-adapter.js +189 -0
- package/dist/batched-sqlite-adapter.js.map +1 -0
- package/dist/dead-letter-queue.d.ts +196 -0
- package/dist/dead-letter-queue.d.ts.map +1 -0
- package/dist/dead-letter-queue.js +427 -0
- package/dist/dead-letter-queue.js.map +1 -0
- package/dist/dlq-adapter.d.ts +195 -0
- package/dist/dlq-adapter.d.ts.map +1 -0
- package/dist/dlq-adapter.js +664 -0
- package/dist/dlq-adapter.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/sqlite-adapter.d.ts +113 -0
- package/dist/sqlite-adapter.d.ts.map +1 -0
- package/dist/sqlite-adapter.js +752 -0
- package/dist/sqlite-adapter.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { PayloadKind, SendMeta } from '@agent-relay/protocol/types';
|
|
2
|
+
export type MessageStatus = 'unread' | 'read' | 'acked' | 'failed';
|
|
3
|
+
export interface StoredMessage {
|
|
4
|
+
id: string;
|
|
5
|
+
ts: number;
|
|
6
|
+
from: string;
|
|
7
|
+
to: string;
|
|
8
|
+
topic?: string;
|
|
9
|
+
kind: PayloadKind;
|
|
10
|
+
body: string;
|
|
11
|
+
data?: Record<string, unknown>;
|
|
12
|
+
/** Optional metadata (importance, replyTo, etc.) */
|
|
13
|
+
payloadMeta?: SendMeta;
|
|
14
|
+
/** Optional thread ID for grouping related messages */
|
|
15
|
+
thread?: string;
|
|
16
|
+
deliverySeq?: number;
|
|
17
|
+
deliverySessionId?: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
/** Per-recipient message status */
|
|
20
|
+
status: MessageStatus;
|
|
21
|
+
/** Whether the message is marked as urgent */
|
|
22
|
+
is_urgent: boolean;
|
|
23
|
+
/** Whether the message was sent as a broadcast (to: '*') */
|
|
24
|
+
is_broadcast?: boolean;
|
|
25
|
+
/** Number of replies in this thread (when message.id is used as thread) */
|
|
26
|
+
replyCount?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface MessageQuery {
|
|
29
|
+
limit?: number;
|
|
30
|
+
sinceTs?: number;
|
|
31
|
+
from?: string;
|
|
32
|
+
to?: string;
|
|
33
|
+
topic?: string;
|
|
34
|
+
/** Filter by thread ID */
|
|
35
|
+
thread?: string;
|
|
36
|
+
order?: 'asc' | 'desc';
|
|
37
|
+
/** Only include unread messages */
|
|
38
|
+
unreadOnly?: boolean;
|
|
39
|
+
/** Only include urgent messages */
|
|
40
|
+
urgentOnly?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface StoredSession {
|
|
43
|
+
id: string;
|
|
44
|
+
agentName: string;
|
|
45
|
+
cli?: string;
|
|
46
|
+
projectId?: string;
|
|
47
|
+
projectRoot?: string;
|
|
48
|
+
startedAt: number;
|
|
49
|
+
endedAt?: number;
|
|
50
|
+
messageCount: number;
|
|
51
|
+
summary?: string;
|
|
52
|
+
resumeToken?: string;
|
|
53
|
+
/** How the session was closed: 'agent' (explicit), 'disconnect', 'error', or undefined (still active) */
|
|
54
|
+
closedBy?: 'agent' | 'disconnect' | 'error';
|
|
55
|
+
}
|
|
56
|
+
export interface SessionQuery {
|
|
57
|
+
agentName?: string;
|
|
58
|
+
projectId?: string;
|
|
59
|
+
since?: number;
|
|
60
|
+
limit?: number;
|
|
61
|
+
}
|
|
62
|
+
export interface AgentSummary {
|
|
63
|
+
agentName: string;
|
|
64
|
+
projectId?: string;
|
|
65
|
+
lastUpdated: number;
|
|
66
|
+
currentTask?: string;
|
|
67
|
+
completedTasks?: string[];
|
|
68
|
+
decisions?: string[];
|
|
69
|
+
context?: string;
|
|
70
|
+
files?: string[];
|
|
71
|
+
}
|
|
72
|
+
export interface StorageAdapter {
|
|
73
|
+
init(): Promise<void>;
|
|
74
|
+
saveMessage(message: StoredMessage): Promise<void>;
|
|
75
|
+
getMessages(query?: MessageQuery): Promise<StoredMessage[]>;
|
|
76
|
+
getMessageById?(id: string): Promise<StoredMessage | null>;
|
|
77
|
+
updateMessageStatus?(id: string, status: MessageStatus): Promise<void>;
|
|
78
|
+
close?(): Promise<void>;
|
|
79
|
+
startSession?(session: Omit<StoredSession, 'messageCount'>): Promise<void>;
|
|
80
|
+
endSession?(sessionId: string, options?: {
|
|
81
|
+
summary?: string;
|
|
82
|
+
closedBy?: 'agent' | 'disconnect' | 'error';
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
getSessions?(query?: SessionQuery): Promise<StoredSession[]>;
|
|
85
|
+
getRecentSessions?(limit?: number): Promise<StoredSession[]>;
|
|
86
|
+
incrementSessionMessageCount?(sessionId: string): Promise<void>;
|
|
87
|
+
getSessionByResumeToken?(resumeToken: string): Promise<StoredSession | null>;
|
|
88
|
+
saveAgentSummary?(summary: Omit<AgentSummary, 'lastUpdated'>): Promise<void>;
|
|
89
|
+
getAgentSummary?(agentName: string): Promise<AgentSummary | null>;
|
|
90
|
+
getAllAgentSummaries?(): Promise<AgentSummary[]>;
|
|
91
|
+
getPendingMessagesForSession?(agentName: string, sessionId: string): Promise<StoredMessage[]>;
|
|
92
|
+
getMaxSeqByStream?(agentName: string, sessionId: string): Promise<Array<{
|
|
93
|
+
peer: string;
|
|
94
|
+
topic?: string;
|
|
95
|
+
maxSeq: number;
|
|
96
|
+
}>>;
|
|
97
|
+
/** Get channels that an agent is a member of (based on stored membership events) */
|
|
98
|
+
getChannelMembershipsForAgent?(memberName: string): Promise<string[]>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Storage configuration options.
|
|
102
|
+
* Can be set via CLI options or environment variables.
|
|
103
|
+
*/
|
|
104
|
+
export interface StorageConfig {
|
|
105
|
+
/** Storage type: 'sqlite', 'sqlite-batched', 'none', or 'postgres' (future) */
|
|
106
|
+
type?: string;
|
|
107
|
+
/** Path for SQLite database */
|
|
108
|
+
path?: string;
|
|
109
|
+
/** Connection URL for database (postgres://..., mysql://...) */
|
|
110
|
+
url?: string;
|
|
111
|
+
/** Batch configuration for batched adapters */
|
|
112
|
+
batch?: {
|
|
113
|
+
maxBatchSize?: number;
|
|
114
|
+
maxBatchDelayMs?: number;
|
|
115
|
+
maxBatchBytes?: number;
|
|
116
|
+
logBatches?: boolean;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* In-memory storage adapter (no persistence).
|
|
121
|
+
* Useful for testing or when persistence is not needed.
|
|
122
|
+
*/
|
|
123
|
+
export declare class MemoryStorageAdapter implements StorageAdapter {
|
|
124
|
+
private messages;
|
|
125
|
+
init(): Promise<void>;
|
|
126
|
+
saveMessage(message: StoredMessage): Promise<void>;
|
|
127
|
+
getMessages(query?: MessageQuery): Promise<StoredMessage[]>;
|
|
128
|
+
getMessageById(id: string): Promise<StoredMessage | null>;
|
|
129
|
+
updateMessageStatus(id: string, status: MessageStatus): Promise<void>;
|
|
130
|
+
getPendingMessagesForSession(agentName: string, sessionId: string): Promise<StoredMessage[]>;
|
|
131
|
+
getMaxSeqByStream(agentName: string, sessionId: string): Promise<Array<{
|
|
132
|
+
peer: string;
|
|
133
|
+
topic?: string;
|
|
134
|
+
maxSeq: number;
|
|
135
|
+
}>>;
|
|
136
|
+
close(): Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get storage configuration from environment variables.
|
|
140
|
+
*/
|
|
141
|
+
export declare function getStorageConfigFromEnv(): StorageConfig;
|
|
142
|
+
/**
|
|
143
|
+
* Create a storage adapter based on configuration.
|
|
144
|
+
*
|
|
145
|
+
* Configuration priority:
|
|
146
|
+
* 1. Explicit config passed to function
|
|
147
|
+
* 2. Environment variables (AGENT_RELAY_STORAGE_TYPE, AGENT_RELAY_STORAGE_PATH, AGENT_RELAY_STORAGE_URL)
|
|
148
|
+
* 3. Default: SQLite at provided dbPath
|
|
149
|
+
*
|
|
150
|
+
* Supported storage types:
|
|
151
|
+
* - 'sqlite' (default): SQLite file-based storage
|
|
152
|
+
* - 'none' or 'memory': In-memory storage (no persistence)
|
|
153
|
+
* - 'postgres': PostgreSQL (requires AGENT_RELAY_STORAGE_URL) - future
|
|
154
|
+
*/
|
|
155
|
+
export declare function createStorageAdapter(dbPath: string, config?: StorageConfig): Promise<StorageAdapter>;
|
|
156
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory storage adapter (no persistence).
|
|
3
|
+
* Useful for testing or when persistence is not needed.
|
|
4
|
+
*/
|
|
5
|
+
export class MemoryStorageAdapter {
|
|
6
|
+
messages = [];
|
|
7
|
+
async init() {
|
|
8
|
+
// No initialization needed
|
|
9
|
+
}
|
|
10
|
+
async saveMessage(message) {
|
|
11
|
+
this.messages.push(message);
|
|
12
|
+
// Keep only last 1000 messages to prevent memory issues
|
|
13
|
+
if (this.messages.length > 1000) {
|
|
14
|
+
this.messages = this.messages.slice(-1000);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async getMessages(query) {
|
|
18
|
+
let result = [...this.messages];
|
|
19
|
+
if (query?.from) {
|
|
20
|
+
result = result.filter(m => m.from === query.from);
|
|
21
|
+
}
|
|
22
|
+
if (query?.to) {
|
|
23
|
+
result = result.filter(m => m.to === query.to);
|
|
24
|
+
}
|
|
25
|
+
if (query?.topic) {
|
|
26
|
+
result = result.filter(m => m.topic === query.topic);
|
|
27
|
+
}
|
|
28
|
+
if (query?.thread) {
|
|
29
|
+
result = result.filter(m => m.thread === query.thread);
|
|
30
|
+
}
|
|
31
|
+
if (query?.sinceTs) {
|
|
32
|
+
result = result.filter(m => m.ts >= query.sinceTs);
|
|
33
|
+
}
|
|
34
|
+
if (query?.order === 'asc') {
|
|
35
|
+
result.sort((a, b) => a.ts - b.ts);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
result.sort((a, b) => b.ts - a.ts);
|
|
39
|
+
}
|
|
40
|
+
if (query?.limit) {
|
|
41
|
+
result = result.slice(0, query.limit);
|
|
42
|
+
}
|
|
43
|
+
// Calculate replyCount for each message (count of messages where thread === message.id)
|
|
44
|
+
return result.map(m => ({
|
|
45
|
+
...m,
|
|
46
|
+
replyCount: this.messages.filter(msg => msg.thread === m.id).length,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
async getMessageById(id) {
|
|
50
|
+
// Support both exact match and prefix match (for short IDs)
|
|
51
|
+
const msg = this.messages.find(m => m.id === id || m.id.startsWith(id));
|
|
52
|
+
if (!msg)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
...msg,
|
|
56
|
+
replyCount: this.messages.filter(m => m.thread === msg.id).length,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async updateMessageStatus(id, status) {
|
|
60
|
+
const msg = this.messages.find(m => m.id === id || m.id.startsWith(id));
|
|
61
|
+
if (msg) {
|
|
62
|
+
msg.status = status;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async getPendingMessagesForSession(agentName, sessionId) {
|
|
66
|
+
return this.messages
|
|
67
|
+
.filter(m => m.to === agentName && m.deliverySessionId === sessionId && m.status !== 'acked')
|
|
68
|
+
.sort((a, b) => {
|
|
69
|
+
const seqA = mSeq(a);
|
|
70
|
+
const seqB = mSeq(b);
|
|
71
|
+
return seqA === seqB ? a.ts - b.ts : seqA - seqB;
|
|
72
|
+
});
|
|
73
|
+
function mSeq(msg) {
|
|
74
|
+
return msg.deliverySeq ?? 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async getMaxSeqByStream(agentName, sessionId) {
|
|
78
|
+
const aggregates = new Map();
|
|
79
|
+
for (const msg of this.messages) {
|
|
80
|
+
if (msg.to !== agentName)
|
|
81
|
+
continue;
|
|
82
|
+
if (msg.deliverySessionId !== sessionId)
|
|
83
|
+
continue;
|
|
84
|
+
if (msg.deliverySeq === undefined || msg.deliverySeq === null)
|
|
85
|
+
continue;
|
|
86
|
+
const topic = msg.topic ?? 'default';
|
|
87
|
+
const key = `${topic}:${msg.from}`;
|
|
88
|
+
const current = aggregates.get(key);
|
|
89
|
+
if (!current || msg.deliverySeq > current.maxSeq) {
|
|
90
|
+
aggregates.set(key, { peer: msg.from, topic: msg.topic, maxSeq: msg.deliverySeq });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return Array.from(aggregates.values());
|
|
94
|
+
}
|
|
95
|
+
async close() {
|
|
96
|
+
this.messages = [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get storage configuration from environment variables.
|
|
101
|
+
*/
|
|
102
|
+
export function getStorageConfigFromEnv() {
|
|
103
|
+
return {
|
|
104
|
+
type: process.env.AGENT_RELAY_STORAGE_TYPE,
|
|
105
|
+
path: process.env.AGENT_RELAY_STORAGE_PATH,
|
|
106
|
+
url: process.env.AGENT_RELAY_STORAGE_URL,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create a storage adapter based on configuration.
|
|
111
|
+
*
|
|
112
|
+
* Configuration priority:
|
|
113
|
+
* 1. Explicit config passed to function
|
|
114
|
+
* 2. Environment variables (AGENT_RELAY_STORAGE_TYPE, AGENT_RELAY_STORAGE_PATH, AGENT_RELAY_STORAGE_URL)
|
|
115
|
+
* 3. Default: SQLite at provided dbPath
|
|
116
|
+
*
|
|
117
|
+
* Supported storage types:
|
|
118
|
+
* - 'sqlite' (default): SQLite file-based storage
|
|
119
|
+
* - 'none' or 'memory': In-memory storage (no persistence)
|
|
120
|
+
* - 'postgres': PostgreSQL (requires AGENT_RELAY_STORAGE_URL) - future
|
|
121
|
+
*/
|
|
122
|
+
export async function createStorageAdapter(dbPath, config) {
|
|
123
|
+
// Merge with env config, explicit config takes priority
|
|
124
|
+
const envConfig = getStorageConfigFromEnv();
|
|
125
|
+
const finalConfig = {
|
|
126
|
+
type: config?.type ?? envConfig.type ?? 'sqlite',
|
|
127
|
+
path: config?.path ?? envConfig.path ?? dbPath,
|
|
128
|
+
url: config?.url ?? envConfig.url,
|
|
129
|
+
};
|
|
130
|
+
const storageType = finalConfig.type?.toLowerCase();
|
|
131
|
+
switch (storageType) {
|
|
132
|
+
case 'none':
|
|
133
|
+
case 'memory': {
|
|
134
|
+
console.log('[storage] Using in-memory storage (no persistence)');
|
|
135
|
+
const adapter = new MemoryStorageAdapter();
|
|
136
|
+
await adapter.init();
|
|
137
|
+
return adapter;
|
|
138
|
+
}
|
|
139
|
+
case 'postgres':
|
|
140
|
+
case 'postgresql': {
|
|
141
|
+
if (!finalConfig.url) {
|
|
142
|
+
throw new Error('PostgreSQL storage requires AGENT_RELAY_STORAGE_URL environment variable or --storage-url option');
|
|
143
|
+
}
|
|
144
|
+
// Future: implement PostgreSQL adapter
|
|
145
|
+
throw new Error('PostgreSQL storage is not yet implemented. Use sqlite or none.');
|
|
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
|
+
}
|
|
158
|
+
case 'sqlite':
|
|
159
|
+
default: {
|
|
160
|
+
const { SqliteStorageAdapter } = await import('./sqlite-adapter.js');
|
|
161
|
+
const adapter = new SqliteStorageAdapter({ dbPath: finalConfig.path });
|
|
162
|
+
await adapter.init();
|
|
163
|
+
return adapter;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
* Get channel memberships for an agent - read-only, delegates to parent.
|
|
72
|
+
*/
|
|
73
|
+
getChannelMembershipsForAgent(memberName: string): Promise<string[]>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=batched-sqlite-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batched-sqlite-adapter.d.ts","sourceRoot":"","sources":["../src/batched-sqlite-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,oBAAoB,EACpB,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,oBAAoB,EAAE,WAKlC,CAAC;AAEF,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC9B;AAOD,qBAAa,oBAAqB,SAAQ,oBAAoB;IAC5D,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAC,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAC,CAAgB;IAGrC,OAAO,CAAC,OAAO,CAOb;gBAEU,OAAO,EAAE,2BAA2B;IAKhD;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCxD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B5B;;OAEG;YACW,UAAU;IA+BxB;;OAEG;IACH,eAAe,IAAI,OAAO,IAAI,CAAC,OAAO,GAAG;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAQvF;;OAEG;IACH,YAAY,IAAI,IAAI;IAWpB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5B;;OAEG;IACG,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3E;;OAEG;IACG,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAG3E"}
|
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
* Get channel memberships for an agent - read-only, delegates to parent.
|
|
184
|
+
*/
|
|
185
|
+
async getChannelMembershipsForAgent(memberName) {
|
|
186
|
+
return super.getChannelMembershipsForAgent(memberName);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=batched-sqlite-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batched-sqlite-adapter.js","sourceRoot":"","sources":["../src/batched-sqlite-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,oBAAoB,GAErB,MAAM,qBAAqB,CAAC;AAc7B,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,YAAY,EAAE,EAAE;IAChB,eAAe,EAAE,GAAG;IACpB,aAAa,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM;IAClC,UAAU,EAAE,KAAK;CAClB,CAAC;AAWF,MAAM,OAAO,oBAAqB,SAAQ,oBAAoB;IACpD,WAAW,CAAc;IACzB,OAAO,GAAqB,EAAE,CAAC;IAC/B,YAAY,GAAG,CAAC,CAAC;IACjB,UAAU,CAAkB;IAC5B,QAAQ,GAAG,KAAK,CAAC;IACjB,YAAY,CAAiB;IAErC,UAAU;IACF,OAAO,GAAG;QAChB,cAAc,EAAE,CAAC;QACjB,eAAe,EAAE,CAAC;QAClB,YAAY,EAAE,CAAC;QACf,cAAc,EAAE,CAAC;QACjB,cAAc,EAAE,CAAC;QACjB,eAAe,EAAE,CAAC;KACnB,CAAC;IAEF,YAAY,OAAoC;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,EAAE,GAAG,oBAAoB,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IACnE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,OAAsB;QACtC,2CAA2C;QAC3C,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,YAAY,CAAC;QAC1B,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAEtD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,YAAY,IAAI,SAAS,CAAC;QAE/B,yBAAyB;QACzB,IAAI,WAAW,GAA4B,IAAI,CAAC;QAEhD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;YACzD,WAAW,GAAG,MAAM,CAAC;YACrB,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAChC,CAAC;aAAM,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC;YAC/D,WAAW,GAAG,OAAO,CAAC;YACtB,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QACjC,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;aAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YAC5B,4BAA4B;YAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;gBAChC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;gBAC9B,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBACzB,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;gBAC7D,CAAC,CAAC,CAAC;YACL,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,qBAAqB;QACrB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;QAED,+CAA+C;QAC/C,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,qBAAqB;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC;QAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QAEtB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAEvD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC;QAC1B,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAChC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,UAAU,CAAC,KAAuB,EAAE,UAAkB;QAClE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,gDAAgD;YAChD,qEAAqE;YACrE,oEAAoE;YACpE,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,KAAK,EAAE,CAAC;gBAChC,MAAM,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC;YAED,iBAAiB;YACjB,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;YAC9B,IAAI,CAAC,OAAO,CAAC,eAAe,IAAI,KAAK,CAAC,MAAM,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,UAAU,CAAC;YAExC,IAAI,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;gBAChC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;gBACvC,OAAO,CAAC,GAAG,CACT,0BAA0B,KAAK,CAAC,MAAM,cAAc,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,OAAO,IAAI,CACxG,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,0CAA0C;YAC1C,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,GAAG,CAAC,CAAC;YACvE,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,CAAC,YAAY,IAAI,UAAU,CAAC;YAChC,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO;YACL,GAAG,IAAI,CAAC,OAAO;YACf,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;YACjC,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,YAAY;QACV,IAAI,CAAC,OAAO,GAAG;YACb,cAAc,EAAE,CAAC;YACjB,eAAe,EAAE,CAAC;YAClB,YAAY,EAAE,CAAC;YACf,cAAc,EAAE,CAAC;YACjB,cAAc,EAAE,CAAC;YACjB,eAAe,EAAE,CAAC;SACnB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,0CAA0C;QAC1C,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;QAED,iCAAiC;QACjC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,YAAY,CAAC;QAC1B,CAAC;QAED,kBAAkB;QAClB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QAED,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB,CAAC,EAAU,EAAE,MAAqB;QACzD,kDAAkD;QAClD,OAAO,KAAK,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,6BAA6B,CAAC,UAAkB;QACpD,OAAO,KAAK,CAAC,6BAA6B,CAAC,UAAU,CAAC,CAAC;IACzD,CAAC;CACF"}
|