@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,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Settings Worker
|
|
3
|
+
*
|
|
4
|
+
* Unified settings management with project/category/key namespacing.
|
|
5
|
+
* Provides API endpoints for reading and updating platform settings.
|
|
6
|
+
*
|
|
7
|
+
* Storage:
|
|
8
|
+
* - D1: platform_settings table
|
|
9
|
+
*
|
|
10
|
+
* @module workers/platform-settings
|
|
11
|
+
* @created 2026-02-03
|
|
12
|
+
* @task task-303.1
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
KVNamespace,
|
|
17
|
+
ExecutionContext,
|
|
18
|
+
D1Database,
|
|
19
|
+
} from '@cloudflare/workers-types';
|
|
20
|
+
import {
|
|
21
|
+
withFeatureBudget,
|
|
22
|
+
CircuitBreakerError,
|
|
23
|
+
createLoggerFromRequest,
|
|
24
|
+
} from '@littlebearapps/platform-consumer-sdk';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// TYPES
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
interface Env {
|
|
31
|
+
PLATFORM_DB: D1Database;
|
|
32
|
+
PLATFORM_CACHE: KVNamespace;
|
|
33
|
+
CLOUDFLARE_ACCOUNT_ID: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Setting {
|
|
37
|
+
id: string;
|
|
38
|
+
project: string;
|
|
39
|
+
category: string;
|
|
40
|
+
key: string;
|
|
41
|
+
value: string; // JSON-encoded
|
|
42
|
+
description: string | null;
|
|
43
|
+
updated_at: number;
|
|
44
|
+
updated_by: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface SettingGroup {
|
|
48
|
+
project: string;
|
|
49
|
+
category: string;
|
|
50
|
+
settings: Setting[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface UpdateSettingRequest {
|
|
54
|
+
value: unknown;
|
|
55
|
+
description?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface BulkUpdateRequest {
|
|
59
|
+
settings: Array<{
|
|
60
|
+
project: string;
|
|
61
|
+
category: string;
|
|
62
|
+
key: string;
|
|
63
|
+
value: unknown;
|
|
64
|
+
description?: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// CONSTANTS
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
const FEATURE_ID = 'platform:settings:api';
|
|
73
|
+
// Add your project names here, or load from D1 project_registry table
|
|
74
|
+
const VALID_PROJECTS = ['global'];
|
|
75
|
+
const VALID_CATEGORIES = ['notifications', 'thresholds', 'display', 'api', 'features'];
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// HELPERS
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
function generateId(project: string, category: string, key: string): string {
|
|
82
|
+
return `${project}:${category}:${key}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getUserEmail(request: Request): string {
|
|
86
|
+
const cfAccessEmail = request.headers.get('cf-access-authenticated-user-email');
|
|
87
|
+
return cfAccessEmail || 'anonymous';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function validateProject(project: string): boolean {
|
|
91
|
+
return VALID_PROJECTS.includes(project);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validateCategory(category: string): boolean {
|
|
95
|
+
return VALID_CATEGORIES.includes(category);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// API HANDLERS
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
async function handleListSettings(env: Env, url: URL): Promise<Response> {
|
|
103
|
+
const project = url.searchParams.get('project');
|
|
104
|
+
const category = url.searchParams.get('category');
|
|
105
|
+
|
|
106
|
+
let query = 'SELECT * FROM platform_settings WHERE 1=1';
|
|
107
|
+
const params: string[] = [];
|
|
108
|
+
|
|
109
|
+
if (project) {
|
|
110
|
+
query += ' AND project = ?';
|
|
111
|
+
params.push(project);
|
|
112
|
+
}
|
|
113
|
+
if (category) {
|
|
114
|
+
query += ' AND category = ?';
|
|
115
|
+
params.push(category);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
query += ' ORDER BY project, category, key';
|
|
119
|
+
|
|
120
|
+
const result = await env.PLATFORM_DB.prepare(query).bind(...params).all<Setting>();
|
|
121
|
+
const settings = result.results || [];
|
|
122
|
+
|
|
123
|
+
// Group by project and category
|
|
124
|
+
const grouped: Record<string, Record<string, Setting[]>> = {};
|
|
125
|
+
for (const setting of settings) {
|
|
126
|
+
if (!grouped[setting.project]) {
|
|
127
|
+
grouped[setting.project] = {};
|
|
128
|
+
}
|
|
129
|
+
if (!grouped[setting.project][setting.category]) {
|
|
130
|
+
grouped[setting.project][setting.category] = [];
|
|
131
|
+
}
|
|
132
|
+
grouped[setting.project][setting.category].push(setting);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return Response.json({
|
|
136
|
+
settings,
|
|
137
|
+
grouped,
|
|
138
|
+
count: settings.length,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function handleGetSettings(
|
|
143
|
+
env: Env,
|
|
144
|
+
project: string,
|
|
145
|
+
category: string
|
|
146
|
+
): Promise<Response> {
|
|
147
|
+
if (!validateProject(project)) {
|
|
148
|
+
return Response.json({ error: `Invalid project: ${project}` }, { status: 400 });
|
|
149
|
+
}
|
|
150
|
+
if (!validateCategory(category)) {
|
|
151
|
+
return Response.json({ error: `Invalid category: ${category}` }, { status: 400 });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const result = await env.PLATFORM_DB.prepare(
|
|
155
|
+
'SELECT * FROM platform_settings WHERE project = ? AND category = ? ORDER BY key'
|
|
156
|
+
)
|
|
157
|
+
.bind(project, category)
|
|
158
|
+
.all<Setting>();
|
|
159
|
+
|
|
160
|
+
const settings = result.results || [];
|
|
161
|
+
|
|
162
|
+
// Parse JSON values for response
|
|
163
|
+
const parsed = settings.map((s) => ({
|
|
164
|
+
...s,
|
|
165
|
+
parsed_value: JSON.parse(s.value),
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
return Response.json({
|
|
169
|
+
project,
|
|
170
|
+
category,
|
|
171
|
+
settings: parsed,
|
|
172
|
+
count: settings.length,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function handleGetSetting(
|
|
177
|
+
env: Env,
|
|
178
|
+
project: string,
|
|
179
|
+
category: string,
|
|
180
|
+
key: string
|
|
181
|
+
): Promise<Response> {
|
|
182
|
+
if (!validateProject(project)) {
|
|
183
|
+
return Response.json({ error: `Invalid project: ${project}` }, { status: 400 });
|
|
184
|
+
}
|
|
185
|
+
if (!validateCategory(category)) {
|
|
186
|
+
return Response.json({ error: `Invalid category: ${category}` }, { status: 400 });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const result = await env.PLATFORM_DB.prepare(
|
|
190
|
+
'SELECT * FROM platform_settings WHERE project = ? AND category = ? AND key = ?'
|
|
191
|
+
)
|
|
192
|
+
.bind(project, category, key)
|
|
193
|
+
.first<Setting>();
|
|
194
|
+
|
|
195
|
+
if (!result) {
|
|
196
|
+
return Response.json({ error: 'Setting not found' }, { status: 404 });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return Response.json({
|
|
200
|
+
...result,
|
|
201
|
+
parsed_value: JSON.parse(result.value),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function handleUpdateSetting(
|
|
206
|
+
request: Request,
|
|
207
|
+
env: Env,
|
|
208
|
+
project: string,
|
|
209
|
+
category: string,
|
|
210
|
+
key: string
|
|
211
|
+
): Promise<Response> {
|
|
212
|
+
if (!validateProject(project)) {
|
|
213
|
+
return Response.json({ error: `Invalid project: ${project}` }, { status: 400 });
|
|
214
|
+
}
|
|
215
|
+
if (!validateCategory(category)) {
|
|
216
|
+
return Response.json({ error: `Invalid category: ${category}` }, { status: 400 });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const body = (await request.json()) as UpdateSettingRequest;
|
|
220
|
+
const email = getUserEmail(request);
|
|
221
|
+
const id = generateId(project, category, key);
|
|
222
|
+
const now = Math.floor(Date.now() / 1000);
|
|
223
|
+
const valueJson = JSON.stringify(body.value);
|
|
224
|
+
|
|
225
|
+
// Upsert the setting
|
|
226
|
+
await env.PLATFORM_DB.prepare(
|
|
227
|
+
`INSERT INTO platform_settings (id, project, category, key, value, description, updated_at, updated_by)
|
|
228
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
229
|
+
ON CONFLICT(project, category, key) DO UPDATE SET
|
|
230
|
+
value = excluded.value,
|
|
231
|
+
description = COALESCE(excluded.description, platform_settings.description),
|
|
232
|
+
updated_at = excluded.updated_at,
|
|
233
|
+
updated_by = excluded.updated_by`
|
|
234
|
+
)
|
|
235
|
+
.bind(id, project, category, key, valueJson, body.description || null, now, email)
|
|
236
|
+
.run();
|
|
237
|
+
|
|
238
|
+
return Response.json({
|
|
239
|
+
success: true,
|
|
240
|
+
id,
|
|
241
|
+
project,
|
|
242
|
+
category,
|
|
243
|
+
key,
|
|
244
|
+
value: body.value,
|
|
245
|
+
updated_at: now,
|
|
246
|
+
updated_by: email,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function handleBulkUpdate(request: Request, env: Env): Promise<Response> {
|
|
251
|
+
const body = (await request.json()) as BulkUpdateRequest;
|
|
252
|
+
const email = getUserEmail(request);
|
|
253
|
+
const now = Math.floor(Date.now() / 1000);
|
|
254
|
+
|
|
255
|
+
if (!body.settings || !Array.isArray(body.settings)) {
|
|
256
|
+
return Response.json({ error: 'Missing settings array' }, { status: 400 });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const results: Array<{ id: string; success: boolean; error?: string }> = [];
|
|
260
|
+
|
|
261
|
+
for (const setting of body.settings) {
|
|
262
|
+
if (!validateProject(setting.project)) {
|
|
263
|
+
results.push({
|
|
264
|
+
id: generateId(setting.project, setting.category, setting.key),
|
|
265
|
+
success: false,
|
|
266
|
+
error: `Invalid project: ${setting.project}`,
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!validateCategory(setting.category)) {
|
|
271
|
+
results.push({
|
|
272
|
+
id: generateId(setting.project, setting.category, setting.key),
|
|
273
|
+
success: false,
|
|
274
|
+
error: `Invalid category: ${setting.category}`,
|
|
275
|
+
});
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const id = generateId(setting.project, setting.category, setting.key);
|
|
280
|
+
const valueJson = JSON.stringify(setting.value);
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
await env.PLATFORM_DB.prepare(
|
|
284
|
+
`INSERT INTO platform_settings (id, project, category, key, value, description, updated_at, updated_by)
|
|
285
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
286
|
+
ON CONFLICT(project, category, key) DO UPDATE SET
|
|
287
|
+
value = excluded.value,
|
|
288
|
+
description = COALESCE(excluded.description, platform_settings.description),
|
|
289
|
+
updated_at = excluded.updated_at,
|
|
290
|
+
updated_by = excluded.updated_by`
|
|
291
|
+
)
|
|
292
|
+
.bind(
|
|
293
|
+
id,
|
|
294
|
+
setting.project,
|
|
295
|
+
setting.category,
|
|
296
|
+
setting.key,
|
|
297
|
+
valueJson,
|
|
298
|
+
setting.description || null,
|
|
299
|
+
now,
|
|
300
|
+
email
|
|
301
|
+
)
|
|
302
|
+
.run();
|
|
303
|
+
|
|
304
|
+
results.push({ id, success: true });
|
|
305
|
+
} catch (error) {
|
|
306
|
+
results.push({
|
|
307
|
+
id,
|
|
308
|
+
success: false,
|
|
309
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const successCount = results.filter((r) => r.success).length;
|
|
315
|
+
return Response.json({
|
|
316
|
+
success: successCount === body.settings.length,
|
|
317
|
+
total: body.settings.length,
|
|
318
|
+
succeeded: successCount,
|
|
319
|
+
failed: body.settings.length - successCount,
|
|
320
|
+
results,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function handleDeleteSetting(
|
|
325
|
+
env: Env,
|
|
326
|
+
project: string,
|
|
327
|
+
category: string,
|
|
328
|
+
key: string
|
|
329
|
+
): Promise<Response> {
|
|
330
|
+
if (!validateProject(project)) {
|
|
331
|
+
return Response.json({ error: `Invalid project: ${project}` }, { status: 400 });
|
|
332
|
+
}
|
|
333
|
+
if (!validateCategory(category)) {
|
|
334
|
+
return Response.json({ error: `Invalid category: ${category}` }, { status: 400 });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const result = await env.PLATFORM_DB.prepare(
|
|
338
|
+
'DELETE FROM platform_settings WHERE project = ? AND category = ? AND key = ?'
|
|
339
|
+
)
|
|
340
|
+
.bind(project, category, key)
|
|
341
|
+
.run();
|
|
342
|
+
|
|
343
|
+
if (result.meta.changes === 0) {
|
|
344
|
+
return Response.json({ error: 'Setting not found' }, { status: 404 });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return Response.json({ success: true, deleted: generateId(project, category, key) });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// =============================================================================
|
|
351
|
+
// MAIN WORKER
|
|
352
|
+
// =============================================================================
|
|
353
|
+
|
|
354
|
+
export default {
|
|
355
|
+
async fetch(
|
|
356
|
+
request: Request,
|
|
357
|
+
env: Env,
|
|
358
|
+
ctx: ExecutionContext
|
|
359
|
+
): Promise<Response> {
|
|
360
|
+
const url = new URL(request.url);
|
|
361
|
+
|
|
362
|
+
// Health check (lightweight)
|
|
363
|
+
if (url.pathname === '/health') {
|
|
364
|
+
return Response.json({
|
|
365
|
+
status: 'ok',
|
|
366
|
+
service: 'platform-settings',
|
|
367
|
+
timestamp: new Date().toISOString(),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const log = createLoggerFromRequest(request, env, 'platform-settings', FEATURE_ID);
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const trackedEnv = withFeatureBudget(env, FEATURE_ID, { ctx });
|
|
375
|
+
|
|
376
|
+
// GET /settings - List all settings
|
|
377
|
+
if (url.pathname === '/settings' && request.method === 'GET') {
|
|
378
|
+
return await handleListSettings(trackedEnv, url);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// PUT /settings/bulk - Bulk update
|
|
382
|
+
if (url.pathname === '/settings/bulk' && request.method === 'PUT') {
|
|
383
|
+
return await handleBulkUpdate(request, trackedEnv);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Routes with project/category/key parameters
|
|
387
|
+
// GET /settings/:project/:category
|
|
388
|
+
const categoryMatch = url.pathname.match(/^\/settings\/([^/]+)\/([^/]+)$/);
|
|
389
|
+
if (categoryMatch && request.method === 'GET') {
|
|
390
|
+
return await handleGetSettings(trackedEnv, categoryMatch[1], categoryMatch[2]);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// GET/PUT/DELETE /settings/:project/:category/:key
|
|
394
|
+
const keyMatch = url.pathname.match(/^\/settings\/([^/]+)\/([^/]+)\/([^/]+)$/);
|
|
395
|
+
if (keyMatch) {
|
|
396
|
+
const [, project, category, key] = keyMatch;
|
|
397
|
+
if (request.method === 'GET') {
|
|
398
|
+
return await handleGetSetting(trackedEnv, project, category, key);
|
|
399
|
+
}
|
|
400
|
+
if (request.method === 'PUT') {
|
|
401
|
+
return await handleUpdateSetting(request, trackedEnv, project, category, key);
|
|
402
|
+
}
|
|
403
|
+
if (request.method === 'DELETE') {
|
|
404
|
+
return await handleDeleteSetting(trackedEnv, project, category, key);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// API index
|
|
409
|
+
return Response.json({
|
|
410
|
+
service: 'platform-settings',
|
|
411
|
+
version: '1.0.0',
|
|
412
|
+
endpoints: [
|
|
413
|
+
'GET /health - Health check',
|
|
414
|
+
'GET /settings - List all settings (with filters)',
|
|
415
|
+
'GET /settings/:project/:category - Get settings for project/category',
|
|
416
|
+
'GET /settings/:project/:category/:key - Get specific setting',
|
|
417
|
+
'PUT /settings/:project/:category/:key - Update setting',
|
|
418
|
+
'DELETE /settings/:project/:category/:key - Delete setting',
|
|
419
|
+
'PUT /settings/bulk - Bulk update multiple settings',
|
|
420
|
+
],
|
|
421
|
+
valid_projects: VALID_PROJECTS,
|
|
422
|
+
valid_categories: VALID_CATEGORIES,
|
|
423
|
+
});
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error instanceof CircuitBreakerError) {
|
|
426
|
+
log.warn('Circuit breaker tripped', error);
|
|
427
|
+
return Response.json(
|
|
428
|
+
{ error: 'Service temporarily unavailable' },
|
|
429
|
+
{ status: 503, headers: { 'Retry-After': '60' } }
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
log.error('Request failed', error);
|
|
433
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "{{projectSlug}}-alert-router",
|
|
4
|
+
"main": "workers/platform-alert-router.ts",
|
|
5
|
+
"compatibility_date": "2026-01-01",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat_v2"],
|
|
7
|
+
"observability": { "enabled": true },
|
|
8
|
+
|
|
9
|
+
"d1_databases": [
|
|
10
|
+
{
|
|
11
|
+
"binding": "PLATFORM_DB",
|
|
12
|
+
"database_name": "{{projectSlug}}-metrics",
|
|
13
|
+
"database_id": "YOUR_D1_DATABASE_ID"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
"kv_namespaces": [
|
|
18
|
+
{
|
|
19
|
+
"binding": "PLATFORM_CACHE",
|
|
20
|
+
"id": "YOUR_KV_NAMESPACE_ID"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"binding": "PLATFORM_ALERTS",
|
|
24
|
+
"id": "YOUR_KV_ALERTS_NAMESPACE_ID"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
"vars": {
|
|
29
|
+
"CLOUDFLARE_ACCOUNT_ID": "YOUR_CLOUDFLARE_ACCOUNT_ID"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Secrets needed:
|
|
33
|
+
// SLACK_WEBHOOK_URL
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "{{projectSlug}}-notifications",
|
|
4
|
+
"main": "workers/platform-notifications.ts",
|
|
5
|
+
"compatibility_date": "2026-01-01",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat_v2"],
|
|
7
|
+
"observability": { "enabled": true },
|
|
8
|
+
|
|
9
|
+
"d1_databases": [
|
|
10
|
+
{
|
|
11
|
+
"binding": "PLATFORM_DB",
|
|
12
|
+
"database_name": "{{projectSlug}}-metrics",
|
|
13
|
+
"database_id": "YOUR_D1_DATABASE_ID"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
"kv_namespaces": [
|
|
18
|
+
{
|
|
19
|
+
"binding": "PLATFORM_CACHE",
|
|
20
|
+
"id": "YOUR_KV_NAMESPACE_ID"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "{{projectSlug}}-pattern-discovery",
|
|
4
|
+
"main": "workers/pattern-discovery.ts",
|
|
5
|
+
"compatibility_date": "2026-01-01",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat_v2"],
|
|
7
|
+
"observability": { "enabled": true },
|
|
8
|
+
|
|
9
|
+
"triggers": {
|
|
10
|
+
"crons": ["0 2 * * *", "0 3 * * *"]
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
"d1_databases": [
|
|
14
|
+
{
|
|
15
|
+
"binding": "PLATFORM_DB",
|
|
16
|
+
"database_name": "{{projectSlug}}-metrics",
|
|
17
|
+
"database_id": "YOUR_D1_DATABASE_ID"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
|
|
21
|
+
"kv_namespaces": [
|
|
22
|
+
{
|
|
23
|
+
"binding": "PLATFORM_CACHE",
|
|
24
|
+
"id": "YOUR_KV_NAMESPACE_ID"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
"ai": { "binding": "AI" },
|
|
29
|
+
|
|
30
|
+
"vars": {
|
|
31
|
+
"GATUS_HEARTBEAT_URL": "{{gatusUrl}}"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "{{projectSlug}}-search",
|
|
4
|
+
"main": "workers/platform-search.ts",
|
|
5
|
+
"compatibility_date": "2026-01-01",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat_v2"],
|
|
7
|
+
"observability": { "enabled": true },
|
|
8
|
+
|
|
9
|
+
"d1_databases": [
|
|
10
|
+
{
|
|
11
|
+
"binding": "PLATFORM_DB",
|
|
12
|
+
"database_name": "{{projectSlug}}-metrics",
|
|
13
|
+
"database_id": "YOUR_D1_DATABASE_ID"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "{{projectSlug}}-settings",
|
|
4
|
+
"main": "workers/platform-settings.ts",
|
|
5
|
+
"compatibility_date": "2026-01-01",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat_v2"],
|
|
7
|
+
"observability": { "enabled": true },
|
|
8
|
+
|
|
9
|
+
"d1_databases": [
|
|
10
|
+
{
|
|
11
|
+
"binding": "PLATFORM_DB",
|
|
12
|
+
"database_name": "{{projectSlug}}-metrics",
|
|
13
|
+
"database_id": "YOUR_D1_DATABASE_ID"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
"kv_namespaces": [
|
|
18
|
+
{
|
|
19
|
+
"binding": "PLATFORM_CACHE",
|
|
20
|
+
"id": "YOUR_KV_NAMESPACE_ID"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# {{projectName}} Platform
|
|
2
|
+
|
|
3
|
+
Cost protection and monitoring infrastructure powered by [Platform SDK](https://github.com/littlebearapps/platform-sdks).
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
**Tier**: {{tier}}
|
|
8
|
+
|
|
9
|
+
### 1. Create Cloudflare Resources
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx wrangler d1 create {{projectSlug}}-metrics
|
|
13
|
+
npx wrangler kv namespace create PLATFORM_CACHE
|
|
14
|
+
npx wrangler queues create {{projectSlug}}-telemetry
|
|
15
|
+
npx wrangler queues create {{projectSlug}}-telemetry-dlq
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 2. Update Wrangler Configs
|
|
19
|
+
|
|
20
|
+
Replace `YOUR_*_ID` placeholders in `wrangler.*.jsonc` with the IDs from step 1.
|
|
21
|
+
|
|
22
|
+
### 3. Run Migrations
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx wrangler d1 migrations apply {{projectSlug}}-metrics --remote
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 4. Sync Config
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run sync:config
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 5. Deploy
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx wrangler deploy -c wrangler.{{projectSlug}}-usage.jsonc
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 6. Install SDK in Consumer Projects
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @littlebearapps/platform-consumer-sdk
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Architecture
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Consumer Projects
|
|
50
|
+
|
|
|
51
|
+
| SDK telemetry (via queue)
|
|
52
|
+
v
|
|
53
|
+
{{projectSlug}}-usage (cron + queue consumer)
|
|
54
|
+
|
|
|
55
|
+
v
|
|
56
|
+
D1 Warehouse + KV Cache + Analytics Engine
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
- `platform/config/services.yaml` — Project and feature registry
|
|
62
|
+
- `platform/config/budgets.yaml` — Limits, thresholds, circuit breakers
|
|
63
|
+
|
|
64
|
+
After editing config, run `npm run sync:config` to sync to D1/KV.
|
|
65
|
+
|
|
66
|
+
## Resources
|
|
67
|
+
|
|
68
|
+
- [Platform SDK Documentation](https://github.com/littlebearapps/platform-sdks)
|
|
69
|
+
- [Cloudflare Workers Pricing](https://developers.cloudflare.com/workers/platform/pricing/)
|