@littlebearapps/platform-consumer-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +306 -0
- package/package.json +53 -0
- package/src/ai-gateway.ts +305 -0
- package/src/constants.ts +147 -0
- package/src/costs.ts +590 -0
- package/src/do-heartbeat.ts +249 -0
- package/src/dynamic-patterns.ts +273 -0
- package/src/errors.ts +285 -0
- package/src/features.ts +149 -0
- package/src/heartbeat.ts +27 -0
- package/src/index.ts +950 -0
- package/src/logging.ts +543 -0
- package/src/middleware.ts +447 -0
- package/src/patterns.ts +156 -0
- package/src/proxy.ts +732 -0
- package/src/retry.ts +19 -0
- package/src/service-client.ts +291 -0
- package/src/telemetry.ts +342 -0
- package/src/timeout.ts +212 -0
- package/src/tracing.ts +403 -0
- package/src/types.ts +465 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Platform SDK Types
|
|
5
|
+
*
|
|
6
|
+
* Shared type definitions for the Platform SDK.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// FEATURE IDENTIFICATION
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Feature identifier string in format 'project:category:feature'
|
|
15
|
+
* Example: 'scout:ocr:process', 'brand-copilot:scanner:github'
|
|
16
|
+
*/
|
|
17
|
+
export type FeatureId = string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parsed feature configuration from a FeatureId.
|
|
21
|
+
*/
|
|
22
|
+
export interface FeatureConfig {
|
|
23
|
+
project: string;
|
|
24
|
+
category: string;
|
|
25
|
+
feature: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// METRICS
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resource metrics tracked per feature invocation.
|
|
34
|
+
* All fields are optional - only tracked resources are included.
|
|
35
|
+
*/
|
|
36
|
+
export interface FeatureMetrics {
|
|
37
|
+
// D1 Database
|
|
38
|
+
d1Writes?: number;
|
|
39
|
+
d1Reads?: number;
|
|
40
|
+
d1RowsRead?: number;
|
|
41
|
+
d1RowsWritten?: number;
|
|
42
|
+
|
|
43
|
+
// KV Namespace
|
|
44
|
+
kvReads?: number;
|
|
45
|
+
kvWrites?: number;
|
|
46
|
+
kvDeletes?: number;
|
|
47
|
+
kvLists?: number;
|
|
48
|
+
|
|
49
|
+
// Workers AI
|
|
50
|
+
aiRequests?: number;
|
|
51
|
+
aiNeurons?: number;
|
|
52
|
+
aiModelBreakdown?: Record<string, number>; // model name → invocation count
|
|
53
|
+
|
|
54
|
+
// Vectorize (vectorizeDeletes removed - Analytics Engine 20 double limit)
|
|
55
|
+
vectorizeQueries?: number;
|
|
56
|
+
vectorizeInserts?: number;
|
|
57
|
+
|
|
58
|
+
// Durable Objects
|
|
59
|
+
doRequests?: number;
|
|
60
|
+
doGbSeconds?: number;
|
|
61
|
+
doAvgLatencyMs?: number;
|
|
62
|
+
doMaxLatencyMs?: number;
|
|
63
|
+
doP99LatencyMs?: number;
|
|
64
|
+
|
|
65
|
+
// R2
|
|
66
|
+
r2ClassA?: number;
|
|
67
|
+
r2ClassB?: number;
|
|
68
|
+
|
|
69
|
+
// Queue
|
|
70
|
+
queueMessages?: number;
|
|
71
|
+
|
|
72
|
+
// Workflow
|
|
73
|
+
workflowInvocations?: number;
|
|
74
|
+
|
|
75
|
+
// General
|
|
76
|
+
requests?: number;
|
|
77
|
+
cpuMs?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Error category for classification in telemetry.
|
|
82
|
+
* Used for alerting priority and deduplication.
|
|
83
|
+
*/
|
|
84
|
+
export type ErrorCategory =
|
|
85
|
+
| 'VALIDATION'
|
|
86
|
+
| 'NETWORK'
|
|
87
|
+
| 'CIRCUIT_BREAKER'
|
|
88
|
+
| 'INTERNAL'
|
|
89
|
+
| 'AUTH'
|
|
90
|
+
| 'RATE_LIMIT'
|
|
91
|
+
| 'D1_ERROR'
|
|
92
|
+
| 'KV_ERROR'
|
|
93
|
+
| 'QUEUE_ERROR'
|
|
94
|
+
| 'EXTERNAL_API'
|
|
95
|
+
| 'TIMEOUT';
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Internal metrics accumulator used during request processing.
|
|
99
|
+
* Mutable version of FeatureMetrics for tracking.
|
|
100
|
+
*/
|
|
101
|
+
export interface MetricsAccumulator {
|
|
102
|
+
d1Writes: number;
|
|
103
|
+
d1Reads: number;
|
|
104
|
+
d1RowsRead: number;
|
|
105
|
+
d1RowsWritten: number;
|
|
106
|
+
kvReads: number;
|
|
107
|
+
kvWrites: number;
|
|
108
|
+
kvDeletes: number;
|
|
109
|
+
kvLists: number;
|
|
110
|
+
aiRequests: number;
|
|
111
|
+
aiNeurons: number;
|
|
112
|
+
aiModelCounts: Map<string, number>; // mutable map for model name accumulation
|
|
113
|
+
vectorizeQueries: number;
|
|
114
|
+
vectorizeInserts: number;
|
|
115
|
+
// R2
|
|
116
|
+
r2ClassA: number;
|
|
117
|
+
r2ClassB: number;
|
|
118
|
+
// Queue
|
|
119
|
+
queueMessages: number;
|
|
120
|
+
// Durable Objects
|
|
121
|
+
doRequests: number;
|
|
122
|
+
doLatencyMs: number[]; // Array for percentile calculation
|
|
123
|
+
doTotalLatencyMs: number; // Sum for average
|
|
124
|
+
// Workflow
|
|
125
|
+
workflowInvocations: number;
|
|
126
|
+
// Error tracking
|
|
127
|
+
errorCount: number;
|
|
128
|
+
lastErrorCategory: ErrorCategory | null;
|
|
129
|
+
errorCodes: string[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a fresh metrics accumulator with all values at zero.
|
|
134
|
+
*/
|
|
135
|
+
export function createMetricsAccumulator(): MetricsAccumulator {
|
|
136
|
+
return {
|
|
137
|
+
d1Writes: 0,
|
|
138
|
+
d1Reads: 0,
|
|
139
|
+
d1RowsRead: 0,
|
|
140
|
+
d1RowsWritten: 0,
|
|
141
|
+
kvReads: 0,
|
|
142
|
+
kvWrites: 0,
|
|
143
|
+
kvDeletes: 0,
|
|
144
|
+
kvLists: 0,
|
|
145
|
+
aiRequests: 0,
|
|
146
|
+
aiNeurons: 0,
|
|
147
|
+
aiModelCounts: new Map<string, number>(),
|
|
148
|
+
vectorizeQueries: 0,
|
|
149
|
+
vectorizeInserts: 0,
|
|
150
|
+
// R2
|
|
151
|
+
r2ClassA: 0,
|
|
152
|
+
r2ClassB: 0,
|
|
153
|
+
// Queue
|
|
154
|
+
queueMessages: 0,
|
|
155
|
+
// Durable Objects
|
|
156
|
+
doRequests: 0,
|
|
157
|
+
doLatencyMs: [],
|
|
158
|
+
doTotalLatencyMs: 0,
|
|
159
|
+
// Workflow
|
|
160
|
+
workflowInvocations: 0,
|
|
161
|
+
// Error tracking
|
|
162
|
+
errorCount: 0,
|
|
163
|
+
lastErrorCategory: null,
|
|
164
|
+
errorCodes: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// TELEMETRY
|
|
170
|
+
// =============================================================================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Queue message format for telemetry.
|
|
174
|
+
* Sent to PLATFORM_TELEMETRY queue for processing by consumer.
|
|
175
|
+
*/
|
|
176
|
+
export interface TelemetryMessage {
|
|
177
|
+
/** Feature key in format 'project:category:feature' */
|
|
178
|
+
feature_key: string;
|
|
179
|
+
/** Project name */
|
|
180
|
+
project: string;
|
|
181
|
+
/** Category within project */
|
|
182
|
+
category: string;
|
|
183
|
+
/** Specific feature */
|
|
184
|
+
feature: string;
|
|
185
|
+
/** Usage metrics for this invocation */
|
|
186
|
+
metrics: FeatureMetrics;
|
|
187
|
+
/** Unix timestamp in milliseconds */
|
|
188
|
+
timestamp: number;
|
|
189
|
+
/** True for health check probes (skips budget check, records in D1) */
|
|
190
|
+
is_heartbeat?: boolean;
|
|
191
|
+
/** Error count for this invocation */
|
|
192
|
+
error_count?: number;
|
|
193
|
+
/** Most recent error category */
|
|
194
|
+
error_category?: ErrorCategory;
|
|
195
|
+
/** Error codes encountered */
|
|
196
|
+
error_codes?: string[];
|
|
197
|
+
/** Correlation ID for request tracing */
|
|
198
|
+
correlation_id?: string;
|
|
199
|
+
/** Request wall-clock duration in milliseconds */
|
|
200
|
+
request_duration_ms?: number;
|
|
201
|
+
/** W3C Trace ID for distributed tracing */
|
|
202
|
+
trace_id?: string;
|
|
203
|
+
/** W3C Span ID for distributed tracing */
|
|
204
|
+
span_id?: string;
|
|
205
|
+
/** External API cost in USD (e.g., OpenAI, Apify) */
|
|
206
|
+
external_cost_usd?: number;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// CIRCUIT BREAKER
|
|
211
|
+
// =============================================================================
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Circuit breaker status values.
|
|
215
|
+
*/
|
|
216
|
+
export type CircuitStatus = 'GO' | 'STOP';
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Circuit breaker check result.
|
|
220
|
+
*/
|
|
221
|
+
export interface CircuitBreakerResult {
|
|
222
|
+
status: CircuitStatus;
|
|
223
|
+
reason?: string;
|
|
224
|
+
level: 'feature' | 'project' | 'global';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Error thrown when a circuit breaker is open (STOP).
|
|
229
|
+
* Workers should catch this and return 503 Service Unavailable.
|
|
230
|
+
*/
|
|
231
|
+
export class CircuitBreakerError extends Error {
|
|
232
|
+
public readonly featureId: FeatureId;
|
|
233
|
+
public readonly level: 'feature' | 'project' | 'global';
|
|
234
|
+
public readonly reason?: string;
|
|
235
|
+
|
|
236
|
+
constructor(featureId: FeatureId, level: 'feature' | 'project' | 'global', reason?: string) {
|
|
237
|
+
const message = reason
|
|
238
|
+
? `Circuit breaker STOP for ${featureId} at ${level} level: ${reason}`
|
|
239
|
+
: `Circuit breaker STOP for ${featureId} at ${level} level`;
|
|
240
|
+
super(message);
|
|
241
|
+
this.name = 'CircuitBreakerError';
|
|
242
|
+
this.featureId = featureId;
|
|
243
|
+
this.level = level;
|
|
244
|
+
this.reason = reason;
|
|
245
|
+
|
|
246
|
+
// Maintain proper stack trace in V8 (optional - not available in all environments)
|
|
247
|
+
const ErrorWithCapture = Error as typeof Error & {
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
249
|
+
captureStackTrace?: (target: object, constructor?: Function) => void;
|
|
250
|
+
};
|
|
251
|
+
if (ErrorWithCapture.captureStackTrace) {
|
|
252
|
+
ErrorWithCapture.captureStackTrace(this, CircuitBreakerError);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// =============================================================================
|
|
258
|
+
// SDK OPTIONS
|
|
259
|
+
// =============================================================================
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Options for withFeatureBudget wrapper.
|
|
263
|
+
*/
|
|
264
|
+
export interface SDKOptions {
|
|
265
|
+
/**
|
|
266
|
+
* ExecutionContext for waitUntil support.
|
|
267
|
+
* Required for proper telemetry flushing.
|
|
268
|
+
*/
|
|
269
|
+
ctx?: ExecutionContext;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Whether to check circuit breaker before processing.
|
|
273
|
+
* Default: true
|
|
274
|
+
*/
|
|
275
|
+
checkCircuitBreaker?: boolean;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Whether to report telemetry after processing.
|
|
279
|
+
* Default: true
|
|
280
|
+
*/
|
|
281
|
+
reportTelemetry?: boolean;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Custom KV namespace for circuit breaker state.
|
|
285
|
+
* Default: env.PLATFORM_CACHE
|
|
286
|
+
*/
|
|
287
|
+
cacheKv?: KVNamespace;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Custom queue for telemetry.
|
|
291
|
+
* Default: env.PLATFORM_TELEMETRY
|
|
292
|
+
*/
|
|
293
|
+
telemetryQueue?: Queue<TelemetryMessage>;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Correlation ID for request tracing.
|
|
297
|
+
* If not provided, one will be generated automatically.
|
|
298
|
+
*/
|
|
299
|
+
correlationId?: string;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* External API cost in USD (e.g., OpenAI, Apify).
|
|
303
|
+
* Added to auto-calculated CF resource costs.
|
|
304
|
+
* @default 0
|
|
305
|
+
*/
|
|
306
|
+
externalCostUsd?: number;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// =============================================================================
|
|
310
|
+
// CRON/QUEUE HELPER OPTIONS
|
|
311
|
+
// =============================================================================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Options for withCronBudget wrapper.
|
|
315
|
+
* Extends SDKOptions with cron-specific fields.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* withCronBudget(env, 'platform:cron:cleanup', {
|
|
320
|
+
* ctx,
|
|
321
|
+
* cronExpression: '0 0 * * *',
|
|
322
|
+
* });
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
export interface CronBudgetOptions {
|
|
326
|
+
/**
|
|
327
|
+
* ExecutionContext - REQUIRED for cron handlers.
|
|
328
|
+
* Used for waitUntil support and proper telemetry flushing.
|
|
329
|
+
*/
|
|
330
|
+
ctx: ExecutionContext;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Cron expression for this scheduled task.
|
|
334
|
+
* Used to generate deterministic correlation IDs for tracing.
|
|
335
|
+
* @example '0 0 * * *' (daily at midnight)
|
|
336
|
+
* @example '0 * * * *' (hourly)
|
|
337
|
+
*/
|
|
338
|
+
cronExpression?: string;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* External API cost in USD (e.g., OpenAI, Apify).
|
|
342
|
+
* Added to auto-calculated CF resource costs.
|
|
343
|
+
* @default 0
|
|
344
|
+
*/
|
|
345
|
+
externalCostUsd?: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Options for withQueueBudget wrapper.
|
|
350
|
+
* Extends SDKOptions with queue-specific fields.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* withQueueBudget(env, 'platform:queue:process', {
|
|
355
|
+
* message: message.body,
|
|
356
|
+
* batchSize: batch.messages.length,
|
|
357
|
+
* });
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
export interface QueueBudgetOptions<M = unknown> {
|
|
361
|
+
/**
|
|
362
|
+
* Queue message body for correlation ID extraction.
|
|
363
|
+
* If the message has a `correlation_id` field, it will be propagated.
|
|
364
|
+
*/
|
|
365
|
+
message?: M;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Number of messages in the current batch.
|
|
369
|
+
* Logged for observability but not used for billing.
|
|
370
|
+
*/
|
|
371
|
+
batchSize?: number;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Queue name for correlation ID generation.
|
|
375
|
+
* @example 'platform-telemetry'
|
|
376
|
+
*/
|
|
377
|
+
queueName?: string;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* External API cost in USD (e.g., OpenAI, Apify).
|
|
381
|
+
* Added to auto-calculated CF resource costs.
|
|
382
|
+
* @default 0
|
|
383
|
+
*/
|
|
384
|
+
externalCostUsd?: number;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// =============================================================================
|
|
388
|
+
// ENVIRONMENT BINDINGS
|
|
389
|
+
// =============================================================================
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Required environment bindings for Platform SDK.
|
|
393
|
+
*/
|
|
394
|
+
export interface PlatformBindings {
|
|
395
|
+
/** KV namespace for circuit breaker state and config */
|
|
396
|
+
PLATFORM_CACHE: KVNamespace;
|
|
397
|
+
/** Queue for telemetry messages */
|
|
398
|
+
PLATFORM_TELEMETRY: Queue<TelemetryMessage>;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Type helper for environments that include Platform bindings.
|
|
403
|
+
*/
|
|
404
|
+
export type WithPlatformBindings<T> = T & PlatformBindings;
|
|
405
|
+
|
|
406
|
+
// =============================================================================
|
|
407
|
+
// HEALTH CHECK
|
|
408
|
+
// =============================================================================
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Result of a control plane health check (KV connectivity).
|
|
412
|
+
*/
|
|
413
|
+
export interface ControlPlaneHealth {
|
|
414
|
+
/** Whether KV is accessible */
|
|
415
|
+
healthy: boolean;
|
|
416
|
+
/** Circuit breaker status if KV is accessible */
|
|
417
|
+
status: CircuitStatus | 'UNKNOWN';
|
|
418
|
+
/** Error message if unhealthy */
|
|
419
|
+
error?: string;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Result of a data plane health check (queue delivery).
|
|
424
|
+
*/
|
|
425
|
+
export interface DataPlaneHealth {
|
|
426
|
+
/** Whether the queue accepted the heartbeat message */
|
|
427
|
+
healthy: boolean;
|
|
428
|
+
/** True if the queue send succeeded */
|
|
429
|
+
queueSent: boolean;
|
|
430
|
+
/** Error message if unhealthy */
|
|
431
|
+
error?: string;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Combined health check result for a feature.
|
|
436
|
+
*/
|
|
437
|
+
export interface HealthResult {
|
|
438
|
+
/** Overall health (both planes must be healthy) */
|
|
439
|
+
healthy: boolean;
|
|
440
|
+
/** Control plane status (KV connectivity) */
|
|
441
|
+
controlPlane: ControlPlaneHealth;
|
|
442
|
+
/** Data plane status (queue delivery) */
|
|
443
|
+
dataPlane: DataPlaneHealth;
|
|
444
|
+
/** Project name from featureId */
|
|
445
|
+
project: string;
|
|
446
|
+
/** Feature key in format 'project:category:feature' */
|
|
447
|
+
feature: string;
|
|
448
|
+
/** Unix timestamp when check was performed */
|
|
449
|
+
timestamp: number;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Extended environment type returned by withFeatureBudget().
|
|
454
|
+
* Includes health() method for dual-plane connectivity checks
|
|
455
|
+
* and fetch() for tracked HTTP calls (auto-detects AI Gateway URLs).
|
|
456
|
+
*/
|
|
457
|
+
export type TrackedEnv<T> = T & {
|
|
458
|
+
health(): Promise<HealthResult>;
|
|
459
|
+
/**
|
|
460
|
+
* Tracked fetch that auto-detects AI Gateway URLs.
|
|
461
|
+
* AI Gateway calls are automatically tracked with provider/model extraction.
|
|
462
|
+
* Non-AI Gateway calls pass through unchanged.
|
|
463
|
+
*/
|
|
464
|
+
fetch: typeof fetch;
|
|
465
|
+
};
|