@littlebearapps/platform-admin-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 +112 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +89 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +80 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.js +65 -0
- package/dist/templates.d.ts +16 -0
- package/dist/templates.js +131 -0
- package/package.json +46 -0
- package/templates/full/migrations/006_pattern_discovery.sql +199 -0
- package/templates/full/migrations/007_notifications_search.sql +127 -0
- package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
- package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
- package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
- package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
- package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
- package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
- package/templates/full/workers/pattern-discovery.ts +661 -0
- package/templates/full/workers/platform-alert-router.ts +1809 -0
- package/templates/full/workers/platform-notifications.ts +424 -0
- package/templates/full/workers/platform-search.ts +480 -0
- package/templates/full/workers/platform-settings.ts +436 -0
- package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
- package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
- package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
- package/templates/full/wrangler.search.jsonc.hbs +16 -0
- package/templates/full/wrangler.settings.jsonc.hbs +23 -0
- package/templates/shared/README.md.hbs +69 -0
- package/templates/shared/config/budgets.yaml.hbs +72 -0
- package/templates/shared/config/services.yaml.hbs +45 -0
- package/templates/shared/migrations/001_core_tables.sql +117 -0
- package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
- package/templates/shared/migrations/003_feature_tracking.sql +250 -0
- package/templates/shared/migrations/004_settings_alerts.sql +452 -0
- package/templates/shared/migrations/seed.sql.hbs +4 -0
- package/templates/shared/package.json.hbs +21 -0
- package/templates/shared/scripts/sync-config.ts +242 -0
- package/templates/shared/tsconfig.json +12 -0
- package/templates/shared/workers/lib/analytics-engine.ts +357 -0
- package/templates/shared/workers/lib/billing.ts +293 -0
- package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
- package/templates/shared/workers/lib/control.ts +292 -0
- package/templates/shared/workers/lib/economics.ts +368 -0
- package/templates/shared/workers/lib/metrics.ts +103 -0
- package/templates/shared/workers/lib/platform-settings.ts +407 -0
- package/templates/shared/workers/lib/shared/allowances.ts +333 -0
- package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
- package/templates/shared/workers/lib/shared/types.ts +58 -0
- package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
- package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
- package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
- package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
- package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
- package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
- package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
- package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
- package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
- package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
- package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
- package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
- package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
- package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
- package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
- package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
- package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
- package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
- package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
- package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
- package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
- package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
- package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
- package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
- package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
- package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
- package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
- package/templates/shared/workers/platform-usage.ts +1915 -0
- package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
- package/templates/standard/migrations/005_error_collection.sql +162 -0
- package/templates/standard/workers/error-collector.ts +2670 -0
- package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
- package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
- package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
- package/templates/standard/workers/lib/error-collector/github.ts +329 -0
- package/templates/standard/workers/lib/error-collector/types.ts +262 -0
- package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
- package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
- package/templates/standard/workers/platform-sentinel.ts +1744 -0
- package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
- package/templates/standard/wrangler.sentinel.jsonc.hbs +45 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Settings Module
|
|
3
|
+
*
|
|
4
|
+
* Centralised configuration management for platform-wide thresholds,
|
|
5
|
+
* circuit breakers, and resource limits.
|
|
6
|
+
*
|
|
7
|
+
* Caching Strategy:
|
|
8
|
+
* 1. Check KV (PLATFORM_CACHE) first - 1 hour TTL
|
|
9
|
+
* 2. If KV miss, query D1 (usage_settings table)
|
|
10
|
+
* 3. If D1 miss, return default value
|
|
11
|
+
* 4. On D1 hit, write to KV for next request
|
|
12
|
+
*
|
|
13
|
+
* Source of Truth:
|
|
14
|
+
* - Git: platform/config/budgets.yaml
|
|
15
|
+
* - Synced to D1 via scripts/sync-config.ts
|
|
16
|
+
* - Cached in KV for runtime performance
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { KVNamespace, D1Database } from '@cloudflare/workers-types';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// TYPES
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* All platform settings from D1 usage_settings table.
|
|
27
|
+
* Centralised configuration for thresholds, circuit breakers, and sampling.
|
|
28
|
+
*/
|
|
29
|
+
export interface PlatformSettings {
|
|
30
|
+
// Budget thresholds (USD)
|
|
31
|
+
budgetSoftLimit: number;
|
|
32
|
+
budgetWarningThreshold: number;
|
|
33
|
+
budgetCriticalThreshold: number;
|
|
34
|
+
|
|
35
|
+
// Alert/utilization thresholds (percentage)
|
|
36
|
+
alertWarningPct: number;
|
|
37
|
+
alertCriticalPct: number;
|
|
38
|
+
utilizationWarningPct: number;
|
|
39
|
+
utilizationCriticalPct: number;
|
|
40
|
+
|
|
41
|
+
// Sampling thresholds (D1 usage ratio)
|
|
42
|
+
samplingFullThreshold: number;
|
|
43
|
+
samplingHalfThreshold: number;
|
|
44
|
+
samplingQuarterThreshold: number;
|
|
45
|
+
|
|
46
|
+
// Circuit breaker defaults
|
|
47
|
+
cbAutoResetSeconds: number;
|
|
48
|
+
cbCooldownSeconds: number;
|
|
49
|
+
cbMaxConsecutiveTrips: number;
|
|
50
|
+
|
|
51
|
+
// Error rate settings
|
|
52
|
+
errorRateThreshold: number;
|
|
53
|
+
errorRateWindowMinutes: number;
|
|
54
|
+
errorRateMinRequests: number;
|
|
55
|
+
|
|
56
|
+
// Resource limits (daily)
|
|
57
|
+
d1WriteLimit: number;
|
|
58
|
+
doGbSecondsDailyLimit: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Environment bindings required for settings functions.
|
|
63
|
+
*/
|
|
64
|
+
export interface SettingsEnv {
|
|
65
|
+
PLATFORM_CACHE: KVNamespace;
|
|
66
|
+
PLATFORM_DB: D1Database;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// CONSTANTS
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Default platform settings (fallback if D1/KV query fails).
|
|
75
|
+
* These MUST match the values in budgets.yaml defaults section.
|
|
76
|
+
*/
|
|
77
|
+
export const DEFAULT_PLATFORM_SETTINGS: PlatformSettings = {
|
|
78
|
+
// Budget thresholds (USD)
|
|
79
|
+
budgetSoftLimit: 25,
|
|
80
|
+
budgetWarningThreshold: 20,
|
|
81
|
+
budgetCriticalThreshold: 50,
|
|
82
|
+
|
|
83
|
+
// Alert/utilization thresholds (percentage) - from budgets.yaml defaults.thresholds
|
|
84
|
+
alertWarningPct: 70,
|
|
85
|
+
alertCriticalPct: 90,
|
|
86
|
+
utilizationWarningPct: 70,
|
|
87
|
+
utilizationCriticalPct: 90,
|
|
88
|
+
|
|
89
|
+
// Sampling thresholds (D1 usage ratio)
|
|
90
|
+
samplingFullThreshold: 0.6,
|
|
91
|
+
samplingHalfThreshold: 0.8,
|
|
92
|
+
samplingQuarterThreshold: 0.9,
|
|
93
|
+
|
|
94
|
+
// Circuit breaker defaults - from budgets.yaml defaults.circuit_breaker
|
|
95
|
+
cbAutoResetSeconds: 3600,
|
|
96
|
+
cbCooldownSeconds: 300,
|
|
97
|
+
cbMaxConsecutiveTrips: 3,
|
|
98
|
+
|
|
99
|
+
// Error rate settings - from budgets.yaml defaults.error_budget
|
|
100
|
+
errorRateThreshold: 0.1,
|
|
101
|
+
errorRateWindowMinutes: 15,
|
|
102
|
+
errorRateMinRequests: 50,
|
|
103
|
+
|
|
104
|
+
// Resource limits (daily)
|
|
105
|
+
d1WriteLimit: 1_000_000, // 1M writes per 24h (adaptive sampling trigger)
|
|
106
|
+
doGbSecondsDailyLimit: 200_000, // 200K GB-seconds per 24h (~$2.50/day)
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Mapping from D1 setting_key to PlatformSettings property.
|
|
111
|
+
*/
|
|
112
|
+
export const SETTING_KEY_MAP: Record<string, keyof PlatformSettings> = {
|
|
113
|
+
budget_soft_limit: 'budgetSoftLimit',
|
|
114
|
+
budget_warning_threshold: 'budgetWarningThreshold',
|
|
115
|
+
budget_critical_threshold: 'budgetCriticalThreshold',
|
|
116
|
+
alert_warning_pct: 'alertWarningPct',
|
|
117
|
+
alert_critical_pct: 'alertCriticalPct',
|
|
118
|
+
utilization_warning_pct: 'utilizationWarningPct',
|
|
119
|
+
utilization_critical_pct: 'utilizationCriticalPct',
|
|
120
|
+
sampling_full_threshold: 'samplingFullThreshold',
|
|
121
|
+
sampling_half_threshold: 'samplingHalfThreshold',
|
|
122
|
+
sampling_quarter_threshold: 'samplingQuarterThreshold',
|
|
123
|
+
cb_auto_reset_seconds: 'cbAutoResetSeconds',
|
|
124
|
+
cb_cooldown_seconds: 'cbCooldownSeconds',
|
|
125
|
+
cb_max_consecutive_trips: 'cbMaxConsecutiveTrips',
|
|
126
|
+
error_rate_threshold: 'errorRateThreshold',
|
|
127
|
+
error_rate_window_minutes: 'errorRateWindowMinutes',
|
|
128
|
+
error_rate_min_requests: 'errorRateMinRequests',
|
|
129
|
+
d1_write_limit: 'd1WriteLimit',
|
|
130
|
+
do_gb_seconds_daily_limit: 'doGbSecondsDailyLimit',
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Reverse mapping from PlatformSettings property to D1 setting_key.
|
|
135
|
+
*/
|
|
136
|
+
export const PROPERTY_TO_KEY_MAP: Record<keyof PlatformSettings, string> = Object.fromEntries(
|
|
137
|
+
Object.entries(SETTING_KEY_MAP).map(([k, v]) => [v, k])
|
|
138
|
+
) as Record<keyof PlatformSettings, string>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* KV key prefix for settings cache.
|
|
142
|
+
*/
|
|
143
|
+
const KV_SETTINGS_PREFIX = 'CONFIG:SETTINGS:';
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* KV key for the full settings object cache.
|
|
147
|
+
*/
|
|
148
|
+
const KV_ALL_SETTINGS_KEY = 'CONFIG:SETTINGS:ALL';
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* KV cache TTL in seconds (1 hour).
|
|
152
|
+
*/
|
|
153
|
+
const KV_CACHE_TTL_SECONDS = 3600;
|
|
154
|
+
|
|
155
|
+
// =============================================================================
|
|
156
|
+
// SINGLE SETTING GETTER
|
|
157
|
+
// =============================================================================
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get a single platform setting with KV-first, D1-fallback caching.
|
|
161
|
+
*
|
|
162
|
+
* Lookup order:
|
|
163
|
+
* 1. KV cache (CONFIG:SETTINGS:{key})
|
|
164
|
+
* 2. D1 usage_settings table
|
|
165
|
+
* 3. Default value
|
|
166
|
+
*
|
|
167
|
+
* On D1 hit, value is cached to KV with 1-hour TTL.
|
|
168
|
+
*
|
|
169
|
+
* @param env - Environment with PLATFORM_CACHE and PLATFORM_DB
|
|
170
|
+
* @param key - Setting key (e.g., 'budget_soft_limit')
|
|
171
|
+
* @param defaultValue - Fallback if not found in KV or D1
|
|
172
|
+
* @returns The setting value, converted to the appropriate type
|
|
173
|
+
*/
|
|
174
|
+
export async function getSetting<T extends string | number | boolean>(
|
|
175
|
+
env: SettingsEnv,
|
|
176
|
+
key: string,
|
|
177
|
+
defaultValue: T
|
|
178
|
+
): Promise<T> {
|
|
179
|
+
const kvKey = `${KV_SETTINGS_PREFIX}${key}`;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// 1. Check KV cache first
|
|
183
|
+
const cached = await env.PLATFORM_CACHE.get(kvKey);
|
|
184
|
+
if (cached !== null) {
|
|
185
|
+
return parseValue(cached, defaultValue);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 2. Query D1
|
|
189
|
+
const result = await env.PLATFORM_DB.prepare(
|
|
190
|
+
`SELECT setting_value FROM usage_settings WHERE project = 'all' AND setting_key = ?`
|
|
191
|
+
)
|
|
192
|
+
.bind(key)
|
|
193
|
+
.first<{ setting_value: string }>();
|
|
194
|
+
|
|
195
|
+
if (result?.setting_value !== undefined) {
|
|
196
|
+
// 3. Cache to KV for next time
|
|
197
|
+
await env.PLATFORM_CACHE.put(kvKey, result.setting_value, {
|
|
198
|
+
expirationTtl: KV_CACHE_TTL_SECONDS,
|
|
199
|
+
});
|
|
200
|
+
return parseValue(result.setting_value, defaultValue);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 4. Return default
|
|
204
|
+
return defaultValue;
|
|
205
|
+
} catch {
|
|
206
|
+
// On error, return default silently (logging handled by caller)
|
|
207
|
+
return defaultValue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Parse a string value to the appropriate type based on the default value type.
|
|
213
|
+
*/
|
|
214
|
+
function parseValue<T extends string | number | boolean>(value: string, defaultValue: T): T {
|
|
215
|
+
if (typeof defaultValue === 'number') {
|
|
216
|
+
const parsed = parseFloat(value);
|
|
217
|
+
return (isNaN(parsed) ? defaultValue : parsed) as T;
|
|
218
|
+
}
|
|
219
|
+
if (typeof defaultValue === 'boolean') {
|
|
220
|
+
return (value === 'true' || value === '1') as unknown as T;
|
|
221
|
+
}
|
|
222
|
+
return value as T;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// FULL SETTINGS GETTER
|
|
227
|
+
// =============================================================================
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get all platform settings with KV-first, D1-fallback caching.
|
|
231
|
+
*
|
|
232
|
+
* This fetches all settings in a single query rather than N+1 queries.
|
|
233
|
+
* Use this when you need multiple settings in one operation.
|
|
234
|
+
*
|
|
235
|
+
* @param env - Environment with PLATFORM_CACHE and PLATFORM_DB
|
|
236
|
+
* @returns Full PlatformSettings object
|
|
237
|
+
*/
|
|
238
|
+
export async function getPlatformSettings(env: SettingsEnv): Promise<PlatformSettings> {
|
|
239
|
+
try {
|
|
240
|
+
// 1. Check KV cache for full settings object
|
|
241
|
+
const cached = await env.PLATFORM_CACHE.get(KV_ALL_SETTINGS_KEY);
|
|
242
|
+
// Empty string means cache was invalidated by sync script - skip to D1
|
|
243
|
+
if (cached !== null && cached !== '') {
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(cached) as Partial<PlatformSettings>;
|
|
246
|
+
// Merge with defaults to handle any missing keys
|
|
247
|
+
return { ...DEFAULT_PLATFORM_SETTINGS, ...parsed };
|
|
248
|
+
} catch {
|
|
249
|
+
// Invalid JSON, fall through to D1
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 2. Query all settings from D1
|
|
254
|
+
const result = await env.PLATFORM_DB.prepare(
|
|
255
|
+
`SELECT setting_key, setting_value FROM usage_settings WHERE project = 'all'`
|
|
256
|
+
).all<{ setting_key: string; setting_value: string }>();
|
|
257
|
+
|
|
258
|
+
const settings = { ...DEFAULT_PLATFORM_SETTINGS };
|
|
259
|
+
|
|
260
|
+
for (const row of result.results ?? []) {
|
|
261
|
+
const prop = SETTING_KEY_MAP[row.setting_key];
|
|
262
|
+
if (prop) {
|
|
263
|
+
const value = parseFloat(row.setting_value);
|
|
264
|
+
if (!isNaN(value)) {
|
|
265
|
+
(settings as Record<string, number>)[prop] = value;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 3. Cache full settings object to KV
|
|
271
|
+
await env.PLATFORM_CACHE.put(KV_ALL_SETTINGS_KEY, JSON.stringify(settings), {
|
|
272
|
+
expirationTtl: KV_CACHE_TTL_SECONDS,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return settings;
|
|
276
|
+
} catch {
|
|
277
|
+
// On error, return defaults
|
|
278
|
+
return DEFAULT_PLATFORM_SETTINGS;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// =============================================================================
|
|
283
|
+
// PROJECT-SPECIFIC SETTINGS
|
|
284
|
+
// =============================================================================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get a project-specific setting, falling back to global ('all') if not found.
|
|
288
|
+
*
|
|
289
|
+
* @param env - Environment with PLATFORM_CACHE and PLATFORM_DB
|
|
290
|
+
* @param project - Project identifier
|
|
291
|
+
* @param key - Setting key (e.g., 'do_gb_seconds_daily_limit')
|
|
292
|
+
* @param defaultValue - Fallback if not found
|
|
293
|
+
* @returns The setting value
|
|
294
|
+
*/
|
|
295
|
+
export async function getProjectSetting<T extends string | number | boolean>(
|
|
296
|
+
env: SettingsEnv,
|
|
297
|
+
project: string,
|
|
298
|
+
key: string,
|
|
299
|
+
defaultValue: T
|
|
300
|
+
): Promise<T> {
|
|
301
|
+
const kvKey = `${KV_SETTINGS_PREFIX}${project}:${key}`;
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// 1. Check KV cache for project-specific value
|
|
305
|
+
const cached = await env.PLATFORM_CACHE.get(kvKey);
|
|
306
|
+
if (cached !== null) {
|
|
307
|
+
return parseValue(cached, defaultValue);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 2. Query D1 for project-specific value
|
|
311
|
+
const projectResult = await env.PLATFORM_DB.prepare(
|
|
312
|
+
`SELECT setting_value FROM usage_settings WHERE project = ? AND setting_key = ?`
|
|
313
|
+
)
|
|
314
|
+
.bind(project, key)
|
|
315
|
+
.first<{ setting_value: string }>();
|
|
316
|
+
|
|
317
|
+
if (projectResult?.setting_value !== undefined) {
|
|
318
|
+
await env.PLATFORM_CACHE.put(kvKey, projectResult.setting_value, {
|
|
319
|
+
expirationTtl: KV_CACHE_TTL_SECONDS,
|
|
320
|
+
});
|
|
321
|
+
return parseValue(projectResult.setting_value, defaultValue);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 3. Fallback to global setting
|
|
325
|
+
return getSetting(env, key, defaultValue);
|
|
326
|
+
} catch {
|
|
327
|
+
// On error, try global setting
|
|
328
|
+
return getSetting(env, key, defaultValue);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// =============================================================================
|
|
333
|
+
// CACHE INVALIDATION
|
|
334
|
+
// =============================================================================
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Invalidate settings cache for a specific key.
|
|
338
|
+
* Call this after updating a setting in D1.
|
|
339
|
+
*/
|
|
340
|
+
export async function invalidateSettingCache(env: SettingsEnv, key: string): Promise<void> {
|
|
341
|
+
await Promise.all([
|
|
342
|
+
env.PLATFORM_CACHE.delete(`${KV_SETTINGS_PREFIX}${key}`),
|
|
343
|
+
env.PLATFORM_CACHE.delete(KV_ALL_SETTINGS_KEY),
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Invalidate all settings cache.
|
|
349
|
+
* Call this after bulk updates to usage_settings.
|
|
350
|
+
*/
|
|
351
|
+
export async function invalidateAllSettingsCache(env: SettingsEnv): Promise<void> {
|
|
352
|
+
// We can't easily list and delete all CONFIG:SETTINGS:* keys,
|
|
353
|
+
// so we rely on TTL expiration for individual keys.
|
|
354
|
+
// The full settings cache is deleted to force a refresh.
|
|
355
|
+
await env.PLATFORM_CACHE.delete(KV_ALL_SETTINGS_KEY);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// =============================================================================
|
|
359
|
+
// UTILITY FUNCTIONS
|
|
360
|
+
// =============================================================================
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Get utilization status based on percentage.
|
|
364
|
+
* Uses thresholds from settings or defaults.
|
|
365
|
+
*/
|
|
366
|
+
export function getUtilizationStatus(
|
|
367
|
+
pct: number,
|
|
368
|
+
settings?: { warningPct: number; criticalPct: number }
|
|
369
|
+
): 'green' | 'yellow' | 'red' {
|
|
370
|
+
const warning = settings?.warningPct ?? DEFAULT_PLATFORM_SETTINGS.utilizationWarningPct;
|
|
371
|
+
const critical = settings?.criticalPct ?? DEFAULT_PLATFORM_SETTINGS.utilizationCriticalPct;
|
|
372
|
+
if (pct < warning) return 'green';
|
|
373
|
+
if (pct < critical) return 'yellow';
|
|
374
|
+
return 'red';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Expected setting keys that should exist in D1.
|
|
379
|
+
* Used by /usage/settings/verify endpoint.
|
|
380
|
+
*/
|
|
381
|
+
export const EXPECTED_SETTINGS_KEYS = [
|
|
382
|
+
// Budget thresholds (USD)
|
|
383
|
+
'budget_soft_limit',
|
|
384
|
+
'budget_warning_threshold',
|
|
385
|
+
'budget_critical_threshold',
|
|
386
|
+
// Alert thresholds (percentage)
|
|
387
|
+
'alert_warning_pct',
|
|
388
|
+
'alert_critical_pct',
|
|
389
|
+
// Utilization thresholds (percentage)
|
|
390
|
+
'utilization_warning_pct',
|
|
391
|
+
'utilization_critical_pct',
|
|
392
|
+
// Sampling thresholds (D1 usage ratio)
|
|
393
|
+
'sampling_full_threshold',
|
|
394
|
+
'sampling_half_threshold',
|
|
395
|
+
'sampling_quarter_threshold',
|
|
396
|
+
// Circuit breaker defaults
|
|
397
|
+
'cb_auto_reset_seconds',
|
|
398
|
+
'cb_cooldown_seconds',
|
|
399
|
+
'cb_max_consecutive_trips',
|
|
400
|
+
// Error rate settings
|
|
401
|
+
'error_rate_threshold',
|
|
402
|
+
'error_rate_window_minutes',
|
|
403
|
+
'error_rate_min_requests',
|
|
404
|
+
// Resource limits
|
|
405
|
+
'd1_write_limit',
|
|
406
|
+
'do_gb_seconds_daily_limit',
|
|
407
|
+
];
|