@betterdb/semantic-cache 0.7.0 → 0.8.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/analytics.d.ts +23 -2
- package/dist/analytics.js +98 -12
- package/dist/types.d.ts +0 -4
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -11,14 +11,35 @@ export interface ValkeyLike {
|
|
|
11
11
|
export interface Analytics {
|
|
12
12
|
init(client: ValkeyLike, name: string, configProps?: Record<string, unknown>): Promise<void>;
|
|
13
13
|
capture(event: string, properties?: Record<string, unknown>): void;
|
|
14
|
+
flush(): Promise<void>;
|
|
14
15
|
shutdown(): Promise<void>;
|
|
15
16
|
}
|
|
16
17
|
export interface AnalyticsOptions {
|
|
17
|
-
apiKey?: string;
|
|
18
|
-
host?: string;
|
|
19
18
|
disabled?: boolean;
|
|
20
19
|
/** Interval in ms for periodic stats snapshots. Default: 300_000 (5 min). 0 to disable. */
|
|
21
20
|
statsIntervalMs?: number;
|
|
22
21
|
}
|
|
23
22
|
export declare const NOOP_ANALYTICS: Analytics;
|
|
23
|
+
type PostHogClient = {
|
|
24
|
+
capture: (opts: {
|
|
25
|
+
distinctId?: string;
|
|
26
|
+
event: string;
|
|
27
|
+
properties?: Record<string, unknown>;
|
|
28
|
+
}) => void;
|
|
29
|
+
flush: () => Promise<void>;
|
|
30
|
+
shutdown: () => Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
export declare class PostHogAnalytics implements Analytics {
|
|
33
|
+
private posthog;
|
|
34
|
+
private distinctId;
|
|
35
|
+
private deploymentId;
|
|
36
|
+
private readonly flushOnExit;
|
|
37
|
+
constructor(posthog: PostHogClient);
|
|
38
|
+
init(client: ValkeyLike, name: string, configProps?: Record<string, unknown>): Promise<void>;
|
|
39
|
+
private resolveDeploymentId;
|
|
40
|
+
capture(event: string, properties?: Record<string, unknown>): void;
|
|
41
|
+
flush(): Promise<void>;
|
|
42
|
+
shutdown(): Promise<void>;
|
|
43
|
+
}
|
|
24
44
|
export declare function createAnalytics(opts?: AnalyticsOptions): Promise<Analytics>;
|
|
45
|
+
export {};
|
package/dist/analytics.js
CHANGED
|
@@ -39,8 +39,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.NOOP_ANALYTICS = void 0;
|
|
42
|
+
exports.PostHogAnalytics = exports.NOOP_ANALYTICS = void 0;
|
|
43
43
|
exports.createAnalytics = createAnalytics;
|
|
44
|
+
const node_fs_1 = require("node:fs");
|
|
45
|
+
const node_os_1 = require("node:os");
|
|
46
|
+
const node_path_1 = require("node:path");
|
|
44
47
|
const EVENT_PREFIX = 'semantic_cache:';
|
|
45
48
|
// Build-time placeholders — replaced by scripts/inject-telemetry-defaults.mjs
|
|
46
49
|
// When the placeholder is NOT replaced, the startsWith('__') guard treats it as unset.
|
|
@@ -49,41 +52,126 @@ const BAKED_POSTHOG_HOST = '__BETTERDB_POSTHOG_HOST__';
|
|
|
49
52
|
exports.NOOP_ANALYTICS = {
|
|
50
53
|
async init() { },
|
|
51
54
|
capture() { },
|
|
55
|
+
async flush() { },
|
|
52
56
|
async shutdown() { },
|
|
53
57
|
};
|
|
54
58
|
function isTelemetryOptedOut() {
|
|
55
59
|
const val = process.env.BETTERDB_TELEMETRY;
|
|
56
60
|
return val !== undefined && ['false', '0', 'no', 'off'].includes(val.toLowerCase());
|
|
57
61
|
}
|
|
62
|
+
const INSTALL_ID_ENV = 'BETTERDB_INSTANCE_ID';
|
|
63
|
+
// Holds a minted id for the rest of the process when persistence fails, so
|
|
64
|
+
// repeated calls (or parallel init) return one stable ephemeral identity.
|
|
65
|
+
let ephemeralInstallId;
|
|
66
|
+
function installIdPath() {
|
|
67
|
+
const base = process.env.XDG_STATE_HOME;
|
|
68
|
+
const root = base ? base : (0, node_path_1.join)((0, node_os_1.homedir)(), '.betterdb');
|
|
69
|
+
return (0, node_path_1.join)(root, 'instance_id');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Stable per-install identity for product analytics. Persisted on the local
|
|
73
|
+
* machine (not in Valkey), so a fleet of processes sharing one Valkey is
|
|
74
|
+
* counted as many installs rather than collapsing to one. Pin it via
|
|
75
|
+
* BETTERDB_INSTANCE_ID for ephemeral containers that would otherwise mint a
|
|
76
|
+
* fresh id every run. Falls back to an ephemeral per-process id when no
|
|
77
|
+
* writable location is available.
|
|
78
|
+
*/
|
|
79
|
+
function getInstallId() {
|
|
80
|
+
const override = process.env[INSTALL_ID_ENV];
|
|
81
|
+
if (override)
|
|
82
|
+
return override;
|
|
83
|
+
const path = installIdPath();
|
|
84
|
+
try {
|
|
85
|
+
const existing = (0, node_fs_1.readFileSync)(path, 'utf8').trim();
|
|
86
|
+
if (existing)
|
|
87
|
+
return existing;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// no existing id
|
|
91
|
+
}
|
|
92
|
+
const newId = ephemeralInstallId ?? crypto.randomUUID();
|
|
93
|
+
try {
|
|
94
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
95
|
+
(0, node_fs_1.writeFileSync)(path, newId);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Persistence failed — hold the id for the rest of this process so
|
|
99
|
+
// repeated calls return a stable ephemeral identity.
|
|
100
|
+
ephemeralInstallId = newId;
|
|
101
|
+
}
|
|
102
|
+
return newId;
|
|
103
|
+
}
|
|
58
104
|
class PostHogAnalytics {
|
|
59
105
|
posthog;
|
|
60
106
|
distinctId = '';
|
|
107
|
+
deploymentId = '';
|
|
108
|
+
// Library consumers are frequently short-lived scripts that never call
|
|
109
|
+
// shutdown(), so PostHog's buffered events (flushAt=20, flushInterval=10s)
|
|
110
|
+
// would be dropped when the process exits before the queue drains. Flush
|
|
111
|
+
// when the event loop empties so lifecycle events are actually delivered.
|
|
112
|
+
// Only enabled instances reach here — the opt-out path returns
|
|
113
|
+
// NOOP_ANALYTICS and registers nothing, keeping disabled consumers silent.
|
|
114
|
+
flushOnExit = () => {
|
|
115
|
+
void this.flush();
|
|
116
|
+
};
|
|
61
117
|
constructor(posthog) {
|
|
62
118
|
this.posthog = posthog;
|
|
119
|
+
process.once('beforeExit', this.flushOnExit);
|
|
63
120
|
}
|
|
64
121
|
async init(client, name, configProps) {
|
|
122
|
+
this.distinctId = getInstallId();
|
|
123
|
+
this.deploymentId = await this.resolveDeploymentId(client, name);
|
|
124
|
+
const merged = { ...(configProps ?? {}) };
|
|
125
|
+
if (this.deploymentId)
|
|
126
|
+
merged.deployment_id = this.deploymentId;
|
|
127
|
+
this.capture('cache_init', merged);
|
|
128
|
+
// Flush the start event immediately so it lands even for processes that exit
|
|
129
|
+
// before the flush interval or the beforeExit hook fires.
|
|
130
|
+
await this.flush();
|
|
131
|
+
}
|
|
132
|
+
async resolveDeploymentId(client, name) {
|
|
133
|
+
// The Valkey-scoped id groups all clients pointed at the same store, so a
|
|
134
|
+
// shared-Valkey fleet can still be rolled up into one deployment.
|
|
65
135
|
const idKey = `${name}:__instance_id`;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
136
|
+
try {
|
|
137
|
+
const existing = await client.get(idKey);
|
|
138
|
+
if (existing)
|
|
139
|
+
return existing;
|
|
140
|
+
const id = crypto.randomUUID();
|
|
69
141
|
await client.set(idKey, id);
|
|
142
|
+
return id;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return '';
|
|
70
146
|
}
|
|
71
|
-
this.distinctId = id;
|
|
72
|
-
this.capture('cache_init', configProps);
|
|
73
147
|
}
|
|
74
148
|
capture(event, properties) {
|
|
75
149
|
try {
|
|
150
|
+
const props = { ...(properties ?? {}) };
|
|
151
|
+
if (this.deploymentId && props.deployment_id === undefined) {
|
|
152
|
+
props.deployment_id = this.deploymentId;
|
|
153
|
+
}
|
|
76
154
|
this.posthog.capture({
|
|
77
155
|
distinctId: this.distinctId,
|
|
78
156
|
event: `${EVENT_PREFIX}${event}`,
|
|
79
|
-
properties,
|
|
157
|
+
properties: props,
|
|
80
158
|
});
|
|
81
159
|
}
|
|
82
160
|
catch {
|
|
83
161
|
// never throw from analytics
|
|
84
162
|
}
|
|
85
163
|
}
|
|
164
|
+
async flush() {
|
|
165
|
+
try {
|
|
166
|
+
await this.posthog.flush();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// swallow
|
|
170
|
+
}
|
|
171
|
+
}
|
|
86
172
|
async shutdown() {
|
|
173
|
+
// Explicit shutdown supersedes the beforeExit backstop.
|
|
174
|
+
process.removeListener('beforeExit', this.flushOnExit);
|
|
87
175
|
try {
|
|
88
176
|
await this.posthog.shutdown();
|
|
89
177
|
}
|
|
@@ -92,18 +180,16 @@ class PostHogAnalytics {
|
|
|
92
180
|
}
|
|
93
181
|
}
|
|
94
182
|
}
|
|
183
|
+
exports.PostHogAnalytics = PostHogAnalytics;
|
|
95
184
|
async function createAnalytics(opts) {
|
|
96
185
|
if (opts?.disabled || isTelemetryOptedOut()) {
|
|
97
186
|
return exports.NOOP_ANALYTICS;
|
|
98
187
|
}
|
|
99
|
-
|
|
100
|
-
const bakedKey = BAKED_POSTHOG_API_KEY.startsWith('__') ? undefined : BAKED_POSTHOG_API_KEY;
|
|
101
|
-
const apiKey = opts?.apiKey ?? process.env.BETTERDB_POSTHOG_API_KEY ?? bakedKey;
|
|
188
|
+
const apiKey = BAKED_POSTHOG_API_KEY.startsWith('__') ? undefined : BAKED_POSTHOG_API_KEY;
|
|
102
189
|
if (!apiKey) {
|
|
103
190
|
return exports.NOOP_ANALYTICS;
|
|
104
191
|
}
|
|
105
|
-
const
|
|
106
|
-
const host = opts?.host ?? process.env.BETTERDB_POSTHOG_HOST ?? bakedHost;
|
|
192
|
+
const host = BAKED_POSTHOG_HOST.startsWith('__') ? undefined : BAKED_POSTHOG_HOST;
|
|
107
193
|
try {
|
|
108
194
|
// @ts-ignore — posthog-node is an optional peer dep
|
|
109
195
|
const { PostHog } = await Promise.resolve().then(() => __importStar(require('posthog-node')));
|
package/dist/types.d.ts
CHANGED
|
@@ -90,10 +90,6 @@ export interface SemanticCacheOptions {
|
|
|
90
90
|
registry?: Registry;
|
|
91
91
|
};
|
|
92
92
|
analytics?: {
|
|
93
|
-
/** PostHog API key. Overrides the build-time baked key if set. */
|
|
94
|
-
apiKey?: string;
|
|
95
|
-
/** PostHog host. Overrides the build-time baked host if set. */
|
|
96
|
-
host?: string;
|
|
97
93
|
/** Disable analytics. Also controlled by BETTERDB_TELEMETRY env var. */
|
|
98
94
|
disabled?: boolean;
|
|
99
95
|
/** Interval in ms for periodic stats snapshots. Default: 300_000 (5 min). 0 to disable. */
|
package/package.json
CHANGED