@littlebearapps/platform-admin-sdk 1.4.2 → 2.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/dist/templates.d.ts +1 -1
- package/dist/templates.js +232 -2
- package/package.json +1 -1
- package/templates/full/config/audit-targets.yaml +72 -0
- package/templates/full/dashboard/src/components/notifications/NotificationBell.tsx +30 -0
- package/templates/full/dashboard/src/components/notifications/NotificationList.tsx +116 -0
- package/templates/full/dashboard/src/components/notifications/index.ts +2 -0
- package/templates/full/dashboard/src/components/patterns/ActivePatterns.tsx +62 -0
- package/templates/full/dashboard/src/components/patterns/PatternStats.tsx +60 -0
- package/templates/full/dashboard/src/components/patterns/PatternTabs.tsx +116 -0
- package/templates/full/dashboard/src/components/patterns/SuggestionsQueue.tsx +115 -0
- package/templates/full/dashboard/src/components/patterns/SystemPatterns.tsx +52 -0
- package/templates/full/dashboard/src/components/patterns/index.ts +5 -0
- package/templates/full/dashboard/src/components/reports/GapDetectionReport.tsx +69 -0
- package/templates/full/dashboard/src/components/reports/SdkAuditReport.tsx +72 -0
- package/templates/full/dashboard/src/components/reports/index.ts +2 -0
- package/templates/full/dashboard/src/components/search/SearchModal.tsx +108 -0
- package/templates/full/dashboard/src/pages/api/notifications/[id]/read.ts +37 -0
- package/templates/full/dashboard/src/pages/api/notifications/index.ts +47 -0
- package/templates/full/dashboard/src/pages/api/notifications/read-all.ts +28 -0
- package/templates/full/dashboard/src/pages/api/notifications/unread-count.ts +31 -0
- package/templates/full/dashboard/src/pages/api/patterns/approve.ts +55 -0
- package/templates/full/dashboard/src/pages/api/patterns/cache-refresh.ts +38 -0
- package/templates/full/dashboard/src/pages/api/patterns/discover.ts +36 -0
- package/templates/full/dashboard/src/pages/api/patterns/index.ts +36 -0
- package/templates/full/dashboard/src/pages/api/patterns/ready-for-review.ts +39 -0
- package/templates/full/dashboard/src/pages/api/patterns/reject.ts +54 -0
- package/templates/full/dashboard/src/pages/api/patterns/stats.ts +39 -0
- package/templates/full/dashboard/src/pages/api/patterns/suggestions.ts +43 -0
- package/templates/full/dashboard/src/pages/api/reports/audit.ts +45 -0
- package/templates/full/dashboard/src/pages/api/reports/usage.ts +52 -0
- package/templates/full/dashboard/src/pages/api/search/index.ts +74 -0
- package/templates/full/dashboard/src/pages/api/search/reindex.ts +28 -0
- package/templates/full/dashboard/src/pages/api/search/stats.ts +27 -0
- package/templates/full/dashboard/src/pages/api/settings/index.ts +37 -0
- package/templates/full/dashboard/src/pages/api/settings/update.ts +41 -0
- package/templates/full/dashboard/src/pages/api/topology/index.ts +56 -0
- package/templates/full/dashboard/src/pages/notifications.astro +11 -0
- package/templates/full/migrations/008_auditor.sql +99 -0
- package/templates/full/migrations/010_pricing_versions.sql +110 -0
- package/templates/full/migrations/011_multi_account.sql +51 -0
- package/templates/full/scripts/ops/set-kv-pricing.ts +182 -0
- package/templates/full/scripts/ops/universal-backfill.ts +147 -0
- package/templates/full/workers/lib/ai-judge-schema.ts +181 -0
- package/templates/full/workers/lib/auditor/comprehensive-report.ts +407 -0
- package/templates/full/workers/lib/auditor/feature-coverage.ts +348 -0
- package/templates/full/workers/lib/auditor/index.ts +9 -0
- package/templates/full/workers/lib/auditor/types.ts +167 -0
- package/templates/full/workers/platform-auditor.ts +1071 -0
- package/templates/full/wrangler.auditor.jsonc.hbs +75 -0
- package/templates/shared/.github/workflows/contract-check.yml.hbs +42 -0
- package/templates/shared/.github/workflows/dashboard-deploy.yml.hbs +39 -0
- package/templates/shared/.github/workflows/platform-check.yml.hbs +28 -0
- package/templates/shared/.github/workflows/security.yml +33 -0
- package/templates/shared/config/observability.yaml.hbs +276 -0
- package/templates/shared/contracts/schemas/envelope.v1.schema.json +64 -0
- package/templates/shared/contracts/schemas/error_report.v1.schema.json +65 -0
- package/templates/shared/contracts/types/telemetry-envelope.types.ts +139 -0
- package/templates/shared/dashboard/astro.config.mjs +21 -0
- package/templates/shared/dashboard/package.json.hbs +29 -0
- package/templates/shared/dashboard/src/components/Header.astro +29 -0
- package/templates/shared/dashboard/src/components/Nav.astro.hbs +59 -0
- package/templates/shared/dashboard/src/components/infrastructure/AlertHistory.tsx +57 -0
- package/templates/shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx +73 -0
- package/templates/shared/dashboard/src/components/infrastructure/ServiceRegistry.tsx +55 -0
- package/templates/shared/dashboard/src/components/infrastructure/UptimeStatus.tsx +56 -0
- package/templates/shared/dashboard/src/components/infrastructure/index.ts +4 -0
- package/templates/shared/dashboard/src/components/overview/ActivityFeed.tsx +134 -0
- package/templates/shared/dashboard/src/components/overview/CostQuadrant.tsx +131 -0
- package/templates/shared/dashboard/src/components/overview/ErrorsQuadrant.tsx +113 -0
- package/templates/shared/dashboard/src/components/overview/HealthQuadrant.tsx +87 -0
- package/templates/shared/dashboard/src/components/overview/MissionControl.tsx +139 -0
- package/templates/shared/dashboard/src/components/resources/AllowanceStatus.tsx +44 -0
- package/templates/shared/dashboard/src/components/resources/CostCentreOverview.tsx +42 -0
- package/templates/shared/dashboard/src/components/resources/ResourceTabs.tsx +69 -0
- package/templates/shared/dashboard/src/components/resources/index.ts +3 -0
- package/templates/shared/dashboard/src/components/settings/SettingsCard.tsx +21 -0
- package/templates/shared/dashboard/src/components/settings/index.ts +1 -0
- package/templates/shared/dashboard/src/components/ui/AlertBanner.tsx +39 -0
- package/templates/shared/dashboard/src/components/ui/Breadcrumbs.tsx +27 -0
- package/templates/shared/dashboard/src/components/ui/EmptyState.tsx +26 -0
- package/templates/shared/dashboard/src/components/ui/ErrorBoundary.tsx +42 -0
- package/templates/shared/dashboard/src/components/ui/LoadingSkeleton.tsx +18 -0
- package/templates/shared/dashboard/src/components/ui/PageShell.tsx +26 -0
- package/templates/shared/dashboard/src/components/ui/Sparkline.tsx +127 -0
- package/templates/shared/dashboard/src/components/ui/StatusDot.tsx +21 -0
- package/templates/shared/dashboard/src/components/ui/Toast.tsx +44 -0
- package/templates/shared/dashboard/src/components/ui/index.ts +9 -0
- package/templates/shared/dashboard/src/components/usage/AnomaliesWidget.tsx +68 -0
- package/templates/shared/dashboard/src/components/usage/HourlyUsageChart.tsx +55 -0
- package/templates/shared/dashboard/src/components/usage/PlanAllowanceDashboard.tsx +67 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCostBreakdown.tsx +55 -0
- package/templates/shared/dashboard/src/components/usage/index.ts +4 -0
- package/templates/shared/dashboard/src/env.d.ts.hbs +34 -0
- package/templates/shared/dashboard/src/layouts/DashboardLayout.astro +37 -0
- package/templates/shared/dashboard/src/lib/cloudflare/costs.ts +21 -0
- package/templates/shared/dashboard/src/lib/fetch.ts +29 -0
- package/templates/shared/dashboard/src/lib/types.ts +72 -0
- package/templates/shared/dashboard/src/middleware/auth.ts +100 -0
- package/templates/shared/dashboard/src/middleware/index.ts +1 -0
- package/templates/shared/dashboard/src/pages/api/costs/overview.ts +65 -0
- package/templates/shared/dashboard/src/pages/api/costs/providers.ts +47 -0
- package/templates/shared/dashboard/src/pages/api/infrastructure/services.ts +55 -0
- package/templates/shared/dashboard/src/pages/api/infrastructure/stats.ts +99 -0
- package/templates/shared/dashboard/src/pages/api/overview/summary.ts +311 -0
- package/templates/shared/dashboard/src/pages/api/usage/allowances.ts +56 -0
- package/templates/shared/dashboard/src/pages/api/usage/anomalies.ts +45 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing.ts +53 -0
- package/templates/shared/dashboard/src/pages/api/usage/circuit-breakers.ts +44 -0
- package/templates/shared/dashboard/src/pages/api/usage/granular.ts +50 -0
- package/templates/shared/dashboard/src/pages/api/usage/hourly.ts +45 -0
- package/templates/shared/dashboard/src/pages/api/usage/projects.ts +51 -0
- package/templates/shared/dashboard/src/pages/api/usage/status.ts +42 -0
- package/templates/shared/dashboard/src/pages/api/user/identity.ts +11 -0
- package/templates/shared/dashboard/src/pages/dashboard.astro +11 -0
- package/templates/shared/dashboard/src/pages/index.astro +3 -0
- package/templates/shared/dashboard/src/pages/resources.astro +11 -0
- package/templates/shared/dashboard/src/pages/settings/index.astro +28 -0
- package/templates/shared/dashboard/src/pages/settings/notifications.astro +34 -0
- package/templates/shared/dashboard/src/pages/settings/thresholds.astro +39 -0
- package/templates/shared/dashboard/src/pages/settings/usage.astro +28 -0
- package/templates/shared/dashboard/src/styles/global.css +29 -0
- package/templates/shared/dashboard/tailwind.config.mjs +9 -0
- package/templates/shared/dashboard/tsconfig.json +9 -0
- package/templates/shared/dashboard/wrangler.json.hbs +47 -0
- package/templates/shared/docs/architecture.md +89 -0
- package/templates/shared/docs/post-deploy-runbook.md +126 -0
- package/templates/shared/docs/troubleshooting.md +91 -0
- package/templates/shared/package.json.hbs +17 -1
- package/templates/shared/scripts/ops/backfill-cloudflare-daily.ts +145 -0
- package/templates/shared/scripts/ops/backfill-cloudflare-hourly.ts +473 -0
- package/templates/shared/scripts/ops/backfill-monthly-rollups.ts +125 -0
- package/templates/shared/scripts/ops/discover-graphql-datasets.ts +482 -0
- package/templates/shared/scripts/ops/reset-budget-state.ts +279 -0
- package/templates/shared/scripts/ops/validate-controls.js +141 -0
- package/templates/shared/scripts/ops/validate-pipeline.ts +237 -0
- package/templates/shared/scripts/ops/verify-account-completeness.ts +236 -0
- package/templates/shared/scripts/validate-schemas.js +61 -0
- package/templates/shared/tests/contract/validate-schemas.test.ts +130 -0
- package/templates/shared/tests/fixtures/telemetry-envelope-invalid.json +9 -0
- package/templates/shared/tests/fixtures/telemetry-envelope-valid.json +27 -0
- package/templates/shared/tests/helpers/mock-d1.ts +61 -0
- package/templates/shared/tests/helpers/mock-kv.ts +37 -0
- package/templates/shared/tests/unit/workers/batch-persistence.test.ts +133 -0
- package/templates/shared/tests/unit/workers/budget-enforcement.test.ts +214 -0
- package/templates/shared/vitest.config.ts +18 -0
- package/templates/shared/workers/lib/usage/collectors/anthropic.ts +114 -0
- package/templates/shared/workers/lib/usage/collectors/apify.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/custom-http.ts +151 -0
- package/templates/shared/workers/lib/usage/collectors/deepseek.ts +92 -0
- package/templates/shared/workers/lib/usage/collectors/gemini.ts +263 -0
- package/templates/shared/workers/lib/usage/collectors/github.ts +362 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +31 -15
- package/templates/shared/workers/lib/usage/collectors/minimax.ts +106 -0
- package/templates/shared/workers/lib/usage/collectors/openai.ts +171 -0
- package/templates/shared/workers/lib/usage/collectors/resend.ts +79 -0
- package/templates/shared/workers/lib/usage/collectors/stripe.ts +192 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +46 -0
- package/templates/shared/workers/platform-usage.ts +98 -8
- package/templates/standard/dashboard/src/components/errors/ErrorStats.tsx +53 -0
- package/templates/standard/dashboard/src/components/errors/ErrorsTable.tsx +133 -0
- package/templates/standard/dashboard/src/components/errors/index.ts +2 -0
- package/templates/standard/dashboard/src/components/health/CircuitBreakerEvents.tsx +69 -0
- package/templates/standard/dashboard/src/components/health/CircuitBreakerPanel.tsx +97 -0
- package/templates/standard/dashboard/src/components/health/DlqStatusCard.tsx +52 -0
- package/templates/standard/dashboard/src/components/health/HealthTabs.tsx +86 -0
- package/templates/standard/dashboard/src/components/health/index.ts +4 -0
- package/templates/standard/dashboard/src/lib/errors.ts +28 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts +49 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/resolve.ts +36 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint].ts +55 -0
- package/templates/standard/dashboard/src/pages/api/errors/index.ts +58 -0
- package/templates/standard/dashboard/src/pages/api/errors/stats.ts +55 -0
- package/templates/standard/dashboard/src/pages/api/health/audit-history.ts +37 -0
- package/templates/standard/dashboard/src/pages/api/health/dlq.ts +43 -0
- package/templates/standard/dashboard/src/pages/circuit-breakers.astro +13 -0
- package/templates/standard/dashboard/src/pages/errors.astro +13 -0
- package/templates/standard/dashboard/src/pages/health.astro +11 -0
- package/templates/standard/migrations/009_topology_mapper.sql +65 -0
- package/templates/standard/tests/unit/error-collector/capture.test.ts +106 -0
- package/templates/standard/tests/unit/error-collector/fingerprint.test.ts +155 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +37 -3
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +32 -1
- package/templates/standard/workers/lib/mapper/attribution-check.ts +339 -0
- package/templates/standard/workers/lib/mapper/index.ts +7 -0
- package/templates/standard/workers/platform-mapper.ts +482 -0
- package/templates/standard/workers/platform-sdk-test-client.ts +125 -0
- package/templates/standard/wrangler.mapper.jsonc.hbs +85 -0
- package/templates/standard/wrangler.sdk-test-client.jsonc.hbs +62 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Monthly Rollup Backfill Script
|
|
4
|
+
*
|
|
5
|
+
* Aggregates daily_usage_rollups into monthly_usage_rollups via the D1 REST API.
|
|
6
|
+
* Queries existing daily data and rolls it up to monthly granularity.
|
|
7
|
+
*
|
|
8
|
+
* Prerequisites:
|
|
9
|
+
* CLOUDFLARE_API_TOKEN — API token with D1:Write permissions
|
|
10
|
+
* CLOUDFLARE_ACCOUNT_ID — Your Cloudflare account ID
|
|
11
|
+
* D1_DATABASE_ID — Your platform-metrics D1 database ID
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* npx tsx scripts/ops/backfill-monthly-rollups.ts
|
|
15
|
+
* npx tsx scripts/ops/backfill-monthly-rollups.ts --dry-run
|
|
16
|
+
* npx tsx scripts/ops/backfill-monthly-rollups.ts --start 2026-01 --end 2026-03
|
|
17
|
+
* npx tsx scripts/ops/backfill-monthly-rollups.ts --limit 12
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const REST_API_BASE = 'https://api.cloudflare.com/client/v4';
|
|
21
|
+
|
|
22
|
+
interface Args {
|
|
23
|
+
start?: string;
|
|
24
|
+
end?: string;
|
|
25
|
+
dryRun: boolean;
|
|
26
|
+
limit: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseArgs(): Args {
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const result: Args = { dryRun: false, limit: 12 };
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
if (args[i] === '--start' && args[i + 1]) result.start = args[++i];
|
|
35
|
+
else if (args[i] === '--end' && args[i + 1]) result.end = args[++i];
|
|
36
|
+
else if (args[i] === '--limit' && args[i + 1]) result.limit = Number(args[++i]);
|
|
37
|
+
else if (args[i] === '--dry-run') result.dryRun = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!result.start) {
|
|
41
|
+
const d = new Date();
|
|
42
|
+
d.setMonth(d.getMonth() - 6);
|
|
43
|
+
result.start = d.toISOString().slice(0, 7);
|
|
44
|
+
}
|
|
45
|
+
if (!result.end) {
|
|
46
|
+
result.end = new Date().toISOString().slice(0, 7);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getEnvOrThrow(key: string): string {
|
|
53
|
+
const val = process.env[key];
|
|
54
|
+
if (!val) throw new Error(`Missing required env var: ${key}`);
|
|
55
|
+
return val;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function d1Query(accountId: string, dbId: string, token: string, sql: string, params: unknown[] = []) {
|
|
59
|
+
const res = await fetch(`${REST_API_BASE}/accounts/${accountId}/d1/database/${dbId}/query`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({ sql, params }),
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const text = await res.text();
|
|
66
|
+
throw new Error(`D1 query failed (${res.status}): ${text}`);
|
|
67
|
+
}
|
|
68
|
+
return res.json() as Promise<{ result: Array<{ results: unknown[] }> }>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main() {
|
|
72
|
+
const args = parseArgs();
|
|
73
|
+
const token = getEnvOrThrow('CLOUDFLARE_API_TOKEN');
|
|
74
|
+
const accountId = getEnvOrThrow('CLOUDFLARE_ACCOUNT_ID');
|
|
75
|
+
const dbId = getEnvOrThrow('D1_DATABASE_ID');
|
|
76
|
+
|
|
77
|
+
console.log(`Backfilling monthly rollups: ${args.start} → ${args.end} (limit: ${args.limit}, dry-run: ${args.dryRun})`);
|
|
78
|
+
|
|
79
|
+
// Generate month list
|
|
80
|
+
const months: string[] = [];
|
|
81
|
+
const current = new Date(args.start + '-01');
|
|
82
|
+
const endDate = new Date(args.end + '-01');
|
|
83
|
+
while (current <= endDate && months.length < args.limit) {
|
|
84
|
+
months.push(current.toISOString().slice(0, 7));
|
|
85
|
+
current.setMonth(current.getMonth() + 1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let inserted = 0;
|
|
89
|
+
for (const month of months) {
|
|
90
|
+
const monthStart = `${month}-01`;
|
|
91
|
+
const nextMonth = new Date(monthStart);
|
|
92
|
+
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
|
93
|
+
const monthEnd = nextMonth.toISOString().slice(0, 10);
|
|
94
|
+
|
|
95
|
+
if (args.dryRun) {
|
|
96
|
+
console.log(`[DRY-RUN] Would roll up ${month}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await d1Query(accountId, dbId, token, `
|
|
101
|
+
INSERT OR REPLACE INTO monthly_usage_rollups (
|
|
102
|
+
project, snapshot_month,
|
|
103
|
+
d1_reads, d1_writes, kv_reads, kv_writes,
|
|
104
|
+
r2_reads, r2_writes, worker_requests, total_cost_usd
|
|
105
|
+
)
|
|
106
|
+
SELECT
|
|
107
|
+
project, ? as snapshot_month,
|
|
108
|
+
SUM(d1_reads), SUM(d1_writes), SUM(kv_reads), SUM(kv_writes),
|
|
109
|
+
SUM(r2_reads), SUM(r2_writes), SUM(worker_requests), SUM(total_cost_usd)
|
|
110
|
+
FROM daily_usage_rollups
|
|
111
|
+
WHERE project = 'all' AND snapshot_date >= ? AND snapshot_date < ?
|
|
112
|
+
GROUP BY project
|
|
113
|
+
`, [month, monthStart, monthEnd]);
|
|
114
|
+
|
|
115
|
+
inserted++;
|
|
116
|
+
console.log(` Rolled up ${month} (${inserted}/${months.length})`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`Done. Inserted ${inserted} monthly rollup rows.`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch((err) => {
|
|
123
|
+
console.error('Fatal error:', err);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
});
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Cloudflare GraphQL Dataset Discovery Script
|
|
4
|
+
*
|
|
5
|
+
* Discovers all available GraphQL datasets for your Cloudflare account using
|
|
6
|
+
* the Settings node. Helps identify datasets you're not currently querying.
|
|
7
|
+
*
|
|
8
|
+
* The Cloudflare GraphQL API exposes 70+ datasets. This script:
|
|
9
|
+
* 1. Queries the account Settings node to list ALL available datasets
|
|
10
|
+
* 2. Shows which datasets are enabled for your account
|
|
11
|
+
* 3. Compares against your current implementation to find gaps
|
|
12
|
+
*
|
|
13
|
+
* Prerequisites:
|
|
14
|
+
* - CLOUDFLARE_API_TOKEN env var with Analytics read permissions
|
|
15
|
+
* - CLOUDFLARE_ACCOUNT_ID env var
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* npx tsx scripts/ops/discover-graphql-datasets.ts
|
|
19
|
+
* npx tsx scripts/ops/discover-graphql-datasets.ts --json # JSON output
|
|
20
|
+
* npx tsx scripts/ops/discover-graphql-datasets.ts --compare # Compare vs current
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const GRAPHQL_ENDPOINT = 'https://api.cloudflare.com/client/v4/graphql';
|
|
24
|
+
|
|
25
|
+
// TODO: Add the datasets your platform-usage worker currently queries.
|
|
26
|
+
// These are the datasets collected by the default data-collection.ts handler.
|
|
27
|
+
// Uncomment/add entries as you enable collection for each service.
|
|
28
|
+
const CURRENTLY_QUERIED_DATASETS = [
|
|
29
|
+
'workersInvocationsAdaptive',
|
|
30
|
+
// 'workersSubrequestsAdaptive',
|
|
31
|
+
'd1AnalyticsAdaptiveGroups',
|
|
32
|
+
'd1StorageAdaptiveGroups',
|
|
33
|
+
'kvOperationsAdaptiveGroups',
|
|
34
|
+
'kvStorageAdaptiveGroups',
|
|
35
|
+
'r2OperationsAdaptiveGroups',
|
|
36
|
+
'r2StorageAdaptiveGroups',
|
|
37
|
+
// 'durableObjectsInvocationsAdaptiveGroups',
|
|
38
|
+
// 'durableObjectsPeriodicGroups',
|
|
39
|
+
// 'durableObjectsStorageGroups',
|
|
40
|
+
// 'vectorizeV2QueriesAdaptiveGroups',
|
|
41
|
+
// 'vectorizeV2StorageAdaptiveGroups',
|
|
42
|
+
'queueMessageOperationsAdaptiveGroups',
|
|
43
|
+
'queueConsumerMetricsAdaptiveGroups',
|
|
44
|
+
// 'workflowsAdaptiveGroups',
|
|
45
|
+
// 'aiInferenceAdaptive',
|
|
46
|
+
// 'httpRequestsAdaptiveGroups',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Known billable datasets — these have cost implications if used
|
|
50
|
+
const BILLABLE_DATASETS = [
|
|
51
|
+
// Workers Platform
|
|
52
|
+
'workersInvocationsAdaptive',
|
|
53
|
+
'workersSubrequestsAdaptive',
|
|
54
|
+
|
|
55
|
+
// D1
|
|
56
|
+
'd1AnalyticsAdaptiveGroups',
|
|
57
|
+
'd1StorageAdaptiveGroups',
|
|
58
|
+
|
|
59
|
+
// KV
|
|
60
|
+
'kvOperationsAdaptiveGroups',
|
|
61
|
+
'kvStorageAdaptiveGroups',
|
|
62
|
+
|
|
63
|
+
// R2
|
|
64
|
+
'r2OperationsAdaptiveGroups',
|
|
65
|
+
'r2StorageAdaptiveGroups',
|
|
66
|
+
|
|
67
|
+
// Durable Objects
|
|
68
|
+
'durableObjectsInvocationsAdaptiveGroups',
|
|
69
|
+
'durableObjectsPeriodicGroups',
|
|
70
|
+
'durableObjectsStorageGroups',
|
|
71
|
+
|
|
72
|
+
// Vectorize
|
|
73
|
+
'vectorizeV2QueriesAdaptiveGroups',
|
|
74
|
+
'vectorizeV2StorageAdaptiveGroups',
|
|
75
|
+
|
|
76
|
+
// Queues
|
|
77
|
+
'queueMessageOperationsAdaptiveGroups',
|
|
78
|
+
'queueConsumerMetricsAdaptiveGroups',
|
|
79
|
+
|
|
80
|
+
// Workflows
|
|
81
|
+
'workflowsAdaptiveGroups',
|
|
82
|
+
|
|
83
|
+
// Workers AI
|
|
84
|
+
'aiInferenceAdaptive',
|
|
85
|
+
|
|
86
|
+
// Stream (video)
|
|
87
|
+
'streamMinutesViewedAdaptive',
|
|
88
|
+
'streamMinutesViewedAdaptiveGroups',
|
|
89
|
+
|
|
90
|
+
// Images
|
|
91
|
+
'imagesAdaptiveGroups',
|
|
92
|
+
|
|
93
|
+
// Pages
|
|
94
|
+
'pagesProjectsAdaptiveGroups',
|
|
95
|
+
|
|
96
|
+
// AI Gateway
|
|
97
|
+
'aiGatewayAdaptiveGroups',
|
|
98
|
+
|
|
99
|
+
// Hyperdrive
|
|
100
|
+
'hyperdriveAdaptiveGroups',
|
|
101
|
+
|
|
102
|
+
// Browser Rendering
|
|
103
|
+
'browserRenderingAdaptiveGroups',
|
|
104
|
+
|
|
105
|
+
// TURN/Calls
|
|
106
|
+
'callsTurnUsageAdaptiveGroups',
|
|
107
|
+
|
|
108
|
+
// Spectrum
|
|
109
|
+
'spectrumNetworkAnalyticsAdaptiveGroups',
|
|
110
|
+
|
|
111
|
+
// Email Routing
|
|
112
|
+
'emailRoutingAdaptiveGroups',
|
|
113
|
+
|
|
114
|
+
// Cache/CDN
|
|
115
|
+
'httpRequestsAdaptiveGroups',
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
interface DatasetInfo {
|
|
119
|
+
name: string;
|
|
120
|
+
enabled: boolean;
|
|
121
|
+
maxPageSize?: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface DiscoveryResult {
|
|
125
|
+
timestamp: string;
|
|
126
|
+
accountId: string;
|
|
127
|
+
totalDatasetsFound: number;
|
|
128
|
+
enabledDatasets: DatasetInfo[];
|
|
129
|
+
disabledDatasets: string[];
|
|
130
|
+
currentlyQueried: string[];
|
|
131
|
+
missingBillable: string[];
|
|
132
|
+
notQueried: string[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Parse command line arguments
|
|
136
|
+
function parseArgs(): { json: boolean; compare: boolean } {
|
|
137
|
+
const args = process.argv.slice(2);
|
|
138
|
+
return {
|
|
139
|
+
json: args.includes('--json'),
|
|
140
|
+
compare: args.includes('--compare'),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Make GraphQL request
|
|
145
|
+
async function graphqlRequest<T>(
|
|
146
|
+
query: string,
|
|
147
|
+
variables: Record<string, unknown> = {}
|
|
148
|
+
): Promise<T> {
|
|
149
|
+
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
150
|
+
if (!token) {
|
|
151
|
+
throw new Error('CLOUDFLARE_API_TOKEN environment variable is required');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const response = await fetch(GRAPHQL_ENDPOINT, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': 'application/json',
|
|
158
|
+
Authorization: `Bearer ${token}`,
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({ query, variables }),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
const text = await response.text();
|
|
165
|
+
throw new Error(`GraphQL request failed: ${response.status} ${text}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = (await response.json()) as { data?: T; errors?: unknown[] };
|
|
169
|
+
|
|
170
|
+
if (result.errors) {
|
|
171
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result.data as T;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Query via GraphQL introspection to discover available datasets
|
|
178
|
+
async function discoverAccountDatasets(accountId: string): Promise<DatasetInfo[]> {
|
|
179
|
+
const introspectionQuery = `
|
|
180
|
+
query IntrospectAccountFields {
|
|
181
|
+
__type(name: "AccountResolver") {
|
|
182
|
+
fields {
|
|
183
|
+
name
|
|
184
|
+
description
|
|
185
|
+
type {
|
|
186
|
+
name
|
|
187
|
+
kind
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const introspectionResult = await graphqlRequest<{
|
|
196
|
+
__type: {
|
|
197
|
+
fields: Array<{
|
|
198
|
+
name: string;
|
|
199
|
+
description: string | null;
|
|
200
|
+
type: { name: string | null; kind: string };
|
|
201
|
+
}>;
|
|
202
|
+
} | null;
|
|
203
|
+
}>(introspectionQuery);
|
|
204
|
+
|
|
205
|
+
if (!introspectionResult.__type) {
|
|
206
|
+
console.log('Could not introspect AccountResolver, trying settings approach...');
|
|
207
|
+
return await discoverDatasetsViaSettings(accountId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const datasets: DatasetInfo[] = [];
|
|
211
|
+
for (const field of introspectionResult.__type.fields) {
|
|
212
|
+
if (
|
|
213
|
+
field.name.includes('Adaptive') ||
|
|
214
|
+
field.name.includes('Analytics') ||
|
|
215
|
+
field.name.includes('Groups') ||
|
|
216
|
+
field.name.includes('Metrics')
|
|
217
|
+
) {
|
|
218
|
+
datasets.push({ name: field.name, enabled: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return datasets;
|
|
223
|
+
} catch {
|
|
224
|
+
console.log('Introspection failed, trying Settings node approach...');
|
|
225
|
+
return await discoverDatasetsViaSettings(accountId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Alternative: Query settings node for dataset availability
|
|
230
|
+
async function discoverDatasetsViaSettings(accountId: string): Promise<DatasetInfo[]> {
|
|
231
|
+
const settingsQuery = `
|
|
232
|
+
query AccountDatasetSettings($accountTag: String!) {
|
|
233
|
+
viewer {
|
|
234
|
+
accounts(filter: { accountTag: $accountTag }) {
|
|
235
|
+
settings {
|
|
236
|
+
workersInvocationsAdaptive { enabled maxPageSize }
|
|
237
|
+
d1AnalyticsAdaptiveGroups { enabled maxPageSize }
|
|
238
|
+
d1StorageAdaptiveGroups { enabled maxPageSize }
|
|
239
|
+
kvOperationsAdaptiveGroups { enabled maxPageSize }
|
|
240
|
+
kvStorageAdaptiveGroups { enabled maxPageSize }
|
|
241
|
+
r2OperationsAdaptiveGroups { enabled maxPageSize }
|
|
242
|
+
r2StorageAdaptiveGroups { enabled maxPageSize }
|
|
243
|
+
durableObjectsInvocationsAdaptiveGroups { enabled maxPageSize }
|
|
244
|
+
durableObjectsPeriodicGroups { enabled maxPageSize }
|
|
245
|
+
durableObjectsStorageGroups { enabled maxPageSize }
|
|
246
|
+
vectorizeV2QueriesAdaptiveGroups { enabled maxPageSize }
|
|
247
|
+
vectorizeV2StorageAdaptiveGroups { enabled maxPageSize }
|
|
248
|
+
queueMessageOperationsAdaptiveGroups { enabled maxPageSize }
|
|
249
|
+
queueConsumerMetricsAdaptiveGroups { enabled maxPageSize }
|
|
250
|
+
workflowsAdaptiveGroups { enabled maxPageSize }
|
|
251
|
+
aiInferenceAdaptive { enabled maxPageSize }
|
|
252
|
+
httpRequestsAdaptiveGroups { enabled maxPageSize }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const settingsResult = await graphqlRequest<{
|
|
261
|
+
viewer: {
|
|
262
|
+
accounts: Array<{
|
|
263
|
+
settings: Record<string, { enabled: boolean; maxPageSize: number } | null>;
|
|
264
|
+
}>;
|
|
265
|
+
};
|
|
266
|
+
}>(settingsQuery, { accountTag: accountId });
|
|
267
|
+
|
|
268
|
+
const settings = settingsResult.viewer?.accounts?.[0]?.settings;
|
|
269
|
+
if (!settings) {
|
|
270
|
+
console.log('No settings returned, falling back to probe approach...');
|
|
271
|
+
return await probeDatasets(accountId);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const datasets: DatasetInfo[] = [];
|
|
275
|
+
for (const [name, info] of Object.entries(settings)) {
|
|
276
|
+
if (info) {
|
|
277
|
+
datasets.push({ name, enabled: info.enabled, maxPageSize: info.maxPageSize });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return datasets;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.log('Settings query failed, falling back to probe approach...');
|
|
284
|
+
console.log('Error:', error);
|
|
285
|
+
return await probeDatasets(accountId);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Fallback: Probe individual datasets to check availability
|
|
290
|
+
async function probeDatasets(accountId: string): Promise<DatasetInfo[]> {
|
|
291
|
+
const datasetsToProbe = BILLABLE_DATASETS;
|
|
292
|
+
const results: DatasetInfo[] = [];
|
|
293
|
+
const now = new Date();
|
|
294
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
295
|
+
|
|
296
|
+
console.log(`Probing ${datasetsToProbe.length} datasets...`);
|
|
297
|
+
|
|
298
|
+
for (const dataset of datasetsToProbe) {
|
|
299
|
+
const probeQuery = `
|
|
300
|
+
query ProbeDataset($accountTag: String!, $limit: Int!) {
|
|
301
|
+
viewer {
|
|
302
|
+
accounts(filter: { accountTag: $accountTag }) {
|
|
303
|
+
${dataset}(limit: $limit, filter: { datetime_geq: "${yesterday.toISOString()}", datetime_leq: "${now.toISOString()}" }) {
|
|
304
|
+
dimensions {
|
|
305
|
+
datetime
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
await graphqlRequest(probeQuery, { accountTag: accountId, limit: 1 });
|
|
315
|
+
results.push({ name: dataset, enabled: true });
|
|
316
|
+
process.stdout.write('.');
|
|
317
|
+
} catch (error) {
|
|
318
|
+
const errorStr = String(error);
|
|
319
|
+
if (errorStr.includes('Cannot query field') || errorStr.includes('Unknown field')) {
|
|
320
|
+
results.push({ name: dataset, enabled: false });
|
|
321
|
+
process.stdout.write('x');
|
|
322
|
+
} else if (errorStr.includes('not enabled') || errorStr.includes('not available')) {
|
|
323
|
+
results.push({ name: dataset, enabled: false });
|
|
324
|
+
process.stdout.write('-');
|
|
325
|
+
} else {
|
|
326
|
+
results.push({ name: dataset, enabled: true });
|
|
327
|
+
process.stdout.write('?');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Small delay between probes to avoid rate limiting
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log('');
|
|
336
|
+
return results;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Main discovery function
|
|
340
|
+
async function discoverDatasets(): Promise<DiscoveryResult> {
|
|
341
|
+
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
342
|
+
if (!accountId) {
|
|
343
|
+
throw new Error('CLOUDFLARE_ACCOUNT_ID environment variable is required');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('='.repeat(60));
|
|
347
|
+
console.log('Cloudflare GraphQL Dataset Discovery');
|
|
348
|
+
console.log('='.repeat(60));
|
|
349
|
+
console.log(`Account ID: ${accountId}`);
|
|
350
|
+
console.log(`Timestamp: ${new Date().toISOString()}`);
|
|
351
|
+
console.log('');
|
|
352
|
+
|
|
353
|
+
console.log('Discovering available datasets...');
|
|
354
|
+
const datasets = await discoverAccountDatasets(accountId);
|
|
355
|
+
|
|
356
|
+
const enabledDatasets = datasets.filter((d) => d.enabled);
|
|
357
|
+
const disabledDatasets = datasets.filter((d) => !d.enabled).map((d) => d.name);
|
|
358
|
+
|
|
359
|
+
const enabledNames = new Set(enabledDatasets.map((d) => d.name));
|
|
360
|
+
const queriedSet = new Set(CURRENTLY_QUERIED_DATASETS);
|
|
361
|
+
|
|
362
|
+
const missingBillable = BILLABLE_DATASETS.filter(
|
|
363
|
+
(d) => enabledNames.has(d) && !queriedSet.has(d)
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const notQueried = enabledDatasets.filter((d) => !queriedSet.has(d.name)).map((d) => d.name);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
timestamp: new Date().toISOString(),
|
|
370
|
+
accountId,
|
|
371
|
+
totalDatasetsFound: datasets.length,
|
|
372
|
+
enabledDatasets,
|
|
373
|
+
disabledDatasets,
|
|
374
|
+
currentlyQueried: CURRENTLY_QUERIED_DATASETS,
|
|
375
|
+
missingBillable,
|
|
376
|
+
notQueried,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Display results
|
|
381
|
+
function displayResults(result: DiscoveryResult, jsonOutput: boolean, compareMode: boolean): void {
|
|
382
|
+
if (jsonOutput) {
|
|
383
|
+
console.log(JSON.stringify(result, null, 2));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log('='.repeat(60));
|
|
389
|
+
console.log('DISCOVERY RESULTS');
|
|
390
|
+
console.log('='.repeat(60));
|
|
391
|
+
|
|
392
|
+
console.log(`\nTotal Datasets Found: ${result.totalDatasetsFound}`);
|
|
393
|
+
console.log(`Enabled: ${result.enabledDatasets.length}`);
|
|
394
|
+
console.log(`Disabled/Unavailable: ${result.disabledDatasets.length}`);
|
|
395
|
+
|
|
396
|
+
console.log('\n--- ENABLED DATASETS ---');
|
|
397
|
+
for (const dataset of result.enabledDatasets) {
|
|
398
|
+
const queried = CURRENTLY_QUERIED_DATASETS.includes(dataset.name)
|
|
399
|
+
? '[QUERIED]'
|
|
400
|
+
: '[NOT QUERIED]';
|
|
401
|
+
const billable = BILLABLE_DATASETS.includes(dataset.name) ? '(billable)' : '';
|
|
402
|
+
console.log(` ${queried} ${dataset.name} ${billable}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (result.disabledDatasets.length > 0) {
|
|
406
|
+
console.log('\n--- DISABLED/UNAVAILABLE DATASETS ---');
|
|
407
|
+
for (const name of result.disabledDatasets) {
|
|
408
|
+
console.log(` [-] ${name}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (compareMode || result.missingBillable.length > 0) {
|
|
413
|
+
console.log('\n' + '='.repeat(60));
|
|
414
|
+
console.log('GAP ANALYSIS');
|
|
415
|
+
console.log('='.repeat(60));
|
|
416
|
+
|
|
417
|
+
if (result.missingBillable.length > 0) {
|
|
418
|
+
console.log('\n*** BILLABLE DATASETS NOT BEING QUERIED ***');
|
|
419
|
+
for (const name of result.missingBillable) {
|
|
420
|
+
console.log(` [!] ${name}`);
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
console.log('\nNo billable dataset gaps found.');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
console.log(`\n--- Currently Queried (${CURRENTLY_QUERIED_DATASETS.length}) ---`);
|
|
427
|
+
for (const name of CURRENTLY_QUERIED_DATASETS) {
|
|
428
|
+
const enabled = result.enabledDatasets.some((d) => d.name === name);
|
|
429
|
+
console.log(` ${enabled ? '[OK]' : '[??]'} ${name}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
console.log('\n' + '='.repeat(60));
|
|
434
|
+
console.log('RECOMMENDATIONS');
|
|
435
|
+
console.log('='.repeat(60));
|
|
436
|
+
|
|
437
|
+
if (result.missingBillable.length > 0) {
|
|
438
|
+
console.log('\nAction Required: Add queries for these billable datasets:');
|
|
439
|
+
for (const name of result.missingBillable) {
|
|
440
|
+
console.log(` - ${name}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const unusedEnabled = result.notQueried.filter(
|
|
445
|
+
(name) =>
|
|
446
|
+
!name.includes('firewall') &&
|
|
447
|
+
!name.includes('Firewall') &&
|
|
448
|
+
!name.includes('healthCheck') &&
|
|
449
|
+
!name.includes('loadBalancing') &&
|
|
450
|
+
!name.includes('network')
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
if (unusedEnabled.length > 0) {
|
|
454
|
+
console.log('\nOther enabled datasets not being queried (may be relevant):');
|
|
455
|
+
for (const name of unusedEnabled.slice(0, 10)) {
|
|
456
|
+
console.log(` - ${name}`);
|
|
457
|
+
}
|
|
458
|
+
if (unusedEnabled.length > 10) {
|
|
459
|
+
console.log(` ... and ${unusedEnabled.length - 10} more`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Main
|
|
465
|
+
async function main(): Promise<void> {
|
|
466
|
+
const { json, compare } = parseArgs();
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
const result = await discoverDatasets();
|
|
470
|
+
displayResults(result, json, compare);
|
|
471
|
+
|
|
472
|
+
// Exit with non-zero if there are missing billable datasets
|
|
473
|
+
if (result.missingBillable.length > 0) {
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
console.error('Error:', error);
|
|
478
|
+
process.exit(2);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
main();
|