@kb-labs/adapters 0.5.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/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/CONTRIBUTING.md +90 -0
- package/IMPLEMENTATION_COMPLETE.md +416 -0
- package/LICENSE +186 -0
- package/README-TEMPLATE.md +179 -0
- package/README.md +306 -0
- package/docs/DOCUMENTATION.md +74 -0
- package/docs/adr/0000-template.md +49 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
- package/docs/adr/0002-plugins-and-extensibility.md +46 -0
- package/docs/adr/0003-package-and-module-boundaries.md +37 -0
- package/docs/adr/0004-versioning-and-release-policy.md +38 -0
- package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
- package/docs/adr/0006-adopt-devkit-sync.md +47 -0
- package/docs/adr/0007-drift-kit-check.md +72 -0
- package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
- package/docs/naming-convention.md +272 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +84 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/adapters-analytics-duckdb/package.json +54 -0
- package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
- package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
- package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
- package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
- package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
- package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
- package/packages/adapters-analytics-file/README.md +32 -0
- package/packages/adapters-analytics-file/eslint.config.js +27 -0
- package/packages/adapters-analytics-file/package.json +50 -0
- package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
- package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
- package/packages/adapters-analytics-file/src/index.test.ts +214 -0
- package/packages/adapters-analytics-file/src/index.ts +830 -0
- package/packages/adapters-analytics-file/src/manifest.ts +45 -0
- package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-file/tsconfig.json +9 -0
- package/packages/adapters-analytics-file/tsup.config.ts +9 -0
- package/packages/adapters-analytics-sqlite/package.json +55 -0
- package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
- package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
- package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
- package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
- package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-environment-docker/README.md +28 -0
- package/packages/adapters-environment-docker/eslint.config.js +5 -0
- package/packages/adapters-environment-docker/package.json +49 -0
- package/packages/adapters-environment-docker/src/index.test.ts +138 -0
- package/packages/adapters-environment-docker/src/index.ts +439 -0
- package/packages/adapters-environment-docker/src/manifest.ts +65 -0
- package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
- package/packages/adapters-environment-docker/tsconfig.json +16 -0
- package/packages/adapters-environment-docker/tsup.config.ts +9 -0
- package/packages/adapters-eventbus-cache/README.md +242 -0
- package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
- package/packages/adapters-eventbus-cache/package.json +46 -0
- package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
- package/packages/adapters-eventbus-cache/src/index.ts +215 -0
- package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
- package/packages/adapters-eventbus-cache/src/types.ts +58 -0
- package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
- package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
- package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
- package/packages/adapters-fs/README.md +171 -0
- package/packages/adapters-fs/allowed.txt +1 -0
- package/packages/adapters-fs/conflict.txt +1 -0
- package/packages/adapters-fs/dest.txt +1 -0
- package/packages/adapters-fs/eslint.config.js +27 -0
- package/packages/adapters-fs/exists.txt +1 -0
- package/packages/adapters-fs/not-allowed.txt +1 -0
- package/packages/adapters-fs/other.txt +1 -0
- package/packages/adapters-fs/package.json +55 -0
- package/packages/adapters-fs/public/file1.txt +1 -0
- package/packages/adapters-fs/public/file2.txt +1 -0
- package/packages/adapters-fs/secret.txt +1 -0
- package/packages/adapters-fs/secrets/key.txt +1 -0
- package/packages/adapters-fs/src/index.test.ts +243 -0
- package/packages/adapters-fs/src/index.ts +258 -0
- package/packages/adapters-fs/src/manifest.ts +35 -0
- package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
- package/packages/adapters-fs/src/secure-storage.ts +268 -0
- package/packages/adapters-fs/test.json +1 -0
- package/packages/adapters-fs/test.txt +1 -0
- package/packages/adapters-fs/test.xyz +1 -0
- package/packages/adapters-fs/test1.txt +1 -0
- package/packages/adapters-fs/test2.txt +1 -0
- package/packages/adapters-fs/tsconfig.build.json +15 -0
- package/packages/adapters-fs/tsconfig.json +9 -0
- package/packages/adapters-fs/tsup.config.ts +8 -0
- package/packages/adapters-fs/vitest.config.ts +19 -0
- package/packages/adapters-log-ringbuffer/README.md +228 -0
- package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
- package/packages/adapters-log-ringbuffer/package.json +47 -0
- package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
- package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
- package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
- package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
- package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
- package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
- package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
- package/packages/adapters-log-sqlite/README.md +396 -0
- package/packages/adapters-log-sqlite/eslint.config.js +27 -0
- package/packages/adapters-log-sqlite/package.json +49 -0
- package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
- package/packages/adapters-log-sqlite/src/index.ts +1068 -0
- package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
- package/packages/adapters-log-sqlite/src/schema.sql +46 -0
- package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-log-sqlite/tsconfig.json +9 -0
- package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
- package/packages/adapters-mongodb/README.md +147 -0
- package/packages/adapters-mongodb/eslint.config.js +27 -0
- package/packages/adapters-mongodb/package.json +53 -0
- package/packages/adapters-mongodb/src/index.ts +428 -0
- package/packages/adapters-mongodb/src/manifest.ts +45 -0
- package/packages/adapters-mongodb/src/secure-document.ts +231 -0
- package/packages/adapters-mongodb/tsconfig.build.json +15 -0
- package/packages/adapters-mongodb/tsconfig.json +9 -0
- package/packages/adapters-mongodb/tsup.config.ts +8 -0
- package/packages/adapters-openai/README.md +151 -0
- package/packages/adapters-openai/embeddings.ts +37 -0
- package/packages/adapters-openai/eslint.config.js +26 -0
- package/packages/adapters-openai/index.ts +22 -0
- package/packages/adapters-openai/package.json +57 -0
- package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
- package/packages/adapters-openai/src/embeddings.ts +104 -0
- package/packages/adapters-openai/src/index.ts +13 -0
- package/packages/adapters-openai/src/llm.ts +304 -0
- package/packages/adapters-openai/src/manifest.ts +47 -0
- package/packages/adapters-openai/tsconfig.build.json +15 -0
- package/packages/adapters-openai/tsconfig.json +9 -0
- package/packages/adapters-openai/tsup.config.ts +8 -0
- package/packages/adapters-pino/README.md +152 -0
- package/packages/adapters-pino/eslint.config.js +27 -0
- package/packages/adapters-pino/package.json +49 -0
- package/packages/adapters-pino/src/index.test.ts +44 -0
- package/packages/adapters-pino/src/index.ts +322 -0
- package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
- package/packages/adapters-pino/src/manifest.ts +49 -0
- package/packages/adapters-pino/tsconfig.build.json +15 -0
- package/packages/adapters-pino/tsconfig.json +9 -0
- package/packages/adapters-pino/tsup.config.ts +9 -0
- package/packages/adapters-pino-http/README.md +141 -0
- package/packages/adapters-pino-http/eslint.config.js +27 -0
- package/packages/adapters-pino-http/package.json +46 -0
- package/packages/adapters-pino-http/src/index.ts +229 -0
- package/packages/adapters-pino-http/tsconfig.build.json +15 -0
- package/packages/adapters-pino-http/tsconfig.json +9 -0
- package/packages/adapters-pino-http/tsup.config.ts +9 -0
- package/packages/adapters-qdrant/README.md +166 -0
- package/packages/adapters-qdrant/eslint.config.js +27 -0
- package/packages/adapters-qdrant/package.json +49 -0
- package/packages/adapters-qdrant/src/index.ts +490 -0
- package/packages/adapters-qdrant/src/manifest.ts +54 -0
- package/packages/adapters-qdrant/src/retry.ts +204 -0
- package/packages/adapters-qdrant/tsconfig.build.json +15 -0
- package/packages/adapters-qdrant/tsconfig.json +9 -0
- package/packages/adapters-qdrant/tsup.config.ts +9 -0
- package/packages/adapters-redis/README.md +159 -0
- package/packages/adapters-redis/eslint.config.js +27 -0
- package/packages/adapters-redis/package.json +49 -0
- package/packages/adapters-redis/src/index.ts +164 -0
- package/packages/adapters-redis/src/manifest.ts +49 -0
- package/packages/adapters-redis/tsconfig.build.json +15 -0
- package/packages/adapters-redis/tsconfig.json +9 -0
- package/packages/adapters-redis/tsup.config.ts +9 -0
- package/packages/adapters-snapshot-localfs/README.md +10 -0
- package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
- package/packages/adapters-snapshot-localfs/package.json +46 -0
- package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
- package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
- package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
- package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
- package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
- package/packages/adapters-sqlite/README.md +163 -0
- package/packages/adapters-sqlite/eslint.config.js +27 -0
- package/packages/adapters-sqlite/package.json +54 -0
- package/packages/adapters-sqlite/src/index.test.ts +245 -0
- package/packages/adapters-sqlite/src/index.ts +382 -0
- package/packages/adapters-sqlite/src/manifest.ts +47 -0
- package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
- package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
- package/packages/adapters-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-sqlite/tsconfig.json +9 -0
- package/packages/adapters-sqlite/tsup.config.ts +8 -0
- package/packages/adapters-sqlite/vitest.config.ts +19 -0
- package/packages/adapters-transport/README.md +170 -0
- package/packages/adapters-transport/eslint.config.js +27 -0
- package/packages/adapters-transport/package.json +49 -0
- package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
- package/packages/adapters-transport/src/index.ts +101 -0
- package/packages/adapters-transport/src/ipc-transport.ts +228 -0
- package/packages/adapters-transport/src/transport.ts +224 -0
- package/packages/adapters-transport/src/types.ts +92 -0
- package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
- package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
- package/packages/adapters-transport/tsconfig.build.json +15 -0
- package/packages/adapters-transport/tsconfig.json +9 -0
- package/packages/adapters-transport/tsup.config.ts +9 -0
- package/packages/adapters-vibeproxy/README.md +159 -0
- package/packages/adapters-vibeproxy/eslint.config.js +27 -0
- package/packages/adapters-vibeproxy/package.json +51 -0
- package/packages/adapters-vibeproxy/src/index.ts +13 -0
- package/packages/adapters-vibeproxy/src/llm.ts +437 -0
- package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
- package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
- package/packages/adapters-vibeproxy/tsconfig.json +9 -0
- package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
- package/packages/adapters-workspace-agent/package.json +46 -0
- package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
- package/packages/adapters-workspace-agent/src/index.ts +220 -0
- package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
- package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-agent/tsconfig.json +16 -0
- package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
- package/packages/adapters-workspace-localfs/README.md +9 -0
- package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
- package/packages/adapters-workspace-localfs/package.json +46 -0
- package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
- package/packages/adapters-workspace-localfs/src/index.ts +172 -0
- package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
- package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
- package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
- package/packages/adapters-workspace-worktree/README.md +9 -0
- package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
- package/packages/adapters-workspace-worktree/package.json +46 -0
- package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
- package/packages/adapters-workspace-worktree/src/index.ts +245 -0
- package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
- package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
- package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +2800 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/test-integration.ts +242 -0
- package/test.txt +1 -0
- package/tsconfig.base.json +6 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +26 -0
- package/tsconfig.tools.json +17 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +103 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-analytics-duckdb
|
|
3
|
+
* DuckDB analytics adapter for KB Labs platform.
|
|
4
|
+
*
|
|
5
|
+
* Implements IAnalytics with SQL-native time-series aggregation:
|
|
6
|
+
* - date_trunc() for groupBy (hour/day/week/month)
|
|
7
|
+
* - json_extract_string() for breakdownBy (dot-notation paths)
|
|
8
|
+
* - Dynamic SELECT for metrics filtering
|
|
9
|
+
* - Full EventsQuery support via SQL WHERE clauses
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { DuckDBInstance } from '@duckdb/node-api';
|
|
13
|
+
import type { DuckDBValue } from '@duckdb/node-api';
|
|
14
|
+
import { join, isAbsolute } from 'node:path';
|
|
15
|
+
import type {
|
|
16
|
+
IAnalytics,
|
|
17
|
+
AnalyticsContext,
|
|
18
|
+
AnalyticsEvent,
|
|
19
|
+
EventsQuery,
|
|
20
|
+
StatsQuery,
|
|
21
|
+
EventsResponse,
|
|
22
|
+
EventsStats,
|
|
23
|
+
BufferStatus,
|
|
24
|
+
DlqStatus,
|
|
25
|
+
DailyStats,
|
|
26
|
+
} from '@kb-labs/core-platform/adapters';
|
|
27
|
+
import {
|
|
28
|
+
CREATE_EVENTS_TABLE,
|
|
29
|
+
CREATE_INDEXES,
|
|
30
|
+
GROUP_BY_TRUNC,
|
|
31
|
+
GROUP_BY_FORMAT,
|
|
32
|
+
dotPathToSQL,
|
|
33
|
+
getDefaultMetrics,
|
|
34
|
+
buildMetricsSelect,
|
|
35
|
+
} from './schema.js';
|
|
36
|
+
|
|
37
|
+
export interface DuckDBAnalyticsOptions {
|
|
38
|
+
/** Path to the DuckDB database file. Default: .kb/analytics/analytics.duckdb */
|
|
39
|
+
dbPath?: string;
|
|
40
|
+
/** Analytics context for event enrichment */
|
|
41
|
+
context?: AnalyticsContext;
|
|
42
|
+
/** Workspace context injected by core-runtime (provides cwd for relative path resolution) */
|
|
43
|
+
workspace?: { cwd: string };
|
|
44
|
+
/** Analytics context injected by core-runtime (for auto-enrichment) */
|
|
45
|
+
analytics?: AnalyticsContext;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* DuckDB-based analytics adapter.
|
|
50
|
+
* Stores events in a local DuckDB file for SQL-native analytics.
|
|
51
|
+
*/
|
|
52
|
+
export class DuckDBAnalytics implements IAnalytics {
|
|
53
|
+
private readonly dbPath: string;
|
|
54
|
+
private context: AnalyticsContext;
|
|
55
|
+
private instance: DuckDBInstance | null = null;
|
|
56
|
+
private initPromise: Promise<void> | null = null;
|
|
57
|
+
|
|
58
|
+
constructor(options: DuckDBAnalyticsOptions = {}) {
|
|
59
|
+
const cwd = options.workspace?.cwd ?? process.cwd();
|
|
60
|
+
const rawPath = options.dbPath ?? '.kb/analytics/analytics.duckdb';
|
|
61
|
+
this.dbPath = isAbsolute(rawPath) ? rawPath : join(cwd, rawPath);
|
|
62
|
+
this.context = options.context ?? options.analytics ?? {
|
|
63
|
+
source: { product: 'unknown', version: '0.0.0' },
|
|
64
|
+
runId: `run-${Date.now()}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
private async init(): Promise<DuckDBInstance> {
|
|
71
|
+
if (!this.initPromise) {
|
|
72
|
+
this.initPromise = this._setup();
|
|
73
|
+
}
|
|
74
|
+
await this.initPromise;
|
|
75
|
+
return this.instance!;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async _setup(): Promise<void> {
|
|
79
|
+
this.instance = await DuckDBInstance.fromCache(this.dbPath);
|
|
80
|
+
const conn = await this.instance.connect();
|
|
81
|
+
try {
|
|
82
|
+
await conn.run(CREATE_EVENTS_TABLE);
|
|
83
|
+
for (const idx of CREATE_INDEXES) {
|
|
84
|
+
await conn.run(idx);
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
conn.closeSync();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async withConnection<T>(fn: (conn: Awaited<ReturnType<DuckDBInstance['connect']>>) => Promise<T>): Promise<T> {
|
|
92
|
+
const db = await this.init();
|
|
93
|
+
const conn = await db.connect();
|
|
94
|
+
try {
|
|
95
|
+
return await fn(conn);
|
|
96
|
+
} finally {
|
|
97
|
+
conn.closeSync();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Write methods ────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
async track(event: string, properties?: Record<string, unknown>): Promise<void> {
|
|
104
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
105
|
+
const now = new Date().toISOString();
|
|
106
|
+
|
|
107
|
+
await this.withConnection(async (conn) => {
|
|
108
|
+
await conn.run(
|
|
109
|
+
`INSERT OR IGNORE INTO events
|
|
110
|
+
(id, schema, type, ts, ingest_ts, run_id, product, version, actor_type, actor_id, actor_name, ctx, payload)
|
|
111
|
+
VALUES ($1, 'kb.v1', $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
|
112
|
+
[
|
|
113
|
+
id,
|
|
114
|
+
event,
|
|
115
|
+
now,
|
|
116
|
+
now,
|
|
117
|
+
this.context.runId ?? null,
|
|
118
|
+
this.context.source.product,
|
|
119
|
+
this.context.source.version,
|
|
120
|
+
this.context.actor?.type ?? null,
|
|
121
|
+
this.context.actor?.id ?? null,
|
|
122
|
+
this.context.actor?.name ?? null,
|
|
123
|
+
this.context.ctx ? JSON.stringify(this.context.ctx) : null,
|
|
124
|
+
properties ? JSON.stringify(properties) : null,
|
|
125
|
+
],
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async identify(userId: string, traits?: Record<string, unknown>): Promise<void> {
|
|
131
|
+
await this.track('identity', { userId, ...traits });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async flush(): Promise<void> {
|
|
135
|
+
// DuckDB writes synchronously — nothing to flush
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Source attribution ───────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
getSource(): { product: string; version: string } | undefined {
|
|
141
|
+
return this.context.source;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setSource(source: { product: string; version: string }): void {
|
|
145
|
+
this.context = { ...this.context, source };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Read: Events ─────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async getEvents(query?: EventsQuery): Promise<EventsResponse> {
|
|
151
|
+
return this.withConnection(async (conn) => {
|
|
152
|
+
const { where, params } = buildWhereClause(query);
|
|
153
|
+
const limit = query?.limit ?? 100;
|
|
154
|
+
const offset = query?.offset ?? 0;
|
|
155
|
+
const whereClause = where ? ` WHERE ${where}` : '';
|
|
156
|
+
|
|
157
|
+
// Count total
|
|
158
|
+
const countResult = await conn.run(
|
|
159
|
+
`SELECT COUNT(*) as total FROM events${whereClause}`,
|
|
160
|
+
params,
|
|
161
|
+
);
|
|
162
|
+
const countRows = await countResult.getRowObjects();
|
|
163
|
+
const total = Number(countRows[0]?.total ?? 0);
|
|
164
|
+
|
|
165
|
+
// Fetch events
|
|
166
|
+
const result = await conn.run(
|
|
167
|
+
`SELECT * FROM events${whereClause} ORDER BY ts DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
168
|
+
params,
|
|
169
|
+
);
|
|
170
|
+
const rows = await result.getRowObjects();
|
|
171
|
+
const events: AnalyticsEvent[] = rows.map(rowToEvent);
|
|
172
|
+
|
|
173
|
+
return { events, total, hasMore: offset + events.length < total };
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Read: Stats ──────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
async getStats(): Promise<EventsStats> {
|
|
180
|
+
return this.withConnection(async (conn) => {
|
|
181
|
+
const totalResult = await conn.run(
|
|
182
|
+
`SELECT COUNT(*) as total, MIN(ts) as minTs, MAX(ts) as maxTs FROM events`,
|
|
183
|
+
);
|
|
184
|
+
const totalRows = await totalResult.getRowObjects();
|
|
185
|
+
const totalEvents = Number(totalRows[0]?.total ?? 0);
|
|
186
|
+
const from = String(totalRows[0]?.minTs ?? new Date().toISOString());
|
|
187
|
+
const to = String(totalRows[0]?.maxTs ?? new Date().toISOString());
|
|
188
|
+
|
|
189
|
+
const byTypeResult = await conn.run(
|
|
190
|
+
`SELECT type, COUNT(*) as cnt FROM events GROUP BY type`,
|
|
191
|
+
);
|
|
192
|
+
const byType: Record<string, number> = {};
|
|
193
|
+
for (const r of await byTypeResult.getRowObjects()) {
|
|
194
|
+
byType[String(r.type)] = Number(r.cnt);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const bySourceResult = await conn.run(
|
|
198
|
+
`SELECT COALESCE(product, 'unknown') as product, COUNT(*) as cnt FROM events GROUP BY product`,
|
|
199
|
+
);
|
|
200
|
+
const bySource: Record<string, number> = {};
|
|
201
|
+
for (const r of await bySourceResult.getRowObjects()) {
|
|
202
|
+
bySource[String(r.product)] = Number(r.cnt);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const byActorResult = await conn.run(
|
|
206
|
+
`SELECT COALESCE(actor_id, 'unknown') as actor, COUNT(*) as cnt FROM events GROUP BY actor`,
|
|
207
|
+
);
|
|
208
|
+
const byActor: Record<string, number> = {};
|
|
209
|
+
for (const r of await byActorResult.getRowObjects()) {
|
|
210
|
+
byActor[String(r.actor)] = Number(r.cnt);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { totalEvents, byType, bySource, byActor, timeRange: { from, to } };
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── Read: Time-series ────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
async getDailyStats(query?: StatsQuery): Promise<DailyStats[]> {
|
|
220
|
+
return this.withConnection(async (conn) => {
|
|
221
|
+
const groupBy = query?.groupBy ?? 'day';
|
|
222
|
+
const truncUnit = GROUP_BY_TRUNC[groupBy] ?? 'day';
|
|
223
|
+
const fmtStr = GROUP_BY_FORMAT[groupBy] ?? '%Y-%m-%d';
|
|
224
|
+
|
|
225
|
+
const breakdownBy = query?.breakdownBy;
|
|
226
|
+
const breakdownSQL = breakdownBy ? dotPathToSQL(breakdownBy) : null;
|
|
227
|
+
|
|
228
|
+
const metricsFilter = query?.metrics ?? getDefaultMetrics(query?.type);
|
|
229
|
+
const metricsSelect = buildMetricsSelect(metricsFilter);
|
|
230
|
+
|
|
231
|
+
const { where, params } = buildWhereClause(query);
|
|
232
|
+
const whereClause = where ? `WHERE ${where}` : '';
|
|
233
|
+
|
|
234
|
+
// Build SELECT
|
|
235
|
+
const selectParts = [
|
|
236
|
+
`strftime(date_trunc('${truncUnit}', ts), '${fmtStr}') as date`,
|
|
237
|
+
`COUNT(*) as count`,
|
|
238
|
+
...metricsSelect,
|
|
239
|
+
...(breakdownSQL ? [`${breakdownSQL} as breakdown`] : []),
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
// Build GROUP BY
|
|
243
|
+
const groupByParts = [`date_trunc('${truncUnit}', ts)`];
|
|
244
|
+
if (breakdownSQL) {groupByParts.push(breakdownSQL);}
|
|
245
|
+
|
|
246
|
+
const sql = `
|
|
247
|
+
SELECT ${selectParts.join(', ')}
|
|
248
|
+
FROM events
|
|
249
|
+
${whereClause}
|
|
250
|
+
GROUP BY ${groupByParts.join(', ')}
|
|
251
|
+
ORDER BY date_trunc('${truncUnit}', ts) ASC${breakdownSQL ? ', breakdown ASC NULLS LAST' : ''}
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
const result = await conn.run(sql, params);
|
|
255
|
+
const rows = await result.getRowObjects();
|
|
256
|
+
|
|
257
|
+
return rows.map((r): DailyStats => {
|
|
258
|
+
const metrics: Record<string, number> = {};
|
|
259
|
+
for (const m of metricsFilter) {
|
|
260
|
+
const v = r[m];
|
|
261
|
+
if (v !== null && v !== undefined) {
|
|
262
|
+
metrics[m] = Number(v);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const stat: DailyStats = {
|
|
267
|
+
date: String(r.date),
|
|
268
|
+
count: Number(r.count),
|
|
269
|
+
};
|
|
270
|
+
if (Object.keys(metrics).length > 0) {stat.metrics = metrics;}
|
|
271
|
+
if (breakdownSQL && r.breakdown !== null && r.breakdown !== undefined) {
|
|
272
|
+
stat.breakdown = String(r.breakdown);
|
|
273
|
+
}
|
|
274
|
+
return stat;
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ─── Read: Buffer / DLQ ──────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
async getBufferStatus(): Promise<BufferStatus | null> {
|
|
282
|
+
return this.withConnection(async (conn) => {
|
|
283
|
+
const result = await conn.run(
|
|
284
|
+
`SELECT COUNT(*) as segments, MIN(ts) as oldest, MAX(ts) as newest FROM events`,
|
|
285
|
+
);
|
|
286
|
+
const rows = await result.getRowObjects();
|
|
287
|
+
const row = rows[0];
|
|
288
|
+
return {
|
|
289
|
+
segments: Number(row?.segments ?? 0),
|
|
290
|
+
totalSizeBytes: 0, // DuckDB doesn't expose file size here easily
|
|
291
|
+
oldestEventTs: row?.oldest ? String(row.oldest) : null,
|
|
292
|
+
newestEventTs: row?.newest ? String(row.newest) : null,
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async getDlqStatus(): Promise<DlqStatus | null> {
|
|
298
|
+
return null; // DuckDB adapter has no DLQ
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ─── SQL helpers ──────────────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
function buildWhereClause(query?: EventsQuery): { where: string; params: DuckDBValue[] } {
|
|
305
|
+
if (!query) {return { where: '', params: [] };}
|
|
306
|
+
|
|
307
|
+
const clauses: string[] = [];
|
|
308
|
+
const params: DuckDBValue[] = [];
|
|
309
|
+
let idx = 1;
|
|
310
|
+
|
|
311
|
+
if (query.type) {
|
|
312
|
+
const types: string[] = Array.isArray(query.type) ? query.type : [query.type];
|
|
313
|
+
if (types.length === 1) {
|
|
314
|
+
clauses.push(`type = $${idx++}`);
|
|
315
|
+
params.push(types[0] as string);
|
|
316
|
+
} else {
|
|
317
|
+
const placeholders = types.map(() => `$${idx++}`).join(', ');
|
|
318
|
+
clauses.push(`type IN (${placeholders})`);
|
|
319
|
+
params.push(...(types as string[]));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (query.source) {
|
|
324
|
+
clauses.push(`product = $${idx++}`);
|
|
325
|
+
params.push(query.source);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (query.actor) {
|
|
329
|
+
clauses.push(`actor_id = $${idx++}`);
|
|
330
|
+
params.push(query.actor);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (query.from) {
|
|
334
|
+
clauses.push(`ts >= $${idx++}::TIMESTAMPTZ`);
|
|
335
|
+
params.push(query.from);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (query.to) {
|
|
339
|
+
clauses.push(`ts <= $${idx++}::TIMESTAMPTZ`);
|
|
340
|
+
params.push(query.to);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return { where: clauses.join(' AND '), params };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function rowToEvent(r: Record<string, unknown>): AnalyticsEvent {
|
|
347
|
+
return {
|
|
348
|
+
id: String(r.id),
|
|
349
|
+
schema: 'kb.v1',
|
|
350
|
+
type: String(r.type),
|
|
351
|
+
ts: String(r.ts),
|
|
352
|
+
ingestTs: String(r.ingest_ts ?? r.ts),
|
|
353
|
+
source: {
|
|
354
|
+
product: String(r.product ?? ''),
|
|
355
|
+
version: String(r.version ?? ''),
|
|
356
|
+
},
|
|
357
|
+
runId: String(r.run_id ?? ''),
|
|
358
|
+
actor: r.actor_type
|
|
359
|
+
? {
|
|
360
|
+
type: r.actor_type as 'user' | 'agent' | 'ci',
|
|
361
|
+
id: r.actor_id ? String(r.actor_id) : undefined,
|
|
362
|
+
name: r.actor_name ? String(r.actor_name) : undefined,
|
|
363
|
+
}
|
|
364
|
+
: undefined,
|
|
365
|
+
ctx: r.ctx ? (JSON.parse(String(r.ctx)) as Record<string, string | number | boolean | null>) : undefined,
|
|
366
|
+
payload: r.payload ? JSON.parse(String(r.payload)) : undefined,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export { manifest } from './manifest.js';
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Factory function required by core-runtime adapter discovery.
|
|
374
|
+
* Called by platform loader with config options from kb.config.json adapterOptions.analytics
|
|
375
|
+
*/
|
|
376
|
+
export function createAdapter(options?: DuckDBAnalyticsOptions): IAnalytics {
|
|
377
|
+
return new DuckDBAnalytics(options);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export default createAdapter;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-analytics-duckdb/manifest
|
|
3
|
+
* Adapter manifest for DuckDB analytics adapter.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AdapterManifest } from '@kb-labs/core-platform';
|
|
7
|
+
|
|
8
|
+
export const manifest: AdapterManifest = {
|
|
9
|
+
manifestVersion: '1.0.0',
|
|
10
|
+
id: 'analytics-duckdb',
|
|
11
|
+
name: 'DuckDB Analytics',
|
|
12
|
+
version: '0.1.0',
|
|
13
|
+
description: 'DuckDB-based analytics adapter — SQL-native time-series, groupBy, breakdownBy, metrics filtering',
|
|
14
|
+
author: 'KB Labs Team',
|
|
15
|
+
license: 'KBPL-1.1',
|
|
16
|
+
type: 'core',
|
|
17
|
+
implements: 'IAnalytics',
|
|
18
|
+
contexts: ['workspace', 'analytics'],
|
|
19
|
+
capabilities: {
|
|
20
|
+
search: true,
|
|
21
|
+
custom: {
|
|
22
|
+
offline: true,
|
|
23
|
+
stats: true,
|
|
24
|
+
sql: true,
|
|
25
|
+
groupBy: true,
|
|
26
|
+
breakdownBy: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
configSchema: {
|
|
30
|
+
dbPath: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
default: '.kb/analytics/analytics.duckdb',
|
|
33
|
+
description: 'Path to the DuckDB database file',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-analytics-duckdb/schema
|
|
3
|
+
* DuckDB schema DDL and helpers for analytics events (kb.v1)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DDL to create the events table.
|
|
8
|
+
* Maps kb.v1 event schema to flat columns for efficient SQL queries.
|
|
9
|
+
* JSON columns (ctx, payload) allow json_extract_string() on arbitrary fields.
|
|
10
|
+
*/
|
|
11
|
+
export const CREATE_EVENTS_TABLE = `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
13
|
+
id VARCHAR PRIMARY KEY,
|
|
14
|
+
schema VARCHAR NOT NULL DEFAULT 'kb.v1',
|
|
15
|
+
type VARCHAR NOT NULL,
|
|
16
|
+
ts TIMESTAMPTZ NOT NULL,
|
|
17
|
+
ingest_ts TIMESTAMPTZ,
|
|
18
|
+
run_id VARCHAR,
|
|
19
|
+
product VARCHAR,
|
|
20
|
+
version VARCHAR,
|
|
21
|
+
actor_type VARCHAR,
|
|
22
|
+
actor_id VARCHAR,
|
|
23
|
+
actor_name VARCHAR,
|
|
24
|
+
ctx JSON,
|
|
25
|
+
payload JSON
|
|
26
|
+
)
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Indexes for fast range and filter queries.
|
|
31
|
+
*/
|
|
32
|
+
export const CREATE_INDEXES = [
|
|
33
|
+
`CREATE INDEX IF NOT EXISTS events_ts_idx ON events (ts)`,
|
|
34
|
+
`CREATE INDEX IF NOT EXISTS events_type_idx ON events (type)`,
|
|
35
|
+
`CREATE INDEX IF NOT EXISTS events_product_idx ON events (product)`,
|
|
36
|
+
`CREATE INDEX IF NOT EXISTS events_type_ts_idx ON events (type, ts)`,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Map groupBy granularity to DuckDB date_trunc unit.
|
|
41
|
+
*/
|
|
42
|
+
export const GROUP_BY_TRUNC: Record<string, string> = {
|
|
43
|
+
hour: 'hour',
|
|
44
|
+
day: 'day',
|
|
45
|
+
week: 'week',
|
|
46
|
+
month: 'month',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default date format per groupBy granularity (used for strftime in DuckDB).
|
|
51
|
+
* DuckDB strftime format strings.
|
|
52
|
+
*/
|
|
53
|
+
export const GROUP_BY_FORMAT: Record<string, string> = {
|
|
54
|
+
hour: '%Y-%m-%dT%H',
|
|
55
|
+
day: '%Y-%m-%d',
|
|
56
|
+
week: '%Y-W%W',
|
|
57
|
+
month: '%Y-%m',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const DIRECT_COLUMNS: Record<string, string> = {
|
|
61
|
+
product: 'product',
|
|
62
|
+
version: 'version',
|
|
63
|
+
type: 'type',
|
|
64
|
+
run_id: 'run_id',
|
|
65
|
+
runId: 'run_id',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const ACTOR_COLUMNS: Record<string, string> = {
|
|
69
|
+
type: 'actor_type',
|
|
70
|
+
id: 'actor_id',
|
|
71
|
+
name: 'actor_name',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const SOURCE_COLUMNS: Record<string, string> = {
|
|
75
|
+
product: 'product',
|
|
76
|
+
version: 'version',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Convert dot-notation path (e.g. 'payload.model') to DuckDB JSON path expression.
|
|
81
|
+
* 'payload.model' → json_extract_string(payload, '$.model')
|
|
82
|
+
* 'source.product' → handled as direct column (product)
|
|
83
|
+
* 'actor.type' → handled as direct column (actor_type)
|
|
84
|
+
*
|
|
85
|
+
* For nested paths inside json columns (ctx, payload), use json_extract_string.
|
|
86
|
+
*/
|
|
87
|
+
export function dotPathToSQL(path: string): string {
|
|
88
|
+
const parts = path.split('.');
|
|
89
|
+
const root = parts[0];
|
|
90
|
+
const child = parts[1];
|
|
91
|
+
|
|
92
|
+
if (root) {
|
|
93
|
+
const direct = DIRECT_COLUMNS[root];
|
|
94
|
+
if (direct) {
|
|
95
|
+
return direct;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (root === 'actor' && child) {
|
|
100
|
+
const mapped = ACTOR_COLUMNS[child];
|
|
101
|
+
if (mapped) {
|
|
102
|
+
return mapped;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (root === 'source' && child) {
|
|
107
|
+
const mapped = SOURCE_COLUMNS[child];
|
|
108
|
+
if (mapped) {
|
|
109
|
+
return mapped;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// JSON column paths
|
|
114
|
+
if (root === 'payload' || root === 'ctx') {
|
|
115
|
+
const jsonPath = '$.' + parts.slice(1).join('.');
|
|
116
|
+
return `json_extract_string(${root}, '${jsonPath}')`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fallback: treat as payload field
|
|
120
|
+
const jsonPath = '$.' + parts.join('.');
|
|
121
|
+
return `json_extract_string(payload, '${jsonPath}')`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Known numeric metrics per event type prefix.
|
|
126
|
+
* Used to build default SELECT list when metrics filter is not specified.
|
|
127
|
+
*/
|
|
128
|
+
export const DEFAULT_METRICS: Record<string, string[]> = {
|
|
129
|
+
'llm.': ['totalTokens', 'totalCost', 'durationMs', 'inputTokens', 'outputTokens'],
|
|
130
|
+
'embeddings.': ['totalTokens', 'totalCost', 'durationMs'],
|
|
131
|
+
'vectorstore.': ['durationMs'],
|
|
132
|
+
'cache.': ['durationMs'],
|
|
133
|
+
'storage.': ['bytesRead', 'bytesWritten', 'durationMs'],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get default metrics for an event type filter.
|
|
138
|
+
*/
|
|
139
|
+
export function getDefaultMetrics(typeFilter?: string | string[]): string[] {
|
|
140
|
+
if (!typeFilter) {return ['totalCost', 'totalTokens', 'durationMs'];}
|
|
141
|
+
|
|
142
|
+
const types = Array.isArray(typeFilter) ? typeFilter : [typeFilter];
|
|
143
|
+
for (const [prefix, metrics] of Object.entries(DEFAULT_METRICS)) {
|
|
144
|
+
if (types.some((t) => t.startsWith(prefix))) {
|
|
145
|
+
return metrics;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return ['totalCost', 'totalTokens', 'durationMs'];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build the metrics SELECT clause fragments.
|
|
153
|
+
* Returns array of SQL expressions like:
|
|
154
|
+
* "SUM(TRY_CAST(json_extract_string(payload, '$.totalTokens') AS DOUBLE)) as totalTokens"
|
|
155
|
+
*/
|
|
156
|
+
export function buildMetricsSelect(metricNames: string[]): string[] {
|
|
157
|
+
return metricNames.map(
|
|
158
|
+
(m) =>
|
|
159
|
+
`SUM(TRY_CAST(json_extract_string(payload, '$.${m}') AS DOUBLE)) as "${m}"`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @kb-labs/adapters-analytics-file
|
|
2
|
+
|
|
3
|
+
> Part of [KB Labs](https://github.com/KirillBaranov/kb-labs) ecosystem. Works exclusively within KB Labs platform.
|
|
4
|
+
|
|
5
|
+
File-based analytics adapter for KB Labs platform. Writes events/metrics as JSONL into `.kb/analytics/buffer`.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
`kb.config.json`:
|
|
10
|
+
```json
|
|
11
|
+
"platform": {
|
|
12
|
+
"adapters": {
|
|
13
|
+
"analytics": "@kb-labs/adapters-analytics-file"
|
|
14
|
+
},
|
|
15
|
+
"adapterOptions": {
|
|
16
|
+
"analytics": {
|
|
17
|
+
"baseDir": ".kb/analytics/buffer",
|
|
18
|
+
"filenamePattern": "events-YYYYMMDD"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Options (all optional):
|
|
25
|
+
- `baseDir`: target directory (default `.kb/analytics/buffer` relative to `process.cwd()`).
|
|
26
|
+
- `filenamePattern`: filename without extension (`YYYYMMDD` will be replaced by date), default `events-YYYYMMDD`.
|
|
27
|
+
|
|
28
|
+
Record format: one JSON object per line.
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
[KB Public License v1.1](../../LICENSE) - KB Labs Team
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard ESLint configuration template
|
|
3
|
+
*
|
|
4
|
+
* This is the canonical template for all @kb-labs packages.
|
|
5
|
+
* DO NOT modify this file locally - it is synced from @kb-labs/devkit
|
|
6
|
+
*
|
|
7
|
+
* Customization guidelines:
|
|
8
|
+
* - DevKit preset already includes all standard ignores
|
|
9
|
+
* - Only add project-specific ignores if absolutely necessary
|
|
10
|
+
* - Document why custom ignores are needed
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/kb-labs/devkit#eslint-configuration
|
|
13
|
+
*/
|
|
14
|
+
import nodePreset from '@kb-labs/devkit/eslint/node.js';
|
|
15
|
+
|
|
16
|
+
export default [
|
|
17
|
+
...nodePreset,
|
|
18
|
+
|
|
19
|
+
// OPTIONAL: Add project-specific ignores only if needed
|
|
20
|
+
// DevKit preset already ignores: dist/, coverage/, node_modules/, *.d.ts, scripts/, etc.
|
|
21
|
+
// {
|
|
22
|
+
// ignores: [
|
|
23
|
+
// // Add ONLY project-specific patterns here
|
|
24
|
+
// // Example: '**/*.generated.ts',
|
|
25
|
+
// ]
|
|
26
|
+
// }
|
|
27
|
+
];
|