@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.
Files changed (94) hide show
  1. package/README.md +112 -0
  2. package/dist/index.d.ts +16 -0
  3. package/dist/index.js +89 -0
  4. package/dist/prompts.d.ts +27 -0
  5. package/dist/prompts.js +80 -0
  6. package/dist/scaffold.d.ts +5 -0
  7. package/dist/scaffold.js +65 -0
  8. package/dist/templates.d.ts +16 -0
  9. package/dist/templates.js +131 -0
  10. package/package.json +46 -0
  11. package/templates/full/migrations/006_pattern_discovery.sql +199 -0
  12. package/templates/full/migrations/007_notifications_search.sql +127 -0
  13. package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
  14. package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
  15. package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
  16. package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
  17. package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
  18. package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
  19. package/templates/full/workers/pattern-discovery.ts +661 -0
  20. package/templates/full/workers/platform-alert-router.ts +1809 -0
  21. package/templates/full/workers/platform-notifications.ts +424 -0
  22. package/templates/full/workers/platform-search.ts +480 -0
  23. package/templates/full/workers/platform-settings.ts +436 -0
  24. package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
  25. package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
  26. package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
  27. package/templates/full/wrangler.search.jsonc.hbs +16 -0
  28. package/templates/full/wrangler.settings.jsonc.hbs +23 -0
  29. package/templates/shared/README.md.hbs +69 -0
  30. package/templates/shared/config/budgets.yaml.hbs +72 -0
  31. package/templates/shared/config/services.yaml.hbs +45 -0
  32. package/templates/shared/migrations/001_core_tables.sql +117 -0
  33. package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
  34. package/templates/shared/migrations/003_feature_tracking.sql +250 -0
  35. package/templates/shared/migrations/004_settings_alerts.sql +452 -0
  36. package/templates/shared/migrations/seed.sql.hbs +4 -0
  37. package/templates/shared/package.json.hbs +21 -0
  38. package/templates/shared/scripts/sync-config.ts +242 -0
  39. package/templates/shared/tsconfig.json +12 -0
  40. package/templates/shared/workers/lib/analytics-engine.ts +357 -0
  41. package/templates/shared/workers/lib/billing.ts +293 -0
  42. package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
  43. package/templates/shared/workers/lib/control.ts +292 -0
  44. package/templates/shared/workers/lib/economics.ts +368 -0
  45. package/templates/shared/workers/lib/metrics.ts +103 -0
  46. package/templates/shared/workers/lib/platform-settings.ts +407 -0
  47. package/templates/shared/workers/lib/shared/allowances.ts +333 -0
  48. package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
  49. package/templates/shared/workers/lib/shared/types.ts +58 -0
  50. package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
  51. package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
  52. package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
  53. package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
  54. package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
  55. package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
  56. package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
  57. package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
  58. package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
  59. package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
  60. package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
  61. package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
  62. package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
  63. package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
  64. package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
  65. package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
  66. package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
  67. package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
  68. package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
  69. package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
  70. package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
  71. package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
  72. package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
  73. package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
  74. package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
  75. package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
  76. package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
  77. package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
  78. package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
  79. package/templates/shared/workers/platform-usage.ts +1915 -0
  80. package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
  81. package/templates/standard/migrations/005_error_collection.sql +162 -0
  82. package/templates/standard/workers/error-collector.ts +2670 -0
  83. package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
  84. package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
  85. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
  86. package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
  87. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
  88. package/templates/standard/workers/lib/error-collector/github.ts +329 -0
  89. package/templates/standard/workers/lib/error-collector/types.ts +262 -0
  90. package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
  91. package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
  92. package/templates/standard/workers/platform-sentinel.ts +1744 -0
  93. package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
  94. 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/)