@runtimescope/workers-sdk 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/index.d.ts +358 -0
- package/dist/index.js +717 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' | 'trace';
|
|
2
|
+
interface ConsoleEvent {
|
|
3
|
+
eventId: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
eventType: 'console';
|
|
7
|
+
level: ConsoleLevel;
|
|
8
|
+
message: string;
|
|
9
|
+
args: unknown[];
|
|
10
|
+
stackTrace?: string;
|
|
11
|
+
sourceFile?: string;
|
|
12
|
+
}
|
|
13
|
+
type DatabaseOperation = 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'OTHER';
|
|
14
|
+
type DatabaseSource = 'd1' | 'kv' | 'r2' | 'generic';
|
|
15
|
+
interface DatabaseEvent {
|
|
16
|
+
eventId: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
eventType: 'database';
|
|
20
|
+
query: string;
|
|
21
|
+
normalizedQuery: string;
|
|
22
|
+
duration: number;
|
|
23
|
+
rowsReturned?: number;
|
|
24
|
+
rowsAffected?: number;
|
|
25
|
+
tablesAccessed: string[];
|
|
26
|
+
operation: DatabaseOperation;
|
|
27
|
+
source: DatabaseSource;
|
|
28
|
+
error?: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
interface NetworkEvent {
|
|
32
|
+
eventId: string;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
timestamp: number;
|
|
35
|
+
eventType: 'network';
|
|
36
|
+
url: string;
|
|
37
|
+
method: string;
|
|
38
|
+
status: number;
|
|
39
|
+
duration: number;
|
|
40
|
+
requestHeaders: Record<string, string>;
|
|
41
|
+
responseHeaders: Record<string, string>;
|
|
42
|
+
requestBodySize: number;
|
|
43
|
+
responseBodySize: number;
|
|
44
|
+
ttfb: number;
|
|
45
|
+
source: 'workers';
|
|
46
|
+
direction: 'incoming';
|
|
47
|
+
/** Cloudflare-specific properties from request.cf */
|
|
48
|
+
cfProperties?: {
|
|
49
|
+
colo?: string;
|
|
50
|
+
country?: string;
|
|
51
|
+
city?: string;
|
|
52
|
+
region?: string;
|
|
53
|
+
asn?: number;
|
|
54
|
+
httpProtocol?: string;
|
|
55
|
+
tlsVersion?: string;
|
|
56
|
+
};
|
|
57
|
+
errorMessage?: string;
|
|
58
|
+
}
|
|
59
|
+
interface CustomEvent {
|
|
60
|
+
eventId: string;
|
|
61
|
+
sessionId: string;
|
|
62
|
+
timestamp: number;
|
|
63
|
+
eventType: 'custom';
|
|
64
|
+
name: string;
|
|
65
|
+
properties?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
type UIInteractionAction = 'breadcrumb';
|
|
68
|
+
interface UIInteractionEvent {
|
|
69
|
+
eventId: string;
|
|
70
|
+
sessionId: string;
|
|
71
|
+
timestamp: number;
|
|
72
|
+
eventType: 'ui';
|
|
73
|
+
action: UIInteractionAction;
|
|
74
|
+
target: string;
|
|
75
|
+
text?: string;
|
|
76
|
+
data?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
type WorkersRuntimeEvent = ConsoleEvent | DatabaseEvent | NetworkEvent | CustomEvent | UIInteractionEvent;
|
|
79
|
+
interface WorkersConfig {
|
|
80
|
+
/** App name — identifies this worker in session info */
|
|
81
|
+
appName: string;
|
|
82
|
+
/** HTTP endpoint for collector (default: http://localhost:9091/api/events) */
|
|
83
|
+
httpEndpoint?: string;
|
|
84
|
+
/** Auth token for authenticated collectors */
|
|
85
|
+
authToken?: string;
|
|
86
|
+
/** Probabilistic sample rate: 0.0–1.0 (default: 1.0 = keep all) */
|
|
87
|
+
sampleRate?: number;
|
|
88
|
+
/** Max events queued per request before dropping (default: 500) */
|
|
89
|
+
maxQueueSize?: number;
|
|
90
|
+
/** Capture console.log/warn/error (default: true) */
|
|
91
|
+
captureConsole?: boolean;
|
|
92
|
+
/** Include request/response headers in network events (default: false) */
|
|
93
|
+
captureHeaders?: boolean;
|
|
94
|
+
/** Headers to redact from network events (default: ['authorization', 'cookie', 'set-cookie']) */
|
|
95
|
+
redactHeaders?: string[];
|
|
96
|
+
/** Filter/modify events before sending. Return null to drop. */
|
|
97
|
+
beforeSend?: (event: WorkersRuntimeEvent) => WorkersRuntimeEvent | null;
|
|
98
|
+
/** User context attached to all events */
|
|
99
|
+
user?: UserContext | null;
|
|
100
|
+
}
|
|
101
|
+
interface UserContext {
|
|
102
|
+
id?: string;
|
|
103
|
+
email?: string;
|
|
104
|
+
name?: string;
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
}
|
|
107
|
+
/** Minimal D1Database interface — matches Cloudflare's D1Database */
|
|
108
|
+
interface D1DatabaseBinding {
|
|
109
|
+
prepare(query: string): D1PreparedStatementBinding;
|
|
110
|
+
batch<T = unknown>(statements: D1PreparedStatementBinding[]): Promise<D1Result<T>[]>;
|
|
111
|
+
exec(query: string): Promise<D1ExecResult>;
|
|
112
|
+
dump(): Promise<ArrayBuffer>;
|
|
113
|
+
}
|
|
114
|
+
interface D1PreparedStatementBinding {
|
|
115
|
+
bind(...values: unknown[]): D1PreparedStatementBinding;
|
|
116
|
+
first<T = unknown>(colName?: string): Promise<T | null>;
|
|
117
|
+
run<T = unknown>(): Promise<D1Result<T>>;
|
|
118
|
+
all<T = unknown>(): Promise<D1Result<T>>;
|
|
119
|
+
raw<T = unknown>(options?: {
|
|
120
|
+
columnNames?: boolean;
|
|
121
|
+
}): Promise<T[]>;
|
|
122
|
+
}
|
|
123
|
+
interface D1Result<T = unknown> {
|
|
124
|
+
results?: T[];
|
|
125
|
+
success: boolean;
|
|
126
|
+
meta: {
|
|
127
|
+
duration: number;
|
|
128
|
+
rows_read: number;
|
|
129
|
+
rows_written: number;
|
|
130
|
+
last_row_id: number;
|
|
131
|
+
changed_db: boolean;
|
|
132
|
+
size_after: number;
|
|
133
|
+
changes: number;
|
|
134
|
+
};
|
|
135
|
+
error?: string;
|
|
136
|
+
}
|
|
137
|
+
interface D1ExecResult {
|
|
138
|
+
count: number;
|
|
139
|
+
duration: number;
|
|
140
|
+
}
|
|
141
|
+
/** Minimal KVNamespace interface — matches Cloudflare's KVNamespace */
|
|
142
|
+
interface KVNamespaceBinding {
|
|
143
|
+
get(key: string, options?: unknown): Promise<string | null>;
|
|
144
|
+
getWithMetadata<M = unknown>(key: string, options?: unknown): Promise<{
|
|
145
|
+
value: string | null;
|
|
146
|
+
metadata: M | null;
|
|
147
|
+
}>;
|
|
148
|
+
put(key: string, value: string | ReadableStream | ArrayBuffer, options?: unknown): Promise<void>;
|
|
149
|
+
delete(key: string): Promise<void>;
|
|
150
|
+
list(options?: unknown): Promise<{
|
|
151
|
+
keys: {
|
|
152
|
+
name: string;
|
|
153
|
+
}[];
|
|
154
|
+
list_complete: boolean;
|
|
155
|
+
cursor?: string;
|
|
156
|
+
}>;
|
|
157
|
+
}
|
|
158
|
+
/** Minimal R2Bucket interface — matches Cloudflare's R2Bucket */
|
|
159
|
+
interface R2BucketBinding {
|
|
160
|
+
get(key: string, options?: unknown): Promise<R2ObjectBody | null>;
|
|
161
|
+
put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: unknown): Promise<R2Object | null>;
|
|
162
|
+
delete(keys: string | string[]): Promise<void>;
|
|
163
|
+
list(options?: unknown): Promise<R2Objects>;
|
|
164
|
+
head(key: string): Promise<R2Object | null>;
|
|
165
|
+
}
|
|
166
|
+
interface R2Object {
|
|
167
|
+
key: string;
|
|
168
|
+
size: number;
|
|
169
|
+
etag: string;
|
|
170
|
+
httpEtag: string;
|
|
171
|
+
uploaded: Date;
|
|
172
|
+
}
|
|
173
|
+
interface R2ObjectBody extends R2Object {
|
|
174
|
+
body: ReadableStream;
|
|
175
|
+
bodyUsed: boolean;
|
|
176
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
177
|
+
text(): Promise<string>;
|
|
178
|
+
json<T>(): Promise<T>;
|
|
179
|
+
blob(): Promise<Blob>;
|
|
180
|
+
}
|
|
181
|
+
interface R2Objects {
|
|
182
|
+
objects: R2Object[];
|
|
183
|
+
truncated: boolean;
|
|
184
|
+
cursor?: string;
|
|
185
|
+
delimitedPrefixes: string[];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare class WorkersTransport {
|
|
189
|
+
private buffer;
|
|
190
|
+
private maxQueueSize;
|
|
191
|
+
private url;
|
|
192
|
+
private authToken;
|
|
193
|
+
private appName;
|
|
194
|
+
private sessionRegistered;
|
|
195
|
+
private _droppedCount;
|
|
196
|
+
readonly sessionId: string;
|
|
197
|
+
constructor(config: WorkersConfig);
|
|
198
|
+
get droppedCount(): number;
|
|
199
|
+
queue(event: WorkersRuntimeEvent): void;
|
|
200
|
+
/**
|
|
201
|
+
* Flush all buffered events to the collector.
|
|
202
|
+
* Call via ctx.waitUntil(transport.flush()) at the end of each request.
|
|
203
|
+
* Retries once on failure with a short delay.
|
|
204
|
+
*/
|
|
205
|
+
flush(): Promise<void>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Internal context exposed to binding wrappers */
|
|
209
|
+
interface RuntimeScopeContext {
|
|
210
|
+
transport: WorkersTransport;
|
|
211
|
+
sessionId: string;
|
|
212
|
+
config: WorkersConfig;
|
|
213
|
+
emit: (event: WorkersRuntimeEvent) => void;
|
|
214
|
+
}
|
|
215
|
+
/** Get the active RuntimeScope context (used by binding wrappers) */
|
|
216
|
+
declare function getActiveContext(): RuntimeScopeContext | null;
|
|
217
|
+
interface WorkersFetchHandler {
|
|
218
|
+
fetch(request: Request, env: unknown, ctx: ExecutionContext): Response | Promise<Response>;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Wrap a Workers fetch handler to capture request/response metrics,
|
|
222
|
+
* errors, and console output. Events are flushed via ctx.waitUntil().
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* import { withRuntimeScope } from '@runtimescope/workers-sdk';
|
|
227
|
+
*
|
|
228
|
+
* export default withRuntimeScope({
|
|
229
|
+
* async fetch(request, env, ctx) {
|
|
230
|
+
* return new Response('Hello!');
|
|
231
|
+
* },
|
|
232
|
+
* }, { appName: 'my-worker' });
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
declare function withRuntimeScope(handler: WorkersFetchHandler, config: WorkersConfig): WorkersFetchHandler;
|
|
236
|
+
|
|
237
|
+
type EmitFn$2 = (event: DatabaseEvent) => void;
|
|
238
|
+
interface D1InstrumentOptions {
|
|
239
|
+
sessionId: string;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Wrap a D1 database binding to capture queries.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* const db = instrumentD1(env.DB, transport.sessionId);
|
|
247
|
+
* const results = await db.prepare('SELECT * FROM users').all();
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
declare function instrumentD1(db: D1DatabaseBinding, emit: EmitFn$2, options: D1InstrumentOptions): D1DatabaseBinding;
|
|
251
|
+
|
|
252
|
+
type EmitFn$1 = (event: DatabaseEvent) => void;
|
|
253
|
+
interface KVInstrumentOptions {
|
|
254
|
+
sessionId: string;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Wrap a KV namespace binding to capture operations.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* const kv = instrumentKV(env.MY_KV, emit, { sessionId });
|
|
262
|
+
* await kv.put('key', 'value');
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
declare function instrumentKV(kv: KVNamespaceBinding, emit: EmitFn$1, options: KVInstrumentOptions): KVNamespaceBinding;
|
|
266
|
+
|
|
267
|
+
type EmitFn = (event: DatabaseEvent) => void;
|
|
268
|
+
interface R2InstrumentOptions {
|
|
269
|
+
sessionId: string;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Wrap an R2 bucket binding to capture operations.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const bucket = instrumentR2(env.MY_BUCKET, emit, { sessionId });
|
|
277
|
+
* await bucket.put('file.txt', 'contents');
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
declare function instrumentR2(bucket: R2BucketBinding, emit: EmitFn, options: R2InstrumentOptions): R2BucketBinding;
|
|
281
|
+
|
|
282
|
+
interface SamplerConfig {
|
|
283
|
+
/** Probabilistic sample rate: 0.0–1.0 (default: 1.0 = keep all) */
|
|
284
|
+
sampleRate?: number;
|
|
285
|
+
}
|
|
286
|
+
declare class Sampler {
|
|
287
|
+
private config;
|
|
288
|
+
private _droppedCount;
|
|
289
|
+
constructor(config: SamplerConfig);
|
|
290
|
+
get droppedCount(): number;
|
|
291
|
+
shouldSample(_event: WorkersRuntimeEvent): boolean;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Generate a random event ID using Web Crypto API (available in Workers) */
|
|
295
|
+
declare function generateId(): string;
|
|
296
|
+
/** Generate a session ID with worker prefix */
|
|
297
|
+
declare function generateSessionId(): string;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Instrument a D1 database using the active request context.
|
|
301
|
+
* Must be called inside a withRuntimeScope handler.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* import { withRuntimeScope, scopeD1 } from '@runtimescope/workers-sdk';
|
|
306
|
+
*
|
|
307
|
+
* export default withRuntimeScope({
|
|
308
|
+
* async fetch(request, env, ctx) {
|
|
309
|
+
* const db = scopeD1(env.DB);
|
|
310
|
+
* const users = await db.prepare('SELECT * FROM users').all();
|
|
311
|
+
* return Response.json(users);
|
|
312
|
+
* },
|
|
313
|
+
* }, { appName: 'my-worker' });
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
declare function scopeD1(db: D1DatabaseBinding): D1DatabaseBinding;
|
|
317
|
+
/** Instrument a KV namespace using the active request context. */
|
|
318
|
+
declare function scopeKV(kv: KVNamespaceBinding): KVNamespaceBinding;
|
|
319
|
+
/** Instrument an R2 bucket using the active request context. */
|
|
320
|
+
declare function scopeR2(bucket: R2BucketBinding): R2BucketBinding;
|
|
321
|
+
/**
|
|
322
|
+
* Track a custom business event (e.g., user signup, payment processed).
|
|
323
|
+
* Must be called inside a withRuntimeScope handler.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* import { withRuntimeScope, track } from '@runtimescope/workers-sdk';
|
|
328
|
+
*
|
|
329
|
+
* export default withRuntimeScope({
|
|
330
|
+
* async fetch(request, env, ctx) {
|
|
331
|
+
* track('payment.processed', { amount: 99.99, currency: 'USD' });
|
|
332
|
+
* return new Response('OK');
|
|
333
|
+
* },
|
|
334
|
+
* }, { appName: 'payments-worker' });
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
declare function track(name: string, properties?: Record<string, unknown>): void;
|
|
338
|
+
/**
|
|
339
|
+
* Add a breadcrumb to the current request's event trail.
|
|
340
|
+
* Useful for marking key points in request processing.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* import { withRuntimeScope, addBreadcrumb } from '@runtimescope/workers-sdk';
|
|
345
|
+
*
|
|
346
|
+
* export default withRuntimeScope({
|
|
347
|
+
* async fetch(request, env, ctx) {
|
|
348
|
+
* addBreadcrumb('auth check passed', { userId: '123' });
|
|
349
|
+
* // ... process request
|
|
350
|
+
* addBreadcrumb('cache miss, fetching from origin');
|
|
351
|
+
* return new Response('OK');
|
|
352
|
+
* },
|
|
353
|
+
* }, { appName: 'api-worker' });
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
declare function addBreadcrumb(message: string, data?: Record<string, unknown>): void;
|
|
357
|
+
|
|
358
|
+
export { type ConsoleEvent, type ConsoleLevel, type CustomEvent, type D1DatabaseBinding, type D1PreparedStatementBinding, type D1Result, type DatabaseEvent, type DatabaseOperation, type DatabaseSource, type KVNamespaceBinding, type NetworkEvent, type R2BucketBinding, type R2Object, type R2ObjectBody, type R2Objects, type RuntimeScopeContext, Sampler, type UIInteractionEvent, type UserContext, type WorkersConfig, type WorkersFetchHandler, type WorkersRuntimeEvent, WorkersTransport, addBreadcrumb, generateId, generateSessionId, getActiveContext, instrumentD1, instrumentKV, instrumentR2, scopeD1, scopeKV, scopeR2, track, withRuntimeScope };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
// src/handler.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
|
|
4
|
+
// src/utils.ts
|
|
5
|
+
function generateId() {
|
|
6
|
+
return crypto.randomUUID();
|
|
7
|
+
}
|
|
8
|
+
function generateSessionId() {
|
|
9
|
+
return `wk-${crypto.randomUUID().slice(0, 16)}`;
|
|
10
|
+
}
|
|
11
|
+
function safeSerialize(value, maxDepth = 5) {
|
|
12
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
13
|
+
function walk(val, depth) {
|
|
14
|
+
if (depth > maxDepth) return "[max depth]";
|
|
15
|
+
if (val === null || val === void 0) return val;
|
|
16
|
+
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
|
|
17
|
+
if (typeof val === "symbol") return val.toString();
|
|
18
|
+
if (typeof val === "bigint") return val.toString();
|
|
19
|
+
if (typeof val !== "object") return val;
|
|
20
|
+
if (val instanceof Error) {
|
|
21
|
+
return { name: val.name, message: val.message, stack: val.stack };
|
|
22
|
+
}
|
|
23
|
+
if (val instanceof Date) {
|
|
24
|
+
return val.toISOString();
|
|
25
|
+
}
|
|
26
|
+
if (val instanceof RegExp) {
|
|
27
|
+
return val.toString();
|
|
28
|
+
}
|
|
29
|
+
if (seen.has(val)) return "[Circular]";
|
|
30
|
+
seen.add(val);
|
|
31
|
+
if (Array.isArray(val)) {
|
|
32
|
+
return val.map((v) => walk(v, depth + 1));
|
|
33
|
+
}
|
|
34
|
+
const result = {};
|
|
35
|
+
for (const key of Object.keys(val)) {
|
|
36
|
+
result[key] = walk(val[key], depth + 1);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
return walk(value, 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/transport.ts
|
|
44
|
+
var SDK_VERSION = "0.8.0";
|
|
45
|
+
var DEFAULT_ENDPOINT = "http://localhost:9091/api/events";
|
|
46
|
+
var WorkersTransport = class {
|
|
47
|
+
buffer = [];
|
|
48
|
+
maxQueueSize;
|
|
49
|
+
url;
|
|
50
|
+
authToken;
|
|
51
|
+
appName;
|
|
52
|
+
sessionRegistered = false;
|
|
53
|
+
_droppedCount = 0;
|
|
54
|
+
sessionId;
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.sessionId = generateSessionId();
|
|
57
|
+
this.url = config.httpEndpoint ?? DEFAULT_ENDPOINT;
|
|
58
|
+
this.appName = config.appName;
|
|
59
|
+
this.authToken = config.authToken;
|
|
60
|
+
this.maxQueueSize = config.maxQueueSize ?? 500;
|
|
61
|
+
}
|
|
62
|
+
get droppedCount() {
|
|
63
|
+
return this._droppedCount;
|
|
64
|
+
}
|
|
65
|
+
queue(event) {
|
|
66
|
+
if (this.buffer.length >= this.maxQueueSize) {
|
|
67
|
+
this.buffer.shift();
|
|
68
|
+
this._droppedCount++;
|
|
69
|
+
}
|
|
70
|
+
this.buffer.push(event);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Flush all buffered events to the collector.
|
|
74
|
+
* Call via ctx.waitUntil(transport.flush()) at the end of each request.
|
|
75
|
+
* Retries once on failure with a short delay.
|
|
76
|
+
*/
|
|
77
|
+
async flush() {
|
|
78
|
+
if (this.buffer.length === 0) return;
|
|
79
|
+
const events = this.buffer.splice(0);
|
|
80
|
+
const payload = {
|
|
81
|
+
sessionId: this.sessionId,
|
|
82
|
+
events
|
|
83
|
+
};
|
|
84
|
+
if (!this.sessionRegistered) {
|
|
85
|
+
payload.appName = this.appName;
|
|
86
|
+
payload.sdkVersion = SDK_VERSION;
|
|
87
|
+
}
|
|
88
|
+
const headers = {
|
|
89
|
+
"Content-Type": "application/json"
|
|
90
|
+
};
|
|
91
|
+
if (this.authToken) {
|
|
92
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
93
|
+
}
|
|
94
|
+
const body = JSON.stringify(payload);
|
|
95
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
96
|
+
try {
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
99
|
+
const response = await fetch(this.url, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers,
|
|
102
|
+
body,
|
|
103
|
+
signal: controller.signal
|
|
104
|
+
});
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
if (response.ok) {
|
|
107
|
+
this.sessionRegistered = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (response.status >= 500 && attempt === 0) continue;
|
|
111
|
+
return;
|
|
112
|
+
} catch {
|
|
113
|
+
if (attempt === 0) {
|
|
114
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/sampler.ts
|
|
123
|
+
var Sampler = class {
|
|
124
|
+
constructor(config) {
|
|
125
|
+
this.config = config;
|
|
126
|
+
}
|
|
127
|
+
_droppedCount = 0;
|
|
128
|
+
get droppedCount() {
|
|
129
|
+
return this._droppedCount;
|
|
130
|
+
}
|
|
131
|
+
shouldSample(_event) {
|
|
132
|
+
const rate = this.config.sampleRate;
|
|
133
|
+
if (rate !== void 0 && rate < 1) {
|
|
134
|
+
if (Math.random() > rate) {
|
|
135
|
+
this._droppedCount++;
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/interceptors/console.ts
|
|
144
|
+
var LEVELS = ["log", "warn", "error", "info", "debug", "trace"];
|
|
145
|
+
function interceptConsole(emit, options) {
|
|
146
|
+
const levels = options.levels ?? LEVELS;
|
|
147
|
+
const originals = {};
|
|
148
|
+
for (const level of levels) {
|
|
149
|
+
originals[level] = console[level].bind(console);
|
|
150
|
+
console[level] = (...args) => {
|
|
151
|
+
const message = args.map((a) => typeof a === "string" ? a : stringifyArg(a)).join(" ");
|
|
152
|
+
const event = {
|
|
153
|
+
eventId: generateId(),
|
|
154
|
+
sessionId: options.sessionId,
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
eventType: "console",
|
|
157
|
+
level,
|
|
158
|
+
message,
|
|
159
|
+
args: args.map((a) => safeSerialize(a, 3)),
|
|
160
|
+
stackTrace: level === "error" || level === "trace" ? new Error().stack?.split("\n").slice(2).join("\n") : void 0
|
|
161
|
+
};
|
|
162
|
+
emit(event);
|
|
163
|
+
originals[level](...args);
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return () => {
|
|
167
|
+
for (const level of levels) {
|
|
168
|
+
console[level] = originals[level];
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function stringifyArg(arg) {
|
|
173
|
+
try {
|
|
174
|
+
return JSON.stringify(arg);
|
|
175
|
+
} catch {
|
|
176
|
+
return String(arg);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/handler.ts
|
|
181
|
+
var _contextStorage = new AsyncLocalStorage();
|
|
182
|
+
function getActiveContext() {
|
|
183
|
+
return _contextStorage.getStore() ?? null;
|
|
184
|
+
}
|
|
185
|
+
var DEFAULT_REDACT_HEADERS = ["authorization", "cookie", "set-cookie"];
|
|
186
|
+
function withRuntimeScope(handler, config) {
|
|
187
|
+
const transport = new WorkersTransport(config);
|
|
188
|
+
const sampler = config.sampleRate !== void 0 && config.sampleRate < 1 ? new Sampler({ sampleRate: config.sampleRate }) : null;
|
|
189
|
+
const redactHeaders = config.redactHeaders ?? DEFAULT_REDACT_HEADERS;
|
|
190
|
+
const redactSet = new Set(redactHeaders.map((h) => h.toLowerCase()));
|
|
191
|
+
function emit(event) {
|
|
192
|
+
if (sampler && !sampler.shouldSample(event)) return;
|
|
193
|
+
if (config.beforeSend) {
|
|
194
|
+
const filtered = config.beforeSend(event);
|
|
195
|
+
if (!filtered) return;
|
|
196
|
+
transport.queue(filtered);
|
|
197
|
+
} else {
|
|
198
|
+
transport.queue(event);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
let restoreConsole = null;
|
|
202
|
+
if (config.captureConsole !== false) {
|
|
203
|
+
restoreConsole = interceptConsole(emit, {
|
|
204
|
+
sessionId: transport.sessionId
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function extractHeaders(headers) {
|
|
208
|
+
if (!config.captureHeaders) return {};
|
|
209
|
+
const result = {};
|
|
210
|
+
headers.forEach((value, key) => {
|
|
211
|
+
result[key] = redactSet.has(key.toLowerCase()) ? "[REDACTED]" : value;
|
|
212
|
+
});
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
async fetch(request, env, ctx) {
|
|
217
|
+
const start = Date.now();
|
|
218
|
+
const url = new URL(request.url);
|
|
219
|
+
const rsContext = {
|
|
220
|
+
transport,
|
|
221
|
+
sessionId: transport.sessionId,
|
|
222
|
+
config,
|
|
223
|
+
emit
|
|
224
|
+
};
|
|
225
|
+
return _contextStorage.run(rsContext, async () => {
|
|
226
|
+
try {
|
|
227
|
+
const response = await handler.fetch(request, env, ctx);
|
|
228
|
+
const duration = Date.now() - start;
|
|
229
|
+
const networkEvent = {
|
|
230
|
+
eventId: generateId(),
|
|
231
|
+
sessionId: transport.sessionId,
|
|
232
|
+
timestamp: start,
|
|
233
|
+
eventType: "network",
|
|
234
|
+
url: url.pathname + url.search,
|
|
235
|
+
method: request.method,
|
|
236
|
+
status: response.status,
|
|
237
|
+
duration,
|
|
238
|
+
requestHeaders: extractHeaders(request.headers),
|
|
239
|
+
responseHeaders: extractHeaders(response.headers),
|
|
240
|
+
requestBodySize: 0,
|
|
241
|
+
responseBodySize: 0,
|
|
242
|
+
ttfb: duration,
|
|
243
|
+
source: "workers",
|
|
244
|
+
direction: "incoming",
|
|
245
|
+
cfProperties: extractCfProperties(request)
|
|
246
|
+
};
|
|
247
|
+
emit(networkEvent);
|
|
248
|
+
ctx.waitUntil(transport.flush());
|
|
249
|
+
return response;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const duration = Date.now() - start;
|
|
252
|
+
const errorEvent = {
|
|
253
|
+
eventId: generateId(),
|
|
254
|
+
sessionId: transport.sessionId,
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
eventType: "console",
|
|
257
|
+
level: "error",
|
|
258
|
+
message: error instanceof Error ? error.message : String(error),
|
|
259
|
+
args: [error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error)],
|
|
260
|
+
stackTrace: error instanceof Error ? error.stack : void 0
|
|
261
|
+
};
|
|
262
|
+
emit(errorEvent);
|
|
263
|
+
const networkEvent = {
|
|
264
|
+
eventId: generateId(),
|
|
265
|
+
sessionId: transport.sessionId,
|
|
266
|
+
timestamp: start,
|
|
267
|
+
eventType: "network",
|
|
268
|
+
url: url.pathname + url.search,
|
|
269
|
+
method: request.method,
|
|
270
|
+
status: 500,
|
|
271
|
+
duration,
|
|
272
|
+
requestHeaders: extractHeaders(request.headers),
|
|
273
|
+
responseHeaders: {},
|
|
274
|
+
requestBodySize: 0,
|
|
275
|
+
responseBodySize: 0,
|
|
276
|
+
ttfb: duration,
|
|
277
|
+
source: "workers",
|
|
278
|
+
direction: "incoming",
|
|
279
|
+
cfProperties: extractCfProperties(request),
|
|
280
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
281
|
+
};
|
|
282
|
+
emit(networkEvent);
|
|
283
|
+
ctx.waitUntil(transport.flush());
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function extractCfProperties(request) {
|
|
291
|
+
const cf = request.cf;
|
|
292
|
+
if (!cf) return void 0;
|
|
293
|
+
return {
|
|
294
|
+
colo: cf.colo,
|
|
295
|
+
country: cf.country,
|
|
296
|
+
city: cf.city,
|
|
297
|
+
region: cf.region,
|
|
298
|
+
asn: cf.asn,
|
|
299
|
+
httpProtocol: cf.httpProtocol,
|
|
300
|
+
tlsVersion: cf.tlsVersion
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/bindings/d1.ts
|
|
305
|
+
function instrumentD1(db, emit, options) {
|
|
306
|
+
return {
|
|
307
|
+
prepare(query) {
|
|
308
|
+
const stmt = db.prepare(query);
|
|
309
|
+
return instrumentStatement(stmt, query, emit, options);
|
|
310
|
+
},
|
|
311
|
+
async batch(statements) {
|
|
312
|
+
const start = Date.now();
|
|
313
|
+
try {
|
|
314
|
+
const results = await db.batch(statements);
|
|
315
|
+
const duration = Date.now() - start;
|
|
316
|
+
emit({
|
|
317
|
+
eventId: generateId(),
|
|
318
|
+
sessionId: options.sessionId,
|
|
319
|
+
timestamp: start,
|
|
320
|
+
eventType: "database",
|
|
321
|
+
query: `BATCH (${statements.length} statements)`,
|
|
322
|
+
normalizedQuery: "BATCH",
|
|
323
|
+
duration,
|
|
324
|
+
tablesAccessed: [],
|
|
325
|
+
operation: "OTHER",
|
|
326
|
+
source: "d1",
|
|
327
|
+
rowsReturned: results.reduce((sum, r) => sum + (r.results?.length ?? 0), 0)
|
|
328
|
+
});
|
|
329
|
+
return results;
|
|
330
|
+
} catch (err) {
|
|
331
|
+
const duration = Date.now() - start;
|
|
332
|
+
emit({
|
|
333
|
+
eventId: generateId(),
|
|
334
|
+
sessionId: options.sessionId,
|
|
335
|
+
timestamp: start,
|
|
336
|
+
eventType: "database",
|
|
337
|
+
query: `BATCH (${statements.length} statements)`,
|
|
338
|
+
normalizedQuery: "BATCH",
|
|
339
|
+
duration,
|
|
340
|
+
tablesAccessed: [],
|
|
341
|
+
operation: "OTHER",
|
|
342
|
+
source: "d1",
|
|
343
|
+
error: err.message
|
|
344
|
+
});
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
async exec(query) {
|
|
349
|
+
const start = Date.now();
|
|
350
|
+
try {
|
|
351
|
+
const result = await db.exec(query);
|
|
352
|
+
emit({
|
|
353
|
+
eventId: generateId(),
|
|
354
|
+
sessionId: options.sessionId,
|
|
355
|
+
timestamp: start,
|
|
356
|
+
eventType: "database",
|
|
357
|
+
query,
|
|
358
|
+
normalizedQuery: normalizeD1Query(query),
|
|
359
|
+
duration: result.duration,
|
|
360
|
+
tablesAccessed: extractTables(query),
|
|
361
|
+
operation: parseOp(query),
|
|
362
|
+
source: "d1",
|
|
363
|
+
rowsAffected: result.count
|
|
364
|
+
});
|
|
365
|
+
return result;
|
|
366
|
+
} catch (err) {
|
|
367
|
+
const duration = Date.now() - start;
|
|
368
|
+
emit({
|
|
369
|
+
eventId: generateId(),
|
|
370
|
+
sessionId: options.sessionId,
|
|
371
|
+
timestamp: start,
|
|
372
|
+
eventType: "database",
|
|
373
|
+
query,
|
|
374
|
+
normalizedQuery: normalizeD1Query(query),
|
|
375
|
+
duration,
|
|
376
|
+
tablesAccessed: extractTables(query),
|
|
377
|
+
operation: parseOp(query),
|
|
378
|
+
source: "d1",
|
|
379
|
+
error: err.message
|
|
380
|
+
});
|
|
381
|
+
throw err;
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
dump() {
|
|
385
|
+
return db.dump();
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function instrumentStatement(stmt, query, emit, options) {
|
|
390
|
+
const op = parseOp(query);
|
|
391
|
+
const tables = extractTables(query);
|
|
392
|
+
const normalized = normalizeD1Query(query);
|
|
393
|
+
function makeEvent(start, duration, extra = {}) {
|
|
394
|
+
return {
|
|
395
|
+
eventId: generateId(),
|
|
396
|
+
sessionId: options.sessionId,
|
|
397
|
+
timestamp: start,
|
|
398
|
+
eventType: "database",
|
|
399
|
+
query,
|
|
400
|
+
normalizedQuery: normalized,
|
|
401
|
+
duration,
|
|
402
|
+
tablesAccessed: tables,
|
|
403
|
+
operation: op,
|
|
404
|
+
source: "d1",
|
|
405
|
+
...extra
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
async function wrapAsync(fn, start, getExtra) {
|
|
409
|
+
try {
|
|
410
|
+
const result = await fn();
|
|
411
|
+
const duration = Date.now() - start;
|
|
412
|
+
emit(makeEvent(start, duration, getExtra?.(result)));
|
|
413
|
+
return result;
|
|
414
|
+
} catch (err) {
|
|
415
|
+
const duration = Date.now() - start;
|
|
416
|
+
emit(makeEvent(start, duration, { error: err.message }));
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
bind(...values) {
|
|
422
|
+
const bound = stmt.bind(...values);
|
|
423
|
+
return instrumentStatement(bound, query, emit, options);
|
|
424
|
+
},
|
|
425
|
+
first(colName) {
|
|
426
|
+
const start = Date.now();
|
|
427
|
+
return wrapAsync(
|
|
428
|
+
() => stmt.first(colName),
|
|
429
|
+
start,
|
|
430
|
+
(result) => ({ rowsReturned: result !== null ? 1 : 0 })
|
|
431
|
+
);
|
|
432
|
+
},
|
|
433
|
+
run() {
|
|
434
|
+
const start = Date.now();
|
|
435
|
+
return wrapAsync(
|
|
436
|
+
() => stmt.run(),
|
|
437
|
+
start,
|
|
438
|
+
(result) => ({
|
|
439
|
+
rowsAffected: result.meta?.changes,
|
|
440
|
+
duration: result.meta?.duration ?? Date.now() - start
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
},
|
|
444
|
+
all() {
|
|
445
|
+
const start = Date.now();
|
|
446
|
+
return wrapAsync(
|
|
447
|
+
() => stmt.all(),
|
|
448
|
+
start,
|
|
449
|
+
(result) => ({
|
|
450
|
+
rowsReturned: result.results?.length ?? 0,
|
|
451
|
+
duration: result.meta?.duration ?? Date.now() - start
|
|
452
|
+
})
|
|
453
|
+
);
|
|
454
|
+
},
|
|
455
|
+
raw(rawOptions) {
|
|
456
|
+
const start = Date.now();
|
|
457
|
+
return wrapAsync(
|
|
458
|
+
() => stmt.raw(rawOptions),
|
|
459
|
+
start,
|
|
460
|
+
(result) => ({ rowsReturned: result.length })
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function parseOp(query) {
|
|
466
|
+
const trimmed = query.trimStart().toUpperCase();
|
|
467
|
+
if (trimmed.startsWith("SELECT")) return "SELECT";
|
|
468
|
+
if (trimmed.startsWith("INSERT")) return "INSERT";
|
|
469
|
+
if (trimmed.startsWith("UPDATE")) return "UPDATE";
|
|
470
|
+
if (trimmed.startsWith("DELETE")) return "DELETE";
|
|
471
|
+
return "OTHER";
|
|
472
|
+
}
|
|
473
|
+
function extractTables(query) {
|
|
474
|
+
const tables = [];
|
|
475
|
+
const fromMatch = query.match(/\bFROM\s+(\w+)/i);
|
|
476
|
+
if (fromMatch) tables.push(fromMatch[1]);
|
|
477
|
+
const intoMatch = query.match(/\bINTO\s+(\w+)/i);
|
|
478
|
+
if (intoMatch) tables.push(intoMatch[1]);
|
|
479
|
+
const updateMatch = query.match(/\bUPDATE\s+(\w+)/i);
|
|
480
|
+
if (updateMatch) tables.push(updateMatch[1]);
|
|
481
|
+
const joinMatches = query.matchAll(/\bJOIN\s+(\w+)/gi);
|
|
482
|
+
for (const m of joinMatches) tables.push(m[1]);
|
|
483
|
+
return [...new Set(tables)];
|
|
484
|
+
}
|
|
485
|
+
function normalizeD1Query(query) {
|
|
486
|
+
return query.replace(/'[^']*'/g, "?").replace(/\b\d+\b/g, "?").replace(/\s+/g, " ").trim();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/bindings/kv.ts
|
|
490
|
+
function instrumentKV(kv, emit, options) {
|
|
491
|
+
function sanitizeKey(key) {
|
|
492
|
+
const safe = key.length > 200 ? key.slice(0, 200) + "\u2026" : key;
|
|
493
|
+
return safe.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
494
|
+
}
|
|
495
|
+
function emitOp(op, key, start, duration, extra = {}) {
|
|
496
|
+
emit({
|
|
497
|
+
eventId: generateId(),
|
|
498
|
+
sessionId: options.sessionId,
|
|
499
|
+
timestamp: start,
|
|
500
|
+
eventType: "database",
|
|
501
|
+
query: `KV.${op}("${sanitizeKey(key)}")`,
|
|
502
|
+
normalizedQuery: `KV.${op}(?)`,
|
|
503
|
+
duration,
|
|
504
|
+
tablesAccessed: [],
|
|
505
|
+
operation: op === "get" || op === "getWithMetadata" || op === "list" ? "SELECT" : op === "put" ? "INSERT" : op === "delete" ? "DELETE" : "OTHER",
|
|
506
|
+
source: "kv",
|
|
507
|
+
...extra
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
async get(key, kvOptions) {
|
|
512
|
+
const start = Date.now();
|
|
513
|
+
try {
|
|
514
|
+
const result = await kv.get(key, kvOptions);
|
|
515
|
+
emitOp("get", key, start, Date.now() - start, { rowsReturned: result !== null ? 1 : 0 });
|
|
516
|
+
return result;
|
|
517
|
+
} catch (err) {
|
|
518
|
+
emitOp("get", key, start, Date.now() - start, { error: err.message });
|
|
519
|
+
throw err;
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
async getWithMetadata(key, kvOptions) {
|
|
523
|
+
const start = Date.now();
|
|
524
|
+
try {
|
|
525
|
+
const result = await kv.getWithMetadata(key, kvOptions);
|
|
526
|
+
emitOp("getWithMetadata", key, start, Date.now() - start, { rowsReturned: result.value !== null ? 1 : 0 });
|
|
527
|
+
return result;
|
|
528
|
+
} catch (err) {
|
|
529
|
+
emitOp("getWithMetadata", key, start, Date.now() - start, { error: err.message });
|
|
530
|
+
throw err;
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
async put(key, value, kvOptions) {
|
|
534
|
+
const start = Date.now();
|
|
535
|
+
try {
|
|
536
|
+
await kv.put(key, value, kvOptions);
|
|
537
|
+
emitOp("put", key, start, Date.now() - start, { rowsAffected: 1 });
|
|
538
|
+
} catch (err) {
|
|
539
|
+
emitOp("put", key, start, Date.now() - start, { error: err.message });
|
|
540
|
+
throw err;
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
async delete(key) {
|
|
544
|
+
const start = Date.now();
|
|
545
|
+
try {
|
|
546
|
+
await kv.delete(key);
|
|
547
|
+
emitOp("delete", key, start, Date.now() - start, { rowsAffected: 1 });
|
|
548
|
+
} catch (err) {
|
|
549
|
+
emitOp("delete", key, start, Date.now() - start, { error: err.message });
|
|
550
|
+
throw err;
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
async list(kvOptions) {
|
|
554
|
+
const start = Date.now();
|
|
555
|
+
try {
|
|
556
|
+
const result = await kv.list(kvOptions);
|
|
557
|
+
emitOp("list", "*", start, Date.now() - start, { rowsReturned: result.keys.length });
|
|
558
|
+
return result;
|
|
559
|
+
} catch (err) {
|
|
560
|
+
emitOp("list", "*", start, Date.now() - start, { error: err.message });
|
|
561
|
+
throw err;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/bindings/r2.ts
|
|
568
|
+
function instrumentR2(bucket, emit, options) {
|
|
569
|
+
function sanitizeKey(key) {
|
|
570
|
+
const safe = key.length > 200 ? key.slice(0, 200) + "\u2026" : key;
|
|
571
|
+
return safe.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
572
|
+
}
|
|
573
|
+
function emitOp(op, key, start, duration, extra = {}) {
|
|
574
|
+
emit({
|
|
575
|
+
eventId: generateId(),
|
|
576
|
+
sessionId: options.sessionId,
|
|
577
|
+
timestamp: start,
|
|
578
|
+
eventType: "database",
|
|
579
|
+
query: `R2.${op}("${sanitizeKey(key)}")`,
|
|
580
|
+
normalizedQuery: `R2.${op}(?)`,
|
|
581
|
+
duration,
|
|
582
|
+
tablesAccessed: [],
|
|
583
|
+
operation: op === "get" || op === "list" || op === "head" ? "SELECT" : op === "put" ? "INSERT" : op === "delete" ? "DELETE" : "OTHER",
|
|
584
|
+
source: "r2",
|
|
585
|
+
...extra
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
async get(key, r2Options) {
|
|
590
|
+
const start = Date.now();
|
|
591
|
+
try {
|
|
592
|
+
const result = await bucket.get(key, r2Options);
|
|
593
|
+
emitOp("get", key, start, Date.now() - start, {
|
|
594
|
+
rowsReturned: result !== null ? 1 : 0,
|
|
595
|
+
label: result ? `${result.size} bytes` : void 0
|
|
596
|
+
});
|
|
597
|
+
return result;
|
|
598
|
+
} catch (err) {
|
|
599
|
+
emitOp("get", key, start, Date.now() - start, { error: err.message });
|
|
600
|
+
throw err;
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
async put(key, value, r2Options) {
|
|
604
|
+
const start = Date.now();
|
|
605
|
+
try {
|
|
606
|
+
const result = await bucket.put(key, value, r2Options);
|
|
607
|
+
emitOp("put", key, start, Date.now() - start, {
|
|
608
|
+
rowsAffected: 1,
|
|
609
|
+
label: result ? `${result.size} bytes` : void 0
|
|
610
|
+
});
|
|
611
|
+
return result;
|
|
612
|
+
} catch (err) {
|
|
613
|
+
emitOp("put", key, start, Date.now() - start, { error: err.message });
|
|
614
|
+
throw err;
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
async delete(keys) {
|
|
618
|
+
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
619
|
+
const keyLabel = keyList.length === 1 ? keyList[0] : `${keyList.length} keys`;
|
|
620
|
+
const start = Date.now();
|
|
621
|
+
try {
|
|
622
|
+
await bucket.delete(keys);
|
|
623
|
+
emitOp("delete", keyLabel, start, Date.now() - start, { rowsAffected: keyList.length });
|
|
624
|
+
} catch (err) {
|
|
625
|
+
emitOp("delete", keyLabel, start, Date.now() - start, { error: err.message });
|
|
626
|
+
throw err;
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
async list(r2Options) {
|
|
630
|
+
const start = Date.now();
|
|
631
|
+
try {
|
|
632
|
+
const result = await bucket.list(r2Options);
|
|
633
|
+
emitOp("list", "*", start, Date.now() - start, { rowsReturned: result.objects.length });
|
|
634
|
+
return result;
|
|
635
|
+
} catch (err) {
|
|
636
|
+
emitOp("list", "*", start, Date.now() - start, { error: err.message });
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
async head(key) {
|
|
641
|
+
const start = Date.now();
|
|
642
|
+
try {
|
|
643
|
+
const result = await bucket.head(key);
|
|
644
|
+
emitOp("head", key, start, Date.now() - start, {
|
|
645
|
+
rowsReturned: result !== null ? 1 : 0,
|
|
646
|
+
label: result ? `${result.size} bytes` : void 0
|
|
647
|
+
});
|
|
648
|
+
return result;
|
|
649
|
+
} catch (err) {
|
|
650
|
+
emitOp("head", key, start, Date.now() - start, { error: err.message });
|
|
651
|
+
throw err;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/index.ts
|
|
658
|
+
function scopeD1(db) {
|
|
659
|
+
const ctx = getActiveContext();
|
|
660
|
+
if (!ctx) return db;
|
|
661
|
+
return instrumentD1(db, ctx.emit, { sessionId: ctx.sessionId });
|
|
662
|
+
}
|
|
663
|
+
function scopeKV(kv) {
|
|
664
|
+
const ctx = getActiveContext();
|
|
665
|
+
if (!ctx) return kv;
|
|
666
|
+
return instrumentKV(kv, ctx.emit, { sessionId: ctx.sessionId });
|
|
667
|
+
}
|
|
668
|
+
function scopeR2(bucket) {
|
|
669
|
+
const ctx = getActiveContext();
|
|
670
|
+
if (!ctx) return bucket;
|
|
671
|
+
return instrumentR2(bucket, ctx.emit, { sessionId: ctx.sessionId });
|
|
672
|
+
}
|
|
673
|
+
function track(name, properties) {
|
|
674
|
+
const ctx = getActiveContext();
|
|
675
|
+
if (!ctx) return;
|
|
676
|
+
const event = {
|
|
677
|
+
eventId: generateId(),
|
|
678
|
+
sessionId: ctx.sessionId,
|
|
679
|
+
timestamp: Date.now(),
|
|
680
|
+
eventType: "custom",
|
|
681
|
+
name,
|
|
682
|
+
...properties && { properties }
|
|
683
|
+
};
|
|
684
|
+
ctx.emit(event);
|
|
685
|
+
}
|
|
686
|
+
function addBreadcrumb(message, data) {
|
|
687
|
+
const ctx = getActiveContext();
|
|
688
|
+
if (!ctx) return;
|
|
689
|
+
const event = {
|
|
690
|
+
eventId: generateId(),
|
|
691
|
+
sessionId: ctx.sessionId,
|
|
692
|
+
timestamp: Date.now(),
|
|
693
|
+
eventType: "ui",
|
|
694
|
+
action: "breadcrumb",
|
|
695
|
+
target: "manual",
|
|
696
|
+
text: message,
|
|
697
|
+
...data && { data }
|
|
698
|
+
};
|
|
699
|
+
ctx.emit(event);
|
|
700
|
+
}
|
|
701
|
+
export {
|
|
702
|
+
Sampler,
|
|
703
|
+
WorkersTransport,
|
|
704
|
+
addBreadcrumb,
|
|
705
|
+
generateId,
|
|
706
|
+
generateSessionId,
|
|
707
|
+
getActiveContext,
|
|
708
|
+
instrumentD1,
|
|
709
|
+
instrumentKV,
|
|
710
|
+
instrumentR2,
|
|
711
|
+
scopeD1,
|
|
712
|
+
scopeKV,
|
|
713
|
+
scopeR2,
|
|
714
|
+
track,
|
|
715
|
+
withRuntimeScope
|
|
716
|
+
};
|
|
717
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handler.ts","../src/utils.ts","../src/transport.ts","../src/sampler.ts","../src/interceptors/console.ts","../src/bindings/d1.ts","../src/bindings/kv.ts","../src/bindings/r2.ts","../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type {\n WorkersConfig,\n WorkersRuntimeEvent,\n NetworkEvent,\n ConsoleEvent,\n UserContext,\n} from './types.js';\nimport { WorkersTransport } from './transport.js';\nimport { Sampler } from './sampler.js';\nimport { interceptConsole } from './interceptors/console.js';\nimport { generateId } from './utils.js';\n\n// ============================================================\n// withRuntimeScope — wraps a Workers fetch handler\n// Captures request/response metrics, errors, console output.\n// Flushes all events via ctx.waitUntil() at end of request.\n// ============================================================\n\n/** Internal context exposed to binding wrappers */\nexport interface RuntimeScopeContext {\n transport: WorkersTransport;\n sessionId: string;\n config: WorkersConfig;\n emit: (event: WorkersRuntimeEvent) => void;\n}\n\n// Per-request context — AsyncLocalStorage ensures isolation under concurrent requests.\n// Available in Workers runtime with nodejs_compat flag and in Node.js 16+.\nconst _contextStorage = new AsyncLocalStorage<RuntimeScopeContext>();\n\n/** Get the active RuntimeScope context (used by binding wrappers) */\nexport function getActiveContext(): RuntimeScopeContext | null {\n return _contextStorage.getStore() ?? null;\n}\n\nconst DEFAULT_REDACT_HEADERS = ['authorization', 'cookie', 'set-cookie'];\n\nexport interface WorkersFetchHandler {\n fetch(\n request: Request,\n env: unknown,\n ctx: ExecutionContext,\n ): Response | Promise<Response>;\n}\n\n/**\n * Wrap a Workers fetch handler to capture request/response metrics,\n * errors, and console output. Events are flushed via ctx.waitUntil().\n *\n * @example\n * ```ts\n * import { withRuntimeScope } from '@runtimescope/workers-sdk';\n *\n * export default withRuntimeScope({\n * async fetch(request, env, ctx) {\n * return new Response('Hello!');\n * },\n * }, { appName: 'my-worker' });\n * ```\n */\nexport function withRuntimeScope(\n handler: WorkersFetchHandler,\n config: WorkersConfig,\n): WorkersFetchHandler {\n const transport = new WorkersTransport(config);\n const sampler = config.sampleRate !== undefined && config.sampleRate < 1\n ? new Sampler({ sampleRate: config.sampleRate })\n : null;\n\n const redactHeaders = config.redactHeaders ?? DEFAULT_REDACT_HEADERS;\n const redactSet = new Set(redactHeaders.map((h) => h.toLowerCase()));\n\n function emit(event: WorkersRuntimeEvent): void {\n if (sampler && !sampler.shouldSample(event)) return;\n if (config.beforeSend) {\n const filtered = config.beforeSend(event);\n if (!filtered) return;\n transport.queue(filtered);\n } else {\n transport.queue(event);\n }\n }\n\n // Set up console interceptor (persistent across requests)\n let restoreConsole: (() => void) | null = null;\n if (config.captureConsole !== false) {\n restoreConsole = interceptConsole(emit, {\n sessionId: transport.sessionId,\n });\n }\n\n function extractHeaders(headers: Headers): Record<string, string> {\n if (!config.captureHeaders) return {};\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key] = redactSet.has(key.toLowerCase()) ? '[REDACTED]' : value;\n });\n return result;\n }\n\n return {\n async fetch(\n request: Request,\n env: unknown,\n ctx: ExecutionContext,\n ): Promise<Response> {\n const start = Date.now();\n const url = new URL(request.url);\n\n // Run handler inside AsyncLocalStorage so binding wrappers\n // get the correct per-request context even under concurrency\n const rsContext: RuntimeScopeContext = {\n transport,\n sessionId: transport.sessionId,\n config,\n emit,\n };\n\n return _contextStorage.run(rsContext, async () => {\n try {\n const response = await handler.fetch(request, env, ctx);\n const duration = Date.now() - start;\n\n const networkEvent: NetworkEvent = {\n eventId: generateId(),\n sessionId: transport.sessionId,\n timestamp: start,\n eventType: 'network',\n url: url.pathname + url.search,\n method: request.method,\n status: response.status,\n duration,\n requestHeaders: extractHeaders(request.headers),\n responseHeaders: extractHeaders(response.headers),\n requestBodySize: 0,\n responseBodySize: 0,\n ttfb: duration,\n source: 'workers',\n direction: 'incoming',\n cfProperties: extractCfProperties(request),\n };\n\n emit(networkEvent);\n ctx.waitUntil(transport.flush());\n return response;\n } catch (error) {\n const duration = Date.now() - start;\n\n // Capture the error as a console error event\n const errorEvent: ConsoleEvent = {\n eventId: generateId(),\n sessionId: transport.sessionId,\n timestamp: Date.now(),\n eventType: 'console',\n level: 'error',\n message: error instanceof Error ? error.message : String(error),\n args: [error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error)],\n stackTrace: error instanceof Error ? error.stack : undefined,\n };\n\n emit(errorEvent);\n\n // Also capture the failed request\n const networkEvent: NetworkEvent = {\n eventId: generateId(),\n sessionId: transport.sessionId,\n timestamp: start,\n eventType: 'network',\n url: url.pathname + url.search,\n method: request.method,\n status: 500,\n duration,\n requestHeaders: extractHeaders(request.headers),\n responseHeaders: {},\n requestBodySize: 0,\n responseBodySize: 0,\n ttfb: duration,\n source: 'workers',\n direction: 'incoming',\n cfProperties: extractCfProperties(request),\n errorMessage: error instanceof Error ? error.message : String(error),\n };\n\n emit(networkEvent);\n ctx.waitUntil(transport.flush());\n throw error;\n }\n });\n },\n };\n}\n\nfunction extractCfProperties(request: Request): NetworkEvent['cfProperties'] | undefined {\n // request.cf is Cloudflare-specific — may not exist in non-CF environments\n const cf = (request as unknown as { cf?: Record<string, unknown> }).cf;\n if (!cf) return undefined;\n\n return {\n colo: cf.colo as string | undefined,\n country: cf.country as string | undefined,\n city: cf.city as string | undefined,\n region: cf.region as string | undefined,\n asn: cf.asn as number | undefined,\n httpProtocol: cf.httpProtocol as string | undefined,\n tlsVersion: cf.tlsVersion as string | undefined,\n };\n}\n","// ============================================================\n// Utility functions — no Node.js APIs, Workers-safe\n// ============================================================\n\n/** Generate a random event ID using Web Crypto API (available in Workers) */\nexport function generateId(): string {\n return crypto.randomUUID();\n}\n\n/** Generate a session ID with worker prefix */\nexport function generateSessionId(): string {\n return `wk-${crypto.randomUUID().slice(0, 16)}`;\n}\n\n/** Safely serialize a value, handling circular references, functions, symbols, and errors. */\nexport function safeSerialize(value: unknown, maxDepth = 5): unknown {\n const seen = new WeakSet();\n\n function walk(val: unknown, depth: number): unknown {\n if (depth > maxDepth) return '[max depth]';\n if (val === null || val === undefined) return val;\n if (typeof val === 'function') return `[Function: ${val.name || 'anonymous'}]`;\n if (typeof val === 'symbol') return val.toString();\n if (typeof val === 'bigint') return val.toString();\n if (typeof val !== 'object') return val;\n\n if (val instanceof Error) {\n return { name: val.name, message: val.message, stack: val.stack };\n }\n if (val instanceof Date) {\n return val.toISOString();\n }\n if (val instanceof RegExp) {\n return val.toString();\n }\n\n if (seen.has(val as object)) return '[Circular]';\n seen.add(val as object);\n\n if (Array.isArray(val)) {\n return val.map((v) => walk(v, depth + 1));\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(val as Record<string, unknown>)) {\n result[key] = walk((val as Record<string, unknown>)[key], depth + 1);\n }\n return result;\n }\n\n return walk(value, 0);\n}\n","import type { WorkersRuntimeEvent, WorkersConfig, UserContext } from './types.js';\nimport { generateSessionId } from './utils.js';\n\n// ============================================================\n// Workers Transport\n// Flush events via ctx.waitUntil(fetch(...)) — no timers, no intervals.\n// Matches collector's POST /api/events endpoint.\n// ============================================================\n\nconst SDK_VERSION = '0.8.0';\nconst DEFAULT_ENDPOINT = 'http://localhost:9091/api/events';\n\nexport class WorkersTransport {\n private buffer: WorkersRuntimeEvent[] = [];\n private maxQueueSize: number;\n private url: string;\n private authToken: string | undefined;\n private appName: string;\n private sessionRegistered = false;\n private _droppedCount = 0;\n\n readonly sessionId: string;\n\n constructor(config: WorkersConfig) {\n this.sessionId = generateSessionId();\n this.url = config.httpEndpoint ?? DEFAULT_ENDPOINT;\n this.appName = config.appName;\n this.authToken = config.authToken;\n this.maxQueueSize = config.maxQueueSize ?? 500;\n }\n\n get droppedCount(): number {\n return this._droppedCount;\n }\n\n queue(event: WorkersRuntimeEvent): void {\n if (this.buffer.length >= this.maxQueueSize) {\n this.buffer.shift();\n this._droppedCount++;\n }\n this.buffer.push(event);\n }\n\n /**\n * Flush all buffered events to the collector.\n * Call via ctx.waitUntil(transport.flush()) at the end of each request.\n * Retries once on failure with a short delay.\n */\n async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer.splice(0);\n const payload: Record<string, unknown> = {\n sessionId: this.sessionId,\n events,\n };\n\n // Include appName/sdkVersion on first request to auto-register session\n if (!this.sessionRegistered) {\n payload.appName = this.appName;\n payload.sdkVersion = SDK_VERSION;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n\n const body = JSON.stringify(payload);\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5_000);\n\n const response = await fetch(this.url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n this.sessionRegistered = true;\n return;\n }\n\n // Server error (5xx) — retry once\n if (response.status >= 500 && attempt === 0) continue;\n // Client error (4xx) — don't retry\n return;\n } catch {\n // Network error or timeout — retry once\n if (attempt === 0) {\n await new Promise((r) => setTimeout(r, 200));\n continue;\n }\n // Second attempt failed — events are lost (acceptable for edge observability)\n }\n }\n }\n}\n","import type { WorkersRuntimeEvent } from './types.js';\n\n// ============================================================\n// Sampler — probabilistic + rate limiting\n// Adapted from server-sdk, no Node.js APIs.\n// ============================================================\n\nexport interface SamplerConfig {\n /** Probabilistic sample rate: 0.0–1.0 (default: 1.0 = keep all) */\n sampleRate?: number;\n}\n\nexport class Sampler {\n private _droppedCount = 0;\n\n constructor(private config: SamplerConfig) {}\n\n get droppedCount(): number {\n return this._droppedCount;\n }\n\n shouldSample(_event: WorkersRuntimeEvent): boolean {\n const rate = this.config.sampleRate;\n if (rate !== undefined && rate < 1) {\n if (Math.random() > rate) {\n this._droppedCount++;\n return false;\n }\n }\n return true;\n }\n}\n","import type { ConsoleEvent, ConsoleLevel } from '../types.js';\nimport { generateId, safeSerialize } from '../utils.js';\n\n// ============================================================\n// Console Interceptor — Workers-safe\n// Patches console.log/warn/error/info/debug/trace.\n// Returns a restore function for cleanup.\n// ============================================================\n\ntype EmitFn = (event: ConsoleEvent) => void;\n\nconst LEVELS: ConsoleLevel[] = ['log', 'warn', 'error', 'info', 'debug', 'trace'];\n\nexport interface ConsoleInterceptorOptions {\n levels?: ConsoleLevel[];\n sessionId: string;\n}\n\nexport function interceptConsole(\n emit: EmitFn,\n options: ConsoleInterceptorOptions\n): () => void {\n const levels = options.levels ?? LEVELS;\n const originals: Record<string, (...args: unknown[]) => void> = {};\n\n for (const level of levels) {\n originals[level] = console[level].bind(console);\n\n console[level] = (...args: unknown[]) => {\n const message = args\n .map((a) => (typeof a === 'string' ? a : stringifyArg(a)))\n .join(' ');\n\n const event: ConsoleEvent = {\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: Date.now(),\n eventType: 'console',\n level,\n message,\n args: args.map((a) => safeSerialize(a, 3)),\n stackTrace:\n level === 'error' || level === 'trace'\n ? new Error().stack?.split('\\n').slice(2).join('\\n')\n : undefined,\n };\n\n // emit() already applies beforeSend — don't double-filter\n emit(event);\n\n // Call original — MUST use saved reference\n originals[level](...args);\n };\n }\n\n return () => {\n for (const level of levels) {\n console[level] = originals[level];\n }\n };\n}\n\nfunction stringifyArg(arg: unknown): string {\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n}\n","import type {\n D1DatabaseBinding,\n D1PreparedStatementBinding,\n D1Result,\n D1ExecResult,\n DatabaseEvent,\n} from '../types.js';\nimport { generateId } from '../utils.js';\n\n// ============================================================\n// D1 Binding Wrapper\n// Wraps a D1Database to capture SQL queries, timing, and results.\n// Events emitted as 'database' type with source: 'd1'.\n// ============================================================\n\ntype EmitFn = (event: DatabaseEvent) => void;\n\ninterface D1InstrumentOptions {\n sessionId: string;\n}\n\n/**\n * Wrap a D1 database binding to capture queries.\n *\n * @example\n * ```ts\n * const db = instrumentD1(env.DB, transport.sessionId);\n * const results = await db.prepare('SELECT * FROM users').all();\n * ```\n */\nexport function instrumentD1(\n db: D1DatabaseBinding,\n emit: EmitFn,\n options: D1InstrumentOptions,\n): D1DatabaseBinding {\n return {\n prepare(query: string): D1PreparedStatementBinding {\n const stmt = db.prepare(query);\n return instrumentStatement(stmt, query, emit, options);\n },\n\n async batch<T = unknown>(statements: D1PreparedStatementBinding[]): Promise<D1Result<T>[]> {\n const start = Date.now();\n try {\n const results = await db.batch<T>(statements);\n const duration = Date.now() - start;\n emit({\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query: `BATCH (${statements.length} statements)`,\n normalizedQuery: 'BATCH',\n duration,\n tablesAccessed: [],\n operation: 'OTHER',\n source: 'd1',\n rowsReturned: results.reduce((sum, r) => sum + (r.results?.length ?? 0), 0),\n });\n return results;\n } catch (err) {\n const duration = Date.now() - start;\n emit({\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query: `BATCH (${statements.length} statements)`,\n normalizedQuery: 'BATCH',\n duration,\n tablesAccessed: [],\n operation: 'OTHER',\n source: 'd1',\n error: (err as Error).message,\n });\n throw err;\n }\n },\n\n async exec(query: string): Promise<D1ExecResult> {\n const start = Date.now();\n try {\n const result = await db.exec(query);\n emit({\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query,\n normalizedQuery: normalizeD1Query(query),\n duration: result.duration,\n tablesAccessed: extractTables(query),\n operation: parseOp(query),\n source: 'd1',\n rowsAffected: result.count,\n });\n return result;\n } catch (err) {\n const duration = Date.now() - start;\n emit({\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query,\n normalizedQuery: normalizeD1Query(query),\n duration,\n tablesAccessed: extractTables(query),\n operation: parseOp(query),\n source: 'd1',\n error: (err as Error).message,\n });\n throw err;\n }\n },\n\n dump(): Promise<ArrayBuffer> {\n return db.dump();\n },\n };\n}\n\nfunction instrumentStatement(\n stmt: D1PreparedStatementBinding,\n query: string,\n emit: EmitFn,\n options: D1InstrumentOptions,\n): D1PreparedStatementBinding {\n const op = parseOp(query);\n const tables = extractTables(query);\n const normalized = normalizeD1Query(query);\n\n function makeEvent(start: number, duration: number, extra: Partial<DatabaseEvent> = {}): DatabaseEvent {\n return {\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query,\n normalizedQuery: normalized,\n duration,\n tablesAccessed: tables,\n operation: op,\n source: 'd1',\n ...extra,\n };\n }\n\n async function wrapAsync<T>(fn: () => Promise<T>, start: number, getExtra?: (result: T) => Partial<DatabaseEvent>): Promise<T> {\n try {\n const result = await fn();\n const duration = Date.now() - start;\n emit(makeEvent(start, duration, getExtra?.(result)));\n return result;\n } catch (err) {\n const duration = Date.now() - start;\n emit(makeEvent(start, duration, { error: (err as Error).message }));\n throw err;\n }\n }\n\n return {\n bind(...values: unknown[]): D1PreparedStatementBinding {\n const bound = stmt.bind(...values);\n return instrumentStatement(bound, query, emit, options);\n },\n\n first<T = unknown>(colName?: string): Promise<T | null> {\n const start = Date.now();\n return wrapAsync(\n () => stmt.first<T>(colName),\n start,\n (result) => ({ rowsReturned: result !== null ? 1 : 0 }),\n );\n },\n\n run<T = unknown>(): Promise<D1Result<T>> {\n const start = Date.now();\n return wrapAsync(\n () => stmt.run<T>(),\n start,\n (result) => ({\n rowsAffected: result.meta?.changes,\n duration: result.meta?.duration ?? (Date.now() - start),\n }),\n );\n },\n\n all<T = unknown>(): Promise<D1Result<T>> {\n const start = Date.now();\n return wrapAsync(\n () => stmt.all<T>(),\n start,\n (result) => ({\n rowsReturned: result.results?.length ?? 0,\n duration: result.meta?.duration ?? (Date.now() - start),\n }),\n );\n },\n\n raw<T = unknown>(rawOptions?: { columnNames?: boolean }): Promise<T[]> {\n const start = Date.now();\n return wrapAsync(\n () => stmt.raw<T>(rawOptions),\n start,\n (result) => ({ rowsReturned: result.length }),\n );\n },\n };\n}\n\n// --- SQL Parsing Helpers (lightweight, no dependencies) ---\n\nfunction parseOp(query: string): DatabaseEvent['operation'] {\n const trimmed = query.trimStart().toUpperCase();\n if (trimmed.startsWith('SELECT')) return 'SELECT';\n if (trimmed.startsWith('INSERT')) return 'INSERT';\n if (trimmed.startsWith('UPDATE')) return 'UPDATE';\n if (trimmed.startsWith('DELETE')) return 'DELETE';\n return 'OTHER';\n}\n\nfunction extractTables(query: string): string[] {\n const tables: string[] = [];\n const fromMatch = query.match(/\\bFROM\\s+(\\w+)/i);\n if (fromMatch) tables.push(fromMatch[1]);\n const intoMatch = query.match(/\\bINTO\\s+(\\w+)/i);\n if (intoMatch) tables.push(intoMatch[1]);\n const updateMatch = query.match(/\\bUPDATE\\s+(\\w+)/i);\n if (updateMatch) tables.push(updateMatch[1]);\n const joinMatches = query.matchAll(/\\bJOIN\\s+(\\w+)/gi);\n for (const m of joinMatches) tables.push(m[1]);\n return [...new Set(tables)];\n}\n\nfunction normalizeD1Query(query: string): string {\n return query\n .replace(/'[^']*'/g, '?')\n .replace(/\\b\\d+\\b/g, '?')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n","import type { KVNamespaceBinding, DatabaseEvent } from '../types.js';\nimport { generateId } from '../utils.js';\n\n// ============================================================\n// KV Namespace Wrapper\n// Wraps a KVNamespace to capture get/put/delete/list operations.\n// Events emitted as 'database' type with source: 'kv'.\n// ============================================================\n\ntype EmitFn = (event: DatabaseEvent) => void;\n\ninterface KVInstrumentOptions {\n sessionId: string;\n}\n\n/**\n * Wrap a KV namespace binding to capture operations.\n *\n * @example\n * ```ts\n * const kv = instrumentKV(env.MY_KV, emit, { sessionId });\n * await kv.put('key', 'value');\n * ```\n */\nexport function instrumentKV(\n kv: KVNamespaceBinding,\n emit: EmitFn,\n options: KVInstrumentOptions,\n): KVNamespaceBinding {\n function sanitizeKey(key: string): string {\n // Truncate and escape quotes/newlines to prevent malformed query strings\n const safe = key.length > 200 ? key.slice(0, 200) + '…' : key;\n return safe.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n');\n }\n\n function emitOp(op: string, key: string, start: number, duration: number, extra: Partial<DatabaseEvent> = {}): void {\n emit({\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query: `KV.${op}(\"${sanitizeKey(key)}\")`,\n normalizedQuery: `KV.${op}(?)`,\n duration,\n tablesAccessed: [],\n operation: op === 'get' || op === 'getWithMetadata' || op === 'list' ? 'SELECT' : op === 'put' ? 'INSERT' : op === 'delete' ? 'DELETE' : 'OTHER',\n source: 'kv',\n ...extra,\n });\n }\n\n return {\n async get(key: string, kvOptions?: unknown): Promise<string | null> {\n const start = Date.now();\n try {\n const result = await kv.get(key, kvOptions);\n emitOp('get', key, start, Date.now() - start, { rowsReturned: result !== null ? 1 : 0 });\n return result;\n } catch (err) {\n emitOp('get', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async getWithMetadata<M = unknown>(key: string, kvOptions?: unknown): Promise<{ value: string | null; metadata: M | null }> {\n const start = Date.now();\n try {\n const result = await kv.getWithMetadata<M>(key, kvOptions);\n emitOp('getWithMetadata', key, start, Date.now() - start, { rowsReturned: result.value !== null ? 1 : 0 });\n return result;\n } catch (err) {\n emitOp('getWithMetadata', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async put(key: string, value: string | ReadableStream | ArrayBuffer, kvOptions?: unknown): Promise<void> {\n const start = Date.now();\n try {\n await kv.put(key, value, kvOptions);\n emitOp('put', key, start, Date.now() - start, { rowsAffected: 1 });\n } catch (err) {\n emitOp('put', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async delete(key: string): Promise<void> {\n const start = Date.now();\n try {\n await kv.delete(key);\n emitOp('delete', key, start, Date.now() - start, { rowsAffected: 1 });\n } catch (err) {\n emitOp('delete', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async list(kvOptions?: unknown): Promise<{ keys: { name: string }[]; list_complete: boolean; cursor?: string }> {\n const start = Date.now();\n try {\n const result = await kv.list(kvOptions);\n emitOp('list', '*', start, Date.now() - start, { rowsReturned: result.keys.length });\n return result;\n } catch (err) {\n emitOp('list', '*', start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n };\n}\n","import type { R2BucketBinding, R2Object, R2ObjectBody, R2Objects, DatabaseEvent } from '../types.js';\nimport { generateId } from '../utils.js';\n\n// ============================================================\n// R2 Bucket Wrapper\n// Wraps an R2Bucket to capture get/put/delete/list/head operations.\n// Events emitted as 'database' type with source: 'r2'.\n// ============================================================\n\ntype EmitFn = (event: DatabaseEvent) => void;\n\ninterface R2InstrumentOptions {\n sessionId: string;\n}\n\n/**\n * Wrap an R2 bucket binding to capture operations.\n *\n * @example\n * ```ts\n * const bucket = instrumentR2(env.MY_BUCKET, emit, { sessionId });\n * await bucket.put('file.txt', 'contents');\n * ```\n */\nexport function instrumentR2(\n bucket: R2BucketBinding,\n emit: EmitFn,\n options: R2InstrumentOptions,\n): R2BucketBinding {\n function sanitizeKey(key: string): string {\n const safe = key.length > 200 ? key.slice(0, 200) + '…' : key;\n return safe.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n');\n }\n\n function emitOp(op: string, key: string, start: number, duration: number, extra: Partial<DatabaseEvent> = {}): void {\n emit({\n eventId: generateId(),\n sessionId: options.sessionId,\n timestamp: start,\n eventType: 'database',\n query: `R2.${op}(\"${sanitizeKey(key)}\")`,\n normalizedQuery: `R2.${op}(?)`,\n duration,\n tablesAccessed: [],\n operation: op === 'get' || op === 'list' || op === 'head' ? 'SELECT' : op === 'put' ? 'INSERT' : op === 'delete' ? 'DELETE' : 'OTHER',\n source: 'r2',\n ...extra,\n });\n }\n\n return {\n async get(key: string, r2Options?: unknown): Promise<R2ObjectBody | null> {\n const start = Date.now();\n try {\n const result = await bucket.get(key, r2Options);\n emitOp('get', key, start, Date.now() - start, {\n rowsReturned: result !== null ? 1 : 0,\n label: result ? `${result.size} bytes` : undefined,\n });\n return result;\n } catch (err) {\n emitOp('get', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async put(\n key: string,\n value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob,\n r2Options?: unknown,\n ): Promise<R2Object | null> {\n const start = Date.now();\n try {\n const result = await bucket.put(key, value, r2Options);\n emitOp('put', key, start, Date.now() - start, {\n rowsAffected: 1,\n label: result ? `${result.size} bytes` : undefined,\n });\n return result;\n } catch (err) {\n emitOp('put', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async delete(keys: string | string[]): Promise<void> {\n const keyList = Array.isArray(keys) ? keys : [keys];\n const keyLabel = keyList.length === 1 ? keyList[0] : `${keyList.length} keys`;\n const start = Date.now();\n try {\n await bucket.delete(keys);\n emitOp('delete', keyLabel, start, Date.now() - start, { rowsAffected: keyList.length });\n } catch (err) {\n emitOp('delete', keyLabel, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async list(r2Options?: unknown): Promise<R2Objects> {\n const start = Date.now();\n try {\n const result = await bucket.list(r2Options);\n emitOp('list', '*', start, Date.now() - start, { rowsReturned: result.objects.length });\n return result;\n } catch (err) {\n emitOp('list', '*', start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n\n async head(key: string): Promise<R2Object | null> {\n const start = Date.now();\n try {\n const result = await bucket.head(key);\n emitOp('head', key, start, Date.now() - start, {\n rowsReturned: result !== null ? 1 : 0,\n label: result ? `${result.size} bytes` : undefined,\n });\n return result;\n } catch (err) {\n emitOp('head', key, start, Date.now() - start, { error: (err as Error).message });\n throw err;\n }\n },\n };\n}\n","// ============================================================\n// @runtimescope/workers-sdk\n// Zero-dependency SDK for Cloudflare Workers.\n// Captures requests, D1 queries, KV/R2 ops, console, errors.\n// ============================================================\n\nexport { withRuntimeScope } from './handler.js';\nexport { getActiveContext } from './handler.js';\nexport type { WorkersFetchHandler, RuntimeScopeContext } from './handler.js';\n\nexport { instrumentD1 } from './bindings/d1.js';\nexport { instrumentKV } from './bindings/kv.js';\nexport { instrumentR2 } from './bindings/r2.js';\n\nexport { WorkersTransport } from './transport.js';\nexport { Sampler } from './sampler.js';\nexport { generateId, generateSessionId } from './utils.js';\n\nexport type {\n WorkersConfig,\n WorkersRuntimeEvent,\n ConsoleEvent,\n DatabaseEvent,\n NetworkEvent,\n CustomEvent,\n UIInteractionEvent,\n UserContext,\n ConsoleLevel,\n DatabaseOperation,\n DatabaseSource,\n D1DatabaseBinding,\n D1PreparedStatementBinding,\n D1Result,\n KVNamespaceBinding,\n R2BucketBinding,\n R2Object,\n R2ObjectBody,\n R2Objects,\n} from './types.js';\n\n// ============================================================\n// Convenience: instrument bindings using the active request context\n// These are shortcuts that automatically use the current request's\n// transport and session ID — no manual wiring needed.\n// ============================================================\n\nimport { getActiveContext } from './handler.js';\nimport { instrumentD1 as _instrumentD1 } from './bindings/d1.js';\nimport { instrumentKV as _instrumentKV } from './bindings/kv.js';\nimport { instrumentR2 as _instrumentR2 } from './bindings/r2.js';\nimport { generateId } from './utils.js';\nimport type {\n D1DatabaseBinding,\n KVNamespaceBinding,\n R2BucketBinding,\n CustomEvent,\n UIInteractionEvent,\n} from './types.js';\n\n/**\n * Instrument a D1 database using the active request context.\n * Must be called inside a withRuntimeScope handler.\n *\n * @example\n * ```ts\n * import { withRuntimeScope, scopeD1 } from '@runtimescope/workers-sdk';\n *\n * export default withRuntimeScope({\n * async fetch(request, env, ctx) {\n * const db = scopeD1(env.DB);\n * const users = await db.prepare('SELECT * FROM users').all();\n * return Response.json(users);\n * },\n * }, { appName: 'my-worker' });\n * ```\n */\nexport function scopeD1(db: D1DatabaseBinding): D1DatabaseBinding {\n const ctx = getActiveContext();\n if (!ctx) return db; // No active context — return unwrapped (safe for non-instrumented calls)\n return _instrumentD1(db, ctx.emit, { sessionId: ctx.sessionId });\n}\n\n/** Instrument a KV namespace using the active request context. */\nexport function scopeKV(kv: KVNamespaceBinding): KVNamespaceBinding {\n const ctx = getActiveContext();\n if (!ctx) return kv;\n return _instrumentKV(kv, ctx.emit, { sessionId: ctx.sessionId });\n}\n\n/** Instrument an R2 bucket using the active request context. */\nexport function scopeR2(bucket: R2BucketBinding): R2BucketBinding {\n const ctx = getActiveContext();\n if (!ctx) return bucket;\n return _instrumentR2(bucket, ctx.emit, { sessionId: ctx.sessionId });\n}\n\n// ============================================================\n// Custom event tracking & breadcrumbs\n// Must be called inside a withRuntimeScope handler.\n// ============================================================\n\n/**\n * Track a custom business event (e.g., user signup, payment processed).\n * Must be called inside a withRuntimeScope handler.\n *\n * @example\n * ```ts\n * import { withRuntimeScope, track } from '@runtimescope/workers-sdk';\n *\n * export default withRuntimeScope({\n * async fetch(request, env, ctx) {\n * track('payment.processed', { amount: 99.99, currency: 'USD' });\n * return new Response('OK');\n * },\n * }, { appName: 'payments-worker' });\n * ```\n */\nexport function track(name: string, properties?: Record<string, unknown>): void {\n const ctx = getActiveContext();\n if (!ctx) return;\n const event: CustomEvent = {\n eventId: generateId(),\n sessionId: ctx.sessionId,\n timestamp: Date.now(),\n eventType: 'custom',\n name,\n ...(properties && { properties }),\n };\n ctx.emit(event);\n}\n\n/**\n * Add a breadcrumb to the current request's event trail.\n * Useful for marking key points in request processing.\n *\n * @example\n * ```ts\n * import { withRuntimeScope, addBreadcrumb } from '@runtimescope/workers-sdk';\n *\n * export default withRuntimeScope({\n * async fetch(request, env, ctx) {\n * addBreadcrumb('auth check passed', { userId: '123' });\n * // ... process request\n * addBreadcrumb('cache miss, fetching from origin');\n * return new Response('OK');\n * },\n * }, { appName: 'api-worker' });\n * ```\n */\nexport function addBreadcrumb(message: string, data?: Record<string, unknown>): void {\n const ctx = getActiveContext();\n if (!ctx) return;\n const event: UIInteractionEvent = {\n eventId: generateId(),\n sessionId: ctx.sessionId,\n timestamp: Date.now(),\n eventType: 'ui',\n action: 'breadcrumb',\n target: 'manual',\n text: message,\n ...(data && { data }),\n };\n ctx.emit(event);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@runtimescope/workers-sdk",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "RuntimeScope SDK for Cloudflare Workers — instruments requests, D1, KV, R2, console, and errors",
|
|
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
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"runtimescope",
|
|
19
|
+
"cloudflare",
|
|
20
|
+
"workers",
|
|
21
|
+
"observability",
|
|
22
|
+
"telemetry",
|
|
23
|
+
"d1",
|
|
24
|
+
"kv",
|
|
25
|
+
"r2",
|
|
26
|
+
"edge"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"dev": "tsup --watch",
|
|
32
|
+
"clean": "rm -rf dist"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@cloudflare/workers-types": "^4.20240512.0",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.4.0"
|
|
38
|
+
}
|
|
39
|
+
}
|