@kb-labs/adapters-log-document 2.96.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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @kb-labs/adapters-log-document
2
+
3
+ > Part of [KB Labs](https://github.com/KirillBaranov/kb-labs) ecosystem.
4
+
5
+ `ILogPersistence` implementation backed by `IDocumentDatabase`. Driver-agnostic — swap sqlite for postgres-JSONB and logs follow.
@@ -0,0 +1,77 @@
1
+ import { ILogPersistence, LogPersistenceConfig, LogRecord, LogQuery } from '@kb-labs/sdk/adapters';
2
+ export { manifest } from './manifest.js';
3
+
4
+ /**
5
+ * @module @kb-labs/adapters-log-document
6
+ *
7
+ * `ILogPersistence` implementation backed by any `IDocumentDatabase`.
8
+ *
9
+ * This is the dogfooded replacement for the old sqlite-only log persistence
10
+ * adapter: log entries are now stored as documents in a `logs` collection
11
+ * using the same abstraction every other plugin uses. The platform stays
12
+ * driver-agnostic — switch from sqlite to postgres-JSONB and logs follow.
13
+ *
14
+ * Buffering: writes are queued in memory and flushed in batches to amortise
15
+ * per-call overhead. `close()` (and the next flush after batchSize entries)
16
+ * drains the queue.
17
+ *
18
+ * Retention: a periodic timer deletes documents older than the configured
19
+ * thresholds per log level. Without retention the collection grows
20
+ * unbounded — defaults are applied if `retention` is absent.
21
+ *
22
+ * Search: implemented via `$contains` on the message field. Full-text
23
+ * indexing (FTS5, MongoDB text) is intentionally OUT of scope here — that's
24
+ * a backend-specific capability and would leak through the contract. If
25
+ * advanced search is needed, route it via an external index (Meilisearch
26
+ * etc.) and keep this layer simple.
27
+ */
28
+
29
+ declare class DocumentLogPersistence implements ILogPersistence {
30
+ private readonly docs;
31
+ private readonly collection;
32
+ private readonly batchSize;
33
+ private readonly flushIntervalMs;
34
+ private readonly retention;
35
+ private buffer;
36
+ private flushTimer;
37
+ private retentionTimer;
38
+ private closed;
39
+ private flushing;
40
+ private initialised;
41
+ constructor(config: LogPersistenceConfig);
42
+ write(record: LogRecord): Promise<void>;
43
+ writeBatch(records: LogRecord[]): Promise<void>;
44
+ private flush;
45
+ query(query: LogQuery, options?: {
46
+ limit?: number;
47
+ offset?: number;
48
+ sortBy?: 'timestamp' | 'level';
49
+ sortOrder?: 'asc' | 'desc';
50
+ }): Promise<{
51
+ logs: LogRecord[];
52
+ total: number;
53
+ hasMore: boolean;
54
+ }>;
55
+ getById(id: string): Promise<LogRecord | null>;
56
+ search(searchText: string, options?: {
57
+ limit?: number;
58
+ offset?: number;
59
+ }): Promise<{
60
+ logs: LogRecord[];
61
+ total: number;
62
+ hasMore: boolean;
63
+ }>;
64
+ deleteOlderThan(beforeTimestamp: number): Promise<number>;
65
+ deleteByLevelOlderThan(levels: string[], beforeTimestamp: number): Promise<number>;
66
+ getStats(): Promise<{
67
+ totalLogs: number;
68
+ oldestTimestamp: number;
69
+ newestTimestamp: number;
70
+ sizeBytes: number;
71
+ }>;
72
+ close(): Promise<void>;
73
+ private runRetention;
74
+ }
75
+ declare function createAdapter(config: LogPersistenceConfig, deps?: Record<string, unknown>): DocumentLogPersistence;
76
+
77
+ export { DocumentLogPersistence, createAdapter, createAdapter as default };
package/dist/index.js ADDED
@@ -0,0 +1,309 @@
1
+ // src/manifest.ts
2
+ var manifest = {
3
+ manifestVersion: "1.0.0",
4
+ id: "log-document",
5
+ name: "Document-backed Log Persistence",
6
+ version: "1.0.0",
7
+ description: "ILogPersistence implementation storing log entries in any IDocumentDatabase. Driver-agnostic.",
8
+ author: "KB Labs Team",
9
+ license: "KBPL-1.1",
10
+ type: "core",
11
+ implements: "ILogPersistence",
12
+ contexts: ["workspace", "process"],
13
+ requires: { adapters: [{ id: "documentDatabase", alias: "documentDatabase" }] },
14
+ capabilities: {
15
+ batch: true,
16
+ search: true,
17
+ custom: {
18
+ retentionPolicy: true
19
+ }
20
+ },
21
+ configSchema: {
22
+ collection: {
23
+ type: "string",
24
+ default: "logs",
25
+ description: "Name of the document collection that stores log entries."
26
+ },
27
+ batchSize: {
28
+ type: "number",
29
+ default: 100,
30
+ description: "Batch size for buffered writes."
31
+ },
32
+ flushInterval: {
33
+ type: "number",
34
+ default: 5e3,
35
+ description: "Maximum delay before a partial batch is flushed (ms)."
36
+ }
37
+ }
38
+ };
39
+
40
+ // src/index.ts
41
+ var LEVEL_ORDER = {
42
+ trace: 0,
43
+ debug: 1,
44
+ info: 2,
45
+ warn: 3,
46
+ error: 4,
47
+ fatal: 5
48
+ };
49
+ var LEVELS_AT_OR_ABOVE = (min) => {
50
+ const threshold = LEVEL_ORDER[min];
51
+ return Object.keys(LEVEL_ORDER).filter((l) => LEVEL_ORDER[l] >= threshold);
52
+ };
53
+ var DEFAULT_RETENTION = {
54
+ maxAge: 7 * 24 * 60 * 60 * 1e3,
55
+ // 7 days for warn/error/fatal
56
+ maxAgeDebug: 60 * 60 * 1e3,
57
+ // 1 hour for debug/trace
58
+ maxAgeInfo: 24 * 60 * 60 * 1e3,
59
+ // 24 hours for info
60
+ maxSizeBytes: 500 * 1024 * 1024,
61
+ // 500 MB
62
+ cleanupIntervalMs: 5 * 60 * 1e3
63
+ // 5 minutes
64
+ };
65
+ var DocumentLogPersistence = class {
66
+ docs;
67
+ collection;
68
+ batchSize;
69
+ flushIntervalMs;
70
+ retention;
71
+ buffer = [];
72
+ flushTimer = null;
73
+ retentionTimer = null;
74
+ closed = false;
75
+ flushing = null;
76
+ initialised;
77
+ constructor(config) {
78
+ this.docs = config.documentDatabase;
79
+ this.collection = config.collection ?? "logs";
80
+ this.batchSize = config.batchSize ?? 100;
81
+ this.flushIntervalMs = config.flushInterval ?? 5e3;
82
+ this.retention = { ...DEFAULT_RETENTION, ...config.retention ?? {} };
83
+ this.initialised = this.docs.ensureCollection(this.collection, {
84
+ indexes: [
85
+ { path: "logId", unique: true },
86
+ { path: "timestamp" },
87
+ { path: "level" },
88
+ { path: "source" }
89
+ ]
90
+ });
91
+ this.flushTimer = setInterval(() => {
92
+ void this.flush();
93
+ }, this.flushIntervalMs).unref();
94
+ this.retentionTimer = setInterval(() => {
95
+ void this.runRetention();
96
+ }, this.retention.cleanupIntervalMs).unref();
97
+ }
98
+ // ──────────────────────────────────────────────────────────────────────────
99
+ // Write path (buffered)
100
+ // ──────────────────────────────────────────────────────────────────────────
101
+ async write(record) {
102
+ if (this.closed) {
103
+ throw new Error("DocumentLogPersistence is closed");
104
+ }
105
+ this.buffer.push(record);
106
+ if (this.buffer.length >= this.batchSize) {
107
+ void this.flush();
108
+ }
109
+ }
110
+ async writeBatch(records) {
111
+ if (this.closed) {
112
+ throw new Error("DocumentLogPersistence is closed");
113
+ }
114
+ if (records.length === 0) {
115
+ return;
116
+ }
117
+ this.buffer.push(...records);
118
+ if (this.buffer.length >= this.batchSize) {
119
+ void this.flush();
120
+ }
121
+ }
122
+ async flush() {
123
+ if (this.flushing) {
124
+ await this.flushing;
125
+ return;
126
+ }
127
+ if (this.buffer.length === 0) {
128
+ return;
129
+ }
130
+ const batch = this.buffer.splice(0, this.buffer.length);
131
+ this.flushing = (async () => {
132
+ try {
133
+ await this.initialised;
134
+ await this.docs.insertMany(this.collection, batch.map((r) => ({
135
+ logId: r.id,
136
+ timestamp: r.timestamp,
137
+ level: r.level,
138
+ message: r.message,
139
+ fields: r.fields,
140
+ source: r.source
141
+ })));
142
+ } catch (err) {
143
+ this.buffer.unshift(...batch);
144
+ throw err;
145
+ } finally {
146
+ this.flushing = null;
147
+ }
148
+ })();
149
+ await this.flushing;
150
+ }
151
+ // ──────────────────────────────────────────────────────────────────────────
152
+ // Read path
153
+ // ──────────────────────────────────────────────────────────────────────────
154
+ async query(query, options) {
155
+ await this.initialised;
156
+ const filter = buildFilter(query);
157
+ const limit = options?.limit ?? 100;
158
+ const offset = options?.offset ?? 0;
159
+ const sortField = options?.sortBy ?? "timestamp";
160
+ const sortDir = options?.sortOrder === "asc" ? 1 : -1;
161
+ const [total, docs] = await Promise.all([
162
+ this.docs.count(this.collection, filter),
163
+ this.docs.find(this.collection, filter, {
164
+ sort: { [sortField]: sortDir },
165
+ limit,
166
+ skip: offset
167
+ })
168
+ ]);
169
+ return {
170
+ logs: docs.map(toLogRecord),
171
+ total,
172
+ hasMore: offset + docs.length < total
173
+ };
174
+ }
175
+ async getById(id) {
176
+ await this.initialised;
177
+ const docs = await this.docs.find(this.collection, { logId: { $eq: id } }, { limit: 1 });
178
+ const first = docs[0];
179
+ return first ? toLogRecord(first) : null;
180
+ }
181
+ async search(searchText, options) {
182
+ await this.initialised;
183
+ const filter = { message: { $contains: searchText } };
184
+ const limit = options?.limit ?? 100;
185
+ const offset = options?.offset ?? 0;
186
+ const [total, docs] = await Promise.all([
187
+ this.docs.count(this.collection, filter),
188
+ this.docs.find(this.collection, filter, {
189
+ sort: { timestamp: -1 },
190
+ limit,
191
+ skip: offset
192
+ })
193
+ ]);
194
+ return {
195
+ logs: docs.map(toLogRecord),
196
+ total,
197
+ hasMore: offset + docs.length < total
198
+ };
199
+ }
200
+ async deleteOlderThan(beforeTimestamp) {
201
+ await this.initialised;
202
+ return this.docs.deleteMany(this.collection, {
203
+ timestamp: { $lt: beforeTimestamp }
204
+ });
205
+ }
206
+ async deleteByLevelOlderThan(levels, beforeTimestamp) {
207
+ await this.initialised;
208
+ return this.docs.deleteMany(this.collection, {
209
+ $and: [
210
+ { level: { $in: levels } },
211
+ { timestamp: { $lt: beforeTimestamp } }
212
+ ]
213
+ });
214
+ }
215
+ async getStats() {
216
+ await this.initialised;
217
+ const [totalLogs, oldest, newest] = await Promise.all([
218
+ this.docs.count(this.collection, {}),
219
+ this.docs.find(this.collection, {}, { sort: { timestamp: 1 }, limit: 1 }),
220
+ this.docs.find(this.collection, {}, { sort: { timestamp: -1 }, limit: 1 })
221
+ ]);
222
+ return {
223
+ totalLogs,
224
+ oldestTimestamp: oldest[0]?.timestamp ?? 0,
225
+ newestTimestamp: newest[0]?.timestamp ?? 0,
226
+ sizeBytes: 0
227
+ };
228
+ }
229
+ async close() {
230
+ if (this.closed) {
231
+ return;
232
+ }
233
+ this.closed = true;
234
+ if (this.flushTimer) {
235
+ clearInterval(this.flushTimer);
236
+ this.flushTimer = null;
237
+ }
238
+ if (this.retentionTimer) {
239
+ clearInterval(this.retentionTimer);
240
+ this.retentionTimer = null;
241
+ }
242
+ try {
243
+ await this.flush();
244
+ } catch {
245
+ }
246
+ }
247
+ // ──────────────────────────────────────────────────────────────────────────
248
+ // Retention
249
+ // ──────────────────────────────────────────────────────────────────────────
250
+ async runRetention() {
251
+ if (this.closed) {
252
+ return;
253
+ }
254
+ const now = Date.now();
255
+ try {
256
+ if (this.retention.maxAgeDebug > 0) {
257
+ await this.deleteByLevelOlderThan(["trace", "debug"], now - this.retention.maxAgeDebug);
258
+ } else {
259
+ await this.deleteByLevelOlderThan(["trace", "debug"], now + 1);
260
+ }
261
+ if (this.retention.maxAgeInfo > 0) {
262
+ await this.deleteByLevelOlderThan(["info"], now - this.retention.maxAgeInfo);
263
+ }
264
+ if (this.retention.maxAge > 0) {
265
+ await this.deleteByLevelOlderThan(["warn", "error", "fatal"], now - this.retention.maxAge);
266
+ }
267
+ } catch {
268
+ }
269
+ }
270
+ };
271
+ var buildFilter = (query) => {
272
+ const clauses = [];
273
+ if (query.level !== void 0) {
274
+ clauses.push({ level: { $in: LEVELS_AT_OR_ABOVE(query.level) } });
275
+ }
276
+ if (query.source !== void 0) {
277
+ clauses.push({ source: { $eq: query.source } });
278
+ }
279
+ if (query.from !== void 0) {
280
+ clauses.push({ timestamp: { $gte: query.from } });
281
+ }
282
+ if (query.to !== void 0) {
283
+ clauses.push({ timestamp: { $lte: query.to } });
284
+ }
285
+ if (clauses.length === 0) {
286
+ return {};
287
+ }
288
+ if (clauses.length === 1) {
289
+ return clauses[0];
290
+ }
291
+ return { $and: clauses };
292
+ };
293
+ var toLogRecord = (doc) => ({
294
+ id: doc.logId,
295
+ timestamp: doc.timestamp,
296
+ level: doc.level,
297
+ message: doc.message,
298
+ fields: doc.fields,
299
+ source: doc.source
300
+ });
301
+ function createAdapter(config, deps) {
302
+ const resolved = deps?.["documentDatabase"] ? { ...config, documentDatabase: deps["documentDatabase"] } : config;
303
+ return new DocumentLogPersistence(resolved);
304
+ }
305
+ var index_default = createAdapter;
306
+
307
+ export { DocumentLogPersistence, createAdapter, index_default as default, manifest };
308
+ //# sourceMappingURL=index.js.map
309
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/adapters-log-document/manifest\n *\n * Manifest for the Document-backed log persistence adapter. It depends on\n * the platform's `documentDatabase` service — register a concrete document\n * driver (sqlite, postgres, mongo) first and this adapter rides on top.\n */\n\nimport type { AdapterManifest } from '@kb-labs/sdk/adapters';\n\nexport const manifest: AdapterManifest = {\n manifestVersion: '1.0.0',\n id: 'log-document',\n name: 'Document-backed Log Persistence',\n version: '1.0.0',\n description:\n 'ILogPersistence implementation storing log entries in any IDocumentDatabase. Driver-agnostic.',\n author: 'KB Labs Team',\n license: 'KBPL-1.1',\n type: 'core',\n implements: 'ILogPersistence',\n contexts: ['workspace', 'process'],\n requires: { adapters: [{ id: 'documentDatabase', alias: 'documentDatabase' }] },\n capabilities: {\n batch: true,\n search: true,\n custom: {\n retentionPolicy: true,\n },\n },\n configSchema: {\n collection: {\n type: 'string',\n default: 'logs',\n description: 'Name of the document collection that stores log entries.',\n },\n batchSize: {\n type: 'number',\n default: 100,\n description: 'Batch size for buffered writes.',\n },\n flushInterval: {\n type: 'number',\n default: 5000,\n description: 'Maximum delay before a partial batch is flushed (ms).',\n },\n },\n};\n","/**\n * @module @kb-labs/adapters-log-document\n *\n * `ILogPersistence` implementation backed by any `IDocumentDatabase`.\n *\n * This is the dogfooded replacement for the old sqlite-only log persistence\n * adapter: log entries are now stored as documents in a `logs` collection\n * using the same abstraction every other plugin uses. The platform stays\n * driver-agnostic — switch from sqlite to postgres-JSONB and logs follow.\n *\n * Buffering: writes are queued in memory and flushed in batches to amortise\n * per-call overhead. `close()` (and the next flush after batchSize entries)\n * drains the queue.\n *\n * Retention: a periodic timer deletes documents older than the configured\n * thresholds per log level. Without retention the collection grows\n * unbounded — defaults are applied if `retention` is absent.\n *\n * Search: implemented via `$contains` on the message field. Full-text\n * indexing (FTS5, MongoDB text) is intentionally OUT of scope here — that's\n * a backend-specific capability and would leak through the contract. If\n * advanced search is needed, route it via an external index (Meilisearch\n * etc.) and keep this layer simple.\n */\n\nimport type {\n ILogPersistence,\n LogPersistenceConfig,\n LogRetentionPolicy,\n LogRecord,\n LogQuery,\n LogLevel,\n IDocumentDatabase,\n DocumentFilter,\n BaseDocument,\n} from '@kb-labs/sdk/adapters';\n\nexport { manifest } from './manifest.js';\n\ninterface LogDoc extends BaseDocument {\n logId: string;\n timestamp: number;\n level: LogLevel;\n message: string;\n fields: Record<string, unknown>;\n source: string;\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n trace: 0,\n debug: 1,\n info: 2,\n warn: 3,\n error: 4,\n fatal: 5,\n};\n\nconst LEVELS_AT_OR_ABOVE = (min: LogLevel): LogLevel[] => {\n const threshold = LEVEL_ORDER[min];\n return (Object.keys(LEVEL_ORDER) as LogLevel[]).filter((l) => LEVEL_ORDER[l] >= threshold);\n};\n\nconst DEFAULT_RETENTION: Required<Pick<LogRetentionPolicy,\n 'maxAge' | 'maxAgeDebug' | 'maxAgeInfo' | 'maxSizeBytes' | 'cleanupIntervalMs'>> = {\n maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days for warn/error/fatal\n maxAgeDebug: 60 * 60 * 1000, // 1 hour for debug/trace\n maxAgeInfo: 24 * 60 * 60 * 1000, // 24 hours for info\n maxSizeBytes: 500 * 1024 * 1024, // 500 MB\n cleanupIntervalMs: 5 * 60 * 1000, // 5 minutes\n};\n\nexport class DocumentLogPersistence implements ILogPersistence {\n private readonly docs: IDocumentDatabase;\n private readonly collection: string;\n private readonly batchSize: number;\n private readonly flushIntervalMs: number;\n private readonly retention: Required<Pick<LogRetentionPolicy,\n 'maxAge' | 'maxAgeDebug' | 'maxAgeInfo' | 'maxSizeBytes' | 'cleanupIntervalMs'>>;\n\n private buffer: LogRecord[] = [];\n private flushTimer: NodeJS.Timeout | null = null;\n private retentionTimer: NodeJS.Timeout | null = null;\n private closed = false;\n private flushing: Promise<void> | null = null;\n private initialised: Promise<void>;\n\n constructor(config: LogPersistenceConfig) {\n this.docs = config.documentDatabase;\n this.collection = config.collection ?? 'logs';\n this.batchSize = config.batchSize ?? 100;\n this.flushIntervalMs = config.flushInterval ?? 5_000;\n this.retention = { ...DEFAULT_RETENTION, ...(config.retention ?? {}) };\n\n this.initialised = this.docs.ensureCollection(this.collection, {\n indexes: [\n { path: 'logId', unique: true },\n { path: 'timestamp' },\n { path: 'level' },\n { path: 'source' },\n ],\n });\n\n this.flushTimer = setInterval(() => { void this.flush(); }, this.flushIntervalMs).unref();\n this.retentionTimer = setInterval(() => { void this.runRetention(); }, this.retention.cleanupIntervalMs).unref();\n }\n\n // ──────────────────────────────────────────────────────────────────────────\n // Write path (buffered)\n // ──────────────────────────────────────────────────────────────────────────\n\n async write(record: LogRecord): Promise<void> {\n if (this.closed) {throw new Error('DocumentLogPersistence is closed');}\n this.buffer.push(record);\n if (this.buffer.length >= this.batchSize) {\n void this.flush();\n }\n }\n\n async writeBatch(records: LogRecord[]): Promise<void> {\n if (this.closed) {throw new Error('DocumentLogPersistence is closed');}\n if (records.length === 0) {return;}\n this.buffer.push(...records);\n if (this.buffer.length >= this.batchSize) {\n void this.flush();\n }\n }\n\n private async flush(): Promise<void> {\n if (this.flushing) {\n await this.flushing;\n return;\n }\n if (this.buffer.length === 0) {return;}\n const batch = this.buffer.splice(0, this.buffer.length);\n\n this.flushing = (async () => {\n try {\n await this.initialised;\n await this.docs.insertMany<LogDoc>(this.collection, batch.map((r) => ({\n logId: r.id,\n timestamp: r.timestamp,\n level: r.level,\n message: r.message,\n fields: r.fields,\n source: r.source,\n })));\n } catch (err) {\n // Re-queue on failure so the next attempt picks up the lost batch.\n this.buffer.unshift(...batch);\n throw err;\n } finally {\n this.flushing = null;\n }\n })();\n\n await this.flushing;\n }\n\n // ──────────────────────────────────────────────────────────────────────────\n // Read path\n // ──────────────────────────────────────────────────────────────────────────\n\n async query(\n query: LogQuery,\n options?: {\n limit?: number;\n offset?: number;\n sortBy?: 'timestamp' | 'level';\n sortOrder?: 'asc' | 'desc';\n },\n ): Promise<{ logs: LogRecord[]; total: number; hasMore: boolean }> {\n await this.initialised;\n const filter = buildFilter(query);\n const limit = options?.limit ?? 100;\n const offset = options?.offset ?? 0;\n const sortField = options?.sortBy ?? 'timestamp';\n const sortDir = options?.sortOrder === 'asc' ? 1 : -1;\n\n const [total, docs] = await Promise.all([\n this.docs.count<LogDoc>(this.collection, filter),\n this.docs.find<LogDoc>(this.collection, filter, {\n sort: { [sortField]: sortDir },\n limit,\n skip: offset,\n }),\n ]);\n\n return {\n logs: docs.map(toLogRecord),\n total,\n hasMore: offset + docs.length < total,\n };\n }\n\n async getById(id: string): Promise<LogRecord | null> {\n await this.initialised;\n const docs = await this.docs.find<LogDoc>(this.collection, { logId: { $eq: id } }, { limit: 1 });\n const first = docs[0];\n return first ? toLogRecord(first) : null;\n }\n\n async search(\n searchText: string,\n options?: { limit?: number; offset?: number },\n ): Promise<{ logs: LogRecord[]; total: number; hasMore: boolean }> {\n await this.initialised;\n const filter: DocumentFilter<LogDoc> = { message: { $contains: searchText } };\n const limit = options?.limit ?? 100;\n const offset = options?.offset ?? 0;\n\n const [total, docs] = await Promise.all([\n this.docs.count<LogDoc>(this.collection, filter),\n this.docs.find<LogDoc>(this.collection, filter, {\n sort: { timestamp: -1 },\n limit,\n skip: offset,\n }),\n ]);\n\n return {\n logs: docs.map(toLogRecord),\n total,\n hasMore: offset + docs.length < total,\n };\n }\n\n async deleteOlderThan(beforeTimestamp: number): Promise<number> {\n await this.initialised;\n return this.docs.deleteMany<LogDoc>(this.collection, {\n timestamp: { $lt: beforeTimestamp },\n });\n }\n\n async deleteByLevelOlderThan(levels: string[], beforeTimestamp: number): Promise<number> {\n await this.initialised;\n return this.docs.deleteMany<LogDoc>(this.collection, {\n $and: [\n { level: { $in: levels as LogLevel[] } },\n { timestamp: { $lt: beforeTimestamp } },\n ],\n });\n }\n\n async getStats(): Promise<{\n totalLogs: number;\n oldestTimestamp: number;\n newestTimestamp: number;\n sizeBytes: number;\n }> {\n await this.initialised;\n const [totalLogs, oldest, newest] = await Promise.all([\n this.docs.count<LogDoc>(this.collection, {}),\n this.docs.find<LogDoc>(this.collection, {}, { sort: { timestamp: 1 }, limit: 1 }),\n this.docs.find<LogDoc>(this.collection, {}, { sort: { timestamp: -1 }, limit: 1 }),\n ]);\n // Size is not exposed by IDocumentDatabase by design (it varies wildly\n // by backend). Returning 0 here is honest — callers driving retention\n // by size should configure maxAge instead and stop pretending bytes are\n // a universal currency across drivers.\n return {\n totalLogs,\n oldestTimestamp: oldest[0]?.timestamp ?? 0,\n newestTimestamp: newest[0]?.timestamp ?? 0,\n sizeBytes: 0,\n };\n }\n\n async close(): Promise<void> {\n if (this.closed) {return;}\n this.closed = true;\n if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; }\n if (this.retentionTimer) { clearInterval(this.retentionTimer); this.retentionTimer = null; }\n try {\n await this.flush();\n } catch {\n /* swallow — we tried */\n }\n }\n\n // ──────────────────────────────────────────────────────────────────────────\n // Retention\n // ──────────────────────────────────────────────────────────────────────────\n\n private async runRetention(): Promise<void> {\n if (this.closed) {return;}\n const now = Date.now();\n try {\n if (this.retention.maxAgeDebug > 0) {\n await this.deleteByLevelOlderThan(['trace', 'debug'], now - this.retention.maxAgeDebug);\n } else {\n // maxAgeDebug = 0 means do not persist debug/trace at all → wipe immediately.\n await this.deleteByLevelOlderThan(['trace', 'debug'], now + 1);\n }\n if (this.retention.maxAgeInfo > 0) {\n await this.deleteByLevelOlderThan(['info'], now - this.retention.maxAgeInfo);\n }\n if (this.retention.maxAge > 0) {\n await this.deleteByLevelOlderThan(['warn', 'error', 'fatal'], now - this.retention.maxAge);\n }\n } catch {\n /* retention failures should not propagate — they're best-effort cleanup */\n }\n }\n}\n\nconst buildFilter = (query: LogQuery): DocumentFilter<LogDoc> => {\n const clauses: Array<DocumentFilter<LogDoc>> = [];\n if (query.level !== undefined) {\n clauses.push({ level: { $in: LEVELS_AT_OR_ABOVE(query.level) } });\n }\n if (query.source !== undefined) {\n clauses.push({ source: { $eq: query.source } });\n }\n if (query.from !== undefined) {\n clauses.push({ timestamp: { $gte: query.from } });\n }\n if (query.to !== undefined) {\n clauses.push({ timestamp: { $lte: query.to } });\n }\n if (clauses.length === 0) {return {};}\n if (clauses.length === 1) {return clauses[0]!;}\n return { $and: clauses };\n};\n\nconst toLogRecord = (doc: LogDoc): LogRecord => ({\n id: doc.logId,\n timestamp: doc.timestamp,\n level: doc.level,\n message: doc.message,\n fields: doc.fields,\n source: doc.source,\n});\n\nexport function createAdapter(\n config: LogPersistenceConfig,\n deps?: Record<string, unknown>,\n): DocumentLogPersistence {\n // deps.documentDatabase is injected by the adapter-loader when the manifest\n // declares requires.adapters = [{ id: \"documentDatabase\" }]. Merge it in so\n // callers don't need to duplicate the reference in adapterOptions config.\n const resolved: LogPersistenceConfig = deps?.['documentDatabase']\n ? { ...config, documentDatabase: deps['documentDatabase'] as IDocumentDatabase }\n : config;\n return new DocumentLogPersistence(resolved);\n}\n\nexport default createAdapter;\n"]}
@@ -0,0 +1,13 @@
1
+ import { AdapterManifest } from '@kb-labs/sdk/adapters';
2
+
3
+ /**
4
+ * @module @kb-labs/adapters-log-document/manifest
5
+ *
6
+ * Manifest for the Document-backed log persistence adapter. It depends on
7
+ * the platform's `documentDatabase` service — register a concrete document
8
+ * driver (sqlite, postgres, mongo) first and this adapter rides on top.
9
+ */
10
+
11
+ declare const manifest: AdapterManifest;
12
+
13
+ export { manifest };
@@ -0,0 +1,42 @@
1
+ // src/manifest.ts
2
+ var manifest = {
3
+ manifestVersion: "1.0.0",
4
+ id: "log-document",
5
+ name: "Document-backed Log Persistence",
6
+ version: "1.0.0",
7
+ description: "ILogPersistence implementation storing log entries in any IDocumentDatabase. Driver-agnostic.",
8
+ author: "KB Labs Team",
9
+ license: "KBPL-1.1",
10
+ type: "core",
11
+ implements: "ILogPersistence",
12
+ contexts: ["workspace", "process"],
13
+ requires: { adapters: [{ id: "documentDatabase", alias: "documentDatabase" }] },
14
+ capabilities: {
15
+ batch: true,
16
+ search: true,
17
+ custom: {
18
+ retentionPolicy: true
19
+ }
20
+ },
21
+ configSchema: {
22
+ collection: {
23
+ type: "string",
24
+ default: "logs",
25
+ description: "Name of the document collection that stores log entries."
26
+ },
27
+ batchSize: {
28
+ type: "number",
29
+ default: 100,
30
+ description: "Batch size for buffered writes."
31
+ },
32
+ flushInterval: {
33
+ type: "number",
34
+ default: 5e3,
35
+ description: "Maximum delay before a partial batch is flushed (ms)."
36
+ }
37
+ }
38
+ };
39
+
40
+ export { manifest };
41
+ //# sourceMappingURL=manifest.js.map
42
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":";AAUO,IAAM,QAAA,GAA4B;AAAA,EACvC,eAAA,EAAiB,OAAA;AAAA,EACjB,EAAA,EAAI,cAAA;AAAA,EACJ,IAAA,EAAM,iCAAA;AAAA,EACN,OAAA,EAAS,OAAA;AAAA,EACT,WAAA,EACE,+FAAA;AAAA,EACF,MAAA,EAAQ,cAAA;AAAA,EACR,OAAA,EAAS,UAAA;AAAA,EACT,IAAA,EAAM,MAAA;AAAA,EACN,UAAA,EAAY,iBAAA;AAAA,EACZ,QAAA,EAAU,CAAC,WAAA,EAAa,SAAS,CAAA;AAAA,EACjC,QAAA,EAAU,EAAE,QAAA,EAAU,CAAC,EAAE,IAAI,kBAAA,EAAoB,KAAA,EAAO,kBAAA,EAAoB,CAAA,EAAE;AAAA,EAC9E,YAAA,EAAc;AAAA,IACZ,KAAA,EAAO,IAAA;AAAA,IACP,MAAA,EAAQ,IAAA;AAAA,IACR,MAAA,EAAQ;AAAA,MACN,eAAA,EAAiB;AAAA;AACnB,GACF;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,UAAA,EAAY;AAAA,MACV,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS,MAAA;AAAA,MACT,WAAA,EAAa;AAAA,KACf;AAAA,IACA,SAAA,EAAW;AAAA,MACT,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS,GAAA;AAAA,MACT,WAAA,EAAa;AAAA,KACf;AAAA,IACA,aAAA,EAAe;AAAA,MACb,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS,GAAA;AAAA,MACT,WAAA,EAAa;AAAA;AACf;AAEJ","file":"manifest.js","sourcesContent":["/**\n * @module @kb-labs/adapters-log-document/manifest\n *\n * Manifest for the Document-backed log persistence adapter. It depends on\n * the platform's `documentDatabase` service — register a concrete document\n * driver (sqlite, postgres, mongo) first and this adapter rides on top.\n */\n\nimport type { AdapterManifest } from '@kb-labs/sdk/adapters';\n\nexport const manifest: AdapterManifest = {\n manifestVersion: '1.0.0',\n id: 'log-document',\n name: 'Document-backed Log Persistence',\n version: '1.0.0',\n description:\n 'ILogPersistence implementation storing log entries in any IDocumentDatabase. Driver-agnostic.',\n author: 'KB Labs Team',\n license: 'KBPL-1.1',\n type: 'core',\n implements: 'ILogPersistence',\n contexts: ['workspace', 'process'],\n requires: { adapters: [{ id: 'documentDatabase', alias: 'documentDatabase' }] },\n capabilities: {\n batch: true,\n search: true,\n custom: {\n retentionPolicy: true,\n },\n },\n configSchema: {\n collection: {\n type: 'string',\n default: 'logs',\n description: 'Name of the document collection that stores log entries.',\n },\n batchSize: {\n type: 'number',\n default: 100,\n description: 'Batch size for buffered writes.',\n },\n flushInterval: {\n type: 'number',\n default: 5000,\n description: 'Maximum delay before a partial batch is flushed (ms).',\n },\n },\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@kb-labs/adapters-log-document",
3
+ "description": "ILogPersistence implementation backed by IDocumentDatabase (dogfooded — same abstraction the rest of the platform uses).",
4
+ "version": "2.96.0",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "devDependencies": {
19
+ "@types/node": "^24.3.3",
20
+ "eslint": "^9",
21
+ "rimraf": "^6.0.1",
22
+ "tsup": "^8.5.0",
23
+ "typescript": "^5.6.3",
24
+ "vitest": "^3.2.6",
25
+ "@kb-labs/devkit": "2.96.0",
26
+ "@kb-labs/sdk": "2.21.2",
27
+ "@kb-labs/adapters-sqlite": "2.96.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=20.0.0",
31
+ "pnpm": ">=9.0.0"
32
+ },
33
+ "kb": {
34
+ "manifest": "./dist/manifest.js"
35
+ },
36
+ "peerDependencies": {
37
+ "@kb-labs/sdk": "^2.21.2"
38
+ },
39
+ "sideEffects": false,
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "clean": "rimraf dist",
43
+ "dev": "tsup --watch",
44
+ "lint": "eslint src --ext .ts",
45
+ "lint:fix": "eslint . --fix",
46
+ "test": "vitest run --passWithNoTests",
47
+ "test:watch": "vitest",
48
+ "type-check": "tsc --noEmit"
49
+ }
50
+ }