feat: BlackRoad GraphQL Gateway - unified API for 40+ products
- GraphQL Yoga server on Cloudflare Workers - Complete schema with User, Agent, Deployment, Device, Product types - Queries for infrastructure stats, agents, deployments, devices - Mutations for deployments, user management, agent scaling - Subscriptions for real-time updates - GraphiQL playground with example queries - Deployed: https://blackroad-graphql-gateway.amundsonalexa.workers.dev
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
.wrangler/
|
||||||
|
.dev.vars
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
109
README.md
Normal file
109
README.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# BlackRoad GraphQL Gateway
|
||||||
|
|
||||||
|
Unified GraphQL API endpoint for the entire BlackRoad ecosystem - 40+ products, 15 GitHub orgs, 1,085 repos.
|
||||||
|
|
||||||
|
## Live Endpoints
|
||||||
|
|
||||||
|
- **GraphQL**: https://blackroad-graphql-gateway.amundsonalexa.workers.dev/graphql
|
||||||
|
- **GraphiQL**: https://blackroad-graphql-gateway.amundsonalexa.workers.dev/graphql (GET)
|
||||||
|
- **Health**: https://blackroad-graphql-gateway.amundsonalexa.workers.dev/health
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Queries
|
||||||
|
- `me` - Current authenticated user
|
||||||
|
- `users` - List all users
|
||||||
|
- `agents` - List AI agents (filter by type, status)
|
||||||
|
- `deployments` - List deployments (filter by service, status)
|
||||||
|
- `devices` - Device fleet inventory
|
||||||
|
- `products` - All BlackRoad products
|
||||||
|
- `errors` - System errors and alerts
|
||||||
|
- `infrastructureStats` - GitHub orgs, repos, devices, agents
|
||||||
|
- `usage` - API calls, compute hours, storage
|
||||||
|
|
||||||
|
### Mutations
|
||||||
|
- `createUser` / `updateUser` / `deleteUser` - User management
|
||||||
|
- `deploy` / `cancelDeployment` / `rollback` - Deployment operations
|
||||||
|
- `scaleAgents` / `restartAgent` - Agent management
|
||||||
|
- `sendNotification` - Multi-channel notifications
|
||||||
|
- `updateConfig` - Service configuration
|
||||||
|
|
||||||
|
### Subscriptions (Real-time)
|
||||||
|
- `deploymentUpdated` - Deployment status changes
|
||||||
|
- `agentStatusChanged` - Agent health updates
|
||||||
|
- `errorOccurred` - New errors/alerts
|
||||||
|
- `usageThresholdExceeded` - Usage alerts
|
||||||
|
|
||||||
|
## Example Queries
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
# Infrastructure Overview
|
||||||
|
query {
|
||||||
|
infrastructureStats {
|
||||||
|
githubOrgs
|
||||||
|
repositories
|
||||||
|
cloudflarePages
|
||||||
|
devices
|
||||||
|
totalAiTops
|
||||||
|
activeAgents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# List Online Agents
|
||||||
|
query {
|
||||||
|
agents(status: ONLINE, limit: 10) {
|
||||||
|
name
|
||||||
|
type
|
||||||
|
tasksCompleted
|
||||||
|
uptimePercent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy a Service
|
||||||
|
mutation {
|
||||||
|
deploy(input: {
|
||||||
|
service: "blackroad-dashboard"
|
||||||
|
environment: PRODUCTION
|
||||||
|
version: "v2.5.0"
|
||||||
|
}) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
startedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **GraphQL Yoga** - GraphQL server
|
||||||
|
- **Cloudflare Workers** - Edge deployment
|
||||||
|
- **TypeScript** - Type safety
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Deploy to Cloudflare
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
| Resource | Count |
|
||||||
|
|----------|-------|
|
||||||
|
| GitHub Orgs | 15 |
|
||||||
|
| Repositories | 1,085 |
|
||||||
|
| Cloudflare Pages | 205 |
|
||||||
|
| KV Namespaces | 35 |
|
||||||
|
| Devices | 8 |
|
||||||
|
| Total AI TOPS | 52 |
|
||||||
|
| Active Agents | 314 |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Proprietary - BlackRoad OS, Inc.
|
||||||
1941
package-lock.json
generated
Normal file
1941
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@blackroad/graphql-gateway",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "BlackRoad Unified GraphQL API Gateway - Single endpoint for all products",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "wrangler dev",
|
||||||
|
"deploy": "wrangler deploy",
|
||||||
|
"generate": "graphql-codegen"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"blackroad",
|
||||||
|
"graphql",
|
||||||
|
"api",
|
||||||
|
"gateway",
|
||||||
|
"cloudflare-workers"
|
||||||
|
],
|
||||||
|
"author": "BlackRoad OS, Inc.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@graphql-tools/schema": "^10.0.0",
|
||||||
|
"graphql": "^16.8.0",
|
||||||
|
"graphql-yoga": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20240117.0",
|
||||||
|
"typescript": "^5.3.0",
|
||||||
|
"wrangler": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
304
src/index.ts
Normal file
304
src/index.ts
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
/**
|
||||||
|
* BlackRoad GraphQL Gateway
|
||||||
|
* Unified API endpoint for all 40+ BlackRoad products
|
||||||
|
*
|
||||||
|
* Deployed on Cloudflare Workers with GraphQL Yoga
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createSchema, createYoga } from 'graphql-yoga';
|
||||||
|
import { typeDefs } from './schema';
|
||||||
|
import { resolvers } from './resolvers';
|
||||||
|
|
||||||
|
// Create the GraphQL schema
|
||||||
|
const schema = createSchema({
|
||||||
|
typeDefs,
|
||||||
|
resolvers,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Yoga instance with Cloudflare Workers adapter
|
||||||
|
const yoga = createYoga({
|
||||||
|
schema,
|
||||||
|
graphqlEndpoint: '/graphql',
|
||||||
|
landingPage: false,
|
||||||
|
cors: {
|
||||||
|
origin: [
|
||||||
|
'https://blackroad.io',
|
||||||
|
'https://dashboard.blackroad.io',
|
||||||
|
'https://api.blackroad.io',
|
||||||
|
'http://localhost:3000',
|
||||||
|
'http://localhost:5173',
|
||||||
|
],
|
||||||
|
credentials: true,
|
||||||
|
methods: ['GET', 'POST', 'OPTIONS'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
|
||||||
|
},
|
||||||
|
graphiql: {
|
||||||
|
title: 'BlackRoad GraphQL Gateway',
|
||||||
|
defaultQuery: `# Welcome to BlackRoad GraphQL Gateway
|
||||||
|
# Unified API for the entire BlackRoad ecosystem
|
||||||
|
|
||||||
|
# Get infrastructure overview
|
||||||
|
query InfrastructureOverview {
|
||||||
|
infrastructureStats {
|
||||||
|
githubOrgs
|
||||||
|
repositories
|
||||||
|
cloudflarePages
|
||||||
|
kvNamespaces
|
||||||
|
devices
|
||||||
|
totalAiTops
|
||||||
|
activeAgents
|
||||||
|
totalAgents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# List all agents
|
||||||
|
query AllAgents {
|
||||||
|
agents {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
type
|
||||||
|
status
|
||||||
|
tasksCompleted
|
||||||
|
uptimePercent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current user
|
||||||
|
query CurrentUser {
|
||||||
|
me {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
name
|
||||||
|
role
|
||||||
|
organization {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# List products
|
||||||
|
query Products {
|
||||||
|
products {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
status
|
||||||
|
url
|
||||||
|
metrics {
|
||||||
|
requestsToday
|
||||||
|
uptime
|
||||||
|
errorRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# List devices in the fleet
|
||||||
|
query DeviceFleet {
|
||||||
|
devices {
|
||||||
|
name
|
||||||
|
hostname
|
||||||
|
status
|
||||||
|
hardware
|
||||||
|
role
|
||||||
|
aiCapability
|
||||||
|
tailscaleIp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Landing page HTML
|
||||||
|
const landingPageHTML = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>BlackRoad GraphQL Gateway</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 34px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 610px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 55px;
|
||||||
|
background: linear-gradient(135deg, #F5A623 0%, #FF1D6C 38.2%, #9C27B0 61.8%, #2979FF 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 21px;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
font-size: 21px;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 34px;
|
||||||
|
}
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 21px;
|
||||||
|
margin-bottom: 34px;
|
||||||
|
}
|
||||||
|
.stat {
|
||||||
|
background: #111;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 13px;
|
||||||
|
padding: 21px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #FF1D6C;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.endpoints {
|
||||||
|
text-align: left;
|
||||||
|
background: #111;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 13px;
|
||||||
|
padding: 21px;
|
||||||
|
margin-bottom: 34px;
|
||||||
|
}
|
||||||
|
.endpoint {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 13px 0;
|
||||||
|
border-bottom: 1px solid #222;
|
||||||
|
}
|
||||||
|
.endpoint:last-child { border-bottom: none; }
|
||||||
|
.endpoint-method {
|
||||||
|
background: #2979FF;
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.endpoint-path {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #F5A623;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #FF1D6C;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.cta {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #FF1D6C 0%, #9C27B0 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 13px 34px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.cta:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>BlackRoad GraphQL</h1>
|
||||||
|
<p class="subtitle">Unified API Gateway for 40+ Products</p>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-value">15</div>
|
||||||
|
<div class="stat-label">GitHub Orgs</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-value">1,085</div>
|
||||||
|
<div class="stat-label">Repositories</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-value">314</div>
|
||||||
|
<div class="stat-label">Active Agents</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoints">
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="endpoint-method">POST</span>
|
||||||
|
<span class="endpoint-path">/graphql</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="endpoint-method">GET</span>
|
||||||
|
<span class="endpoint-path">/graphql (GraphiQL)</span>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="endpoint-method">GET</span>
|
||||||
|
<span class="endpoint-path">/health</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/graphql" class="cta">Open GraphiQL Playground</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
// Health check response
|
||||||
|
const healthResponse = {
|
||||||
|
status: 'healthy',
|
||||||
|
service: 'blackroad-graphql-gateway',
|
||||||
|
version: '1.0.0',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
infrastructure: {
|
||||||
|
githubOrgs: 15,
|
||||||
|
repositories: 1085,
|
||||||
|
activeAgents: 314,
|
||||||
|
devices: 8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main fetch handler
|
||||||
|
export default {
|
||||||
|
async fetch(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response> {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// Health check endpoint
|
||||||
|
if (url.pathname === '/health') {
|
||||||
|
return new Response(JSON.stringify({ ...healthResponse, timestamp: new Date().toISOString() }), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Landing page
|
||||||
|
if (url.pathname === '/' && request.method === 'GET') {
|
||||||
|
return new Response(landingPageHTML, {
|
||||||
|
headers: { 'Content-Type': 'text/html' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphQL endpoint
|
||||||
|
if (url.pathname === '/graphql') {
|
||||||
|
return yoga.fetch(request, env, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404 for other routes
|
||||||
|
return new Response(JSON.stringify({ error: 'Not found', endpoints: ['/graphql', '/health', '/'] }), {
|
||||||
|
status: 404,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
259
src/resolvers.ts
Normal file
259
src/resolvers.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/**
|
||||||
|
* BlackRoad GraphQL Resolvers
|
||||||
|
* Mock data representing real infrastructure
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Mock data representing BlackRoad infrastructure
|
||||||
|
const mockUsers = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
email: 'alexa@blackroad.io',
|
||||||
|
name: 'Alexa Amundson',
|
||||||
|
role: 'ADMIN',
|
||||||
|
createdAt: '2024-01-01T00:00:00Z',
|
||||||
|
updatedAt: '2026-02-14T00:00:00Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockOrganizations = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'BlackRoad OS',
|
||||||
|
slug: 'blackroad-os',
|
||||||
|
createdAt: '2024-01-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockProducts = [
|
||||||
|
{ id: '1', name: 'BlackRoad Dashboard', slug: 'dashboard', description: 'Unified monitoring dashboard', status: 'active', url: 'https://dashboard.blackroad.io' },
|
||||||
|
{ id: '2', name: 'BlackRoad API', slug: 'api', description: 'Core API gateway', status: 'active', url: 'https://api.blackroad.io' },
|
||||||
|
{ id: '3', name: 'BlackRoad Codex', slug: 'codex', description: 'Component search engine', status: 'active', url: 'https://codex.blackroad.io' },
|
||||||
|
{ id: '4', name: 'CECE OS', slug: 'cece', description: 'Sovereign AI operating system', status: 'active', url: 'https://cece.blackroad.io' },
|
||||||
|
{ id: '5', name: 'BlackRoad Prism', slug: 'prism', description: 'GitHub insights console', status: 'active', url: 'https://prism.blackroad.io' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockAgents = [
|
||||||
|
{ id: '1', name: 'zeus-orchestrator', type: 'INFRASTRUCTURE', status: 'ONLINE', tasksCompleted: 1247, uptimePercent: 99.9, lastActiveAt: new Date().toISOString(), createdAt: '2024-06-01T00:00:00Z' },
|
||||||
|
{ id: '2', name: 'prometheus-monitor', type: 'ANALYTICS', status: 'ONLINE', tasksCompleted: 892, uptimePercent: 99.7, lastActiveAt: new Date().toISOString(), createdAt: '2024-06-15T00:00:00Z' },
|
||||||
|
{ id: '3', name: 'athena-reviewer', type: 'CODE_REVIEW', status: 'ONLINE', tasksCompleted: 2341, uptimePercent: 99.5, lastActiveAt: new Date().toISOString(), createdAt: '2024-07-01T00:00:00Z' },
|
||||||
|
{ id: '4', name: 'ares-security', type: 'SECURITY', status: 'BUSY', tasksCompleted: 567, uptimePercent: 99.8, lastActiveAt: new Date().toISOString(), createdAt: '2024-07-15T00:00:00Z' },
|
||||||
|
{ id: '5', name: 'hermes-deployer', type: 'INFRASTRUCTURE', status: 'ONLINE', tasksCompleted: 1893, uptimePercent: 99.6, lastActiveAt: new Date().toISOString(), createdAt: '2024-08-01T00:00:00Z' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockDeployments = [
|
||||||
|
{ id: '1', service: 'blackroad-dashboard', version: 'v2.4.1', status: 'SUCCESS', environment: 'PRODUCTION', startedAt: '2026-02-14T10:00:00Z', completedAt: '2026-02-14T10:02:30Z', durationSeconds: 150, url: 'https://dashboard.blackroad.io' },
|
||||||
|
{ id: '2', service: 'blackroad-api', version: 'v3.1.0', status: 'SUCCESS', environment: 'PRODUCTION', startedAt: '2026-02-14T09:30:00Z', completedAt: '2026-02-14T09:31:45Z', durationSeconds: 105, url: 'https://api.blackroad.io' },
|
||||||
|
{ id: '3', service: 'blackroad-codex', version: 'v1.8.2', status: 'IN_PROGRESS', environment: 'STAGING', startedAt: '2026-02-14T11:00:00Z', completedAt: null, durationSeconds: null, url: null },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockDevices = [
|
||||||
|
{ id: '1', name: 'cecilia', hostname: 'cecilia.local', ipAddress: '192.168.4.89', tailscaleIp: '100.72.180.98', status: 'online', hardware: 'Pi 5 + Hailo-8', role: 'Primary AI Agent', aiCapability: 26.0, lastSeen: new Date().toISOString() },
|
||||||
|
{ id: '2', name: 'lucidia', hostname: 'lucidia.local', ipAddress: '192.168.4.81', tailscaleIp: '100.83.149.86', status: 'online', hardware: 'Pi 5 + Pironman', role: 'AI Inference', aiCapability: 4.0, lastSeen: new Date().toISOString() },
|
||||||
|
{ id: '3', name: 'octavia', hostname: 'octavia.local', ipAddress: '192.168.4.38', tailscaleIp: '100.66.235.47', status: 'online', hardware: 'Pi 5', role: 'Multi-arm Processing', aiCapability: 4.0, lastSeen: new Date().toISOString() },
|
||||||
|
{ id: '4', name: 'alice', hostname: 'alice.local', ipAddress: '192.168.4.49', tailscaleIp: '100.77.210.18', status: 'online', hardware: 'Pi 4', role: 'Worker Node', aiCapability: 2.0, lastSeen: new Date().toISOString() },
|
||||||
|
{ id: '5', name: 'aria', hostname: 'aria.local', ipAddress: '192.168.4.82', tailscaleIp: '100.109.14.17', status: 'online', hardware: 'Pi 5', role: 'Harmony Protocols', aiCapability: 4.0, lastSeen: new Date().toISOString() },
|
||||||
|
{ id: '6', name: 'shellfish', hostname: 'shellfish', ipAddress: '174.138.44.45', tailscaleIp: '100.94.33.37', status: 'online', hardware: 'DigitalOcean Droplet', role: 'Edge Compute', aiCapability: 8.0, lastSeen: new Date().toISOString() },
|
||||||
|
{ id: '7', name: 'codex-infinity', hostname: 'codex-infinity', ipAddress: '159.65.43.12', tailscaleIp: '100.108.132.8', status: 'online', hardware: 'DigitalOcean Droplet', role: 'Cloud Oracle', aiCapability: 4.0, lastSeen: new Date().toISOString() },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockErrors = [
|
||||||
|
{ id: '1', service: 'blackroad-api', severity: 'WARNING', message: 'High latency detected', code: 'LATENCY_001', occurredAt: '2026-02-14T08:30:00Z', resolved: true, resolvedAt: '2026-02-14T08:35:00Z', affectedUsers: 12 },
|
||||||
|
{ id: '2', service: 'blackroad-codex', severity: 'INFO', message: 'Index rebuild scheduled', code: 'INDEX_001', occurredAt: '2026-02-14T06:00:00Z', resolved: true, resolvedAt: '2026-02-14T06:15:00Z', affectedUsers: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const infrastructureStats = {
|
||||||
|
githubOrgs: 15,
|
||||||
|
repositories: 1085,
|
||||||
|
cloudflarePages: 205,
|
||||||
|
kvNamespaces: 35,
|
||||||
|
railwayServices: 2,
|
||||||
|
devices: 8,
|
||||||
|
totalAiTops: 52.0,
|
||||||
|
activeAgents: 314,
|
||||||
|
totalAgents: 400,
|
||||||
|
};
|
||||||
|
|
||||||
|
const usage = {
|
||||||
|
apiCalls: 1247893,
|
||||||
|
computeHours: 2847.5,
|
||||||
|
storageGb: 156.3,
|
||||||
|
bandwidthGb: 892.7,
|
||||||
|
period: '2026-02',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resolver implementations
|
||||||
|
export const resolvers = {
|
||||||
|
Query: {
|
||||||
|
// Users
|
||||||
|
me: () => mockUsers[0],
|
||||||
|
user: (_: unknown, { id }: { id: string }) => mockUsers.find(u => u.id === id),
|
||||||
|
users: (_: unknown, { limit = 100, offset = 0 }: { limit?: number; offset?: number }) =>
|
||||||
|
mockUsers.slice(offset, offset + limit),
|
||||||
|
|
||||||
|
// Organizations
|
||||||
|
organization: (_: unknown, { id }: { id: string }) => mockOrganizations.find(o => o.id === id),
|
||||||
|
organizations: () => mockOrganizations,
|
||||||
|
|
||||||
|
// Products
|
||||||
|
product: (_: unknown, { slug }: { slug: string }) => mockProducts.find(p => p.slug === slug),
|
||||||
|
products: () => mockProducts,
|
||||||
|
|
||||||
|
// Agents
|
||||||
|
agent: (_: unknown, { id }: { id: string }) => mockAgents.find(a => a.id === id),
|
||||||
|
agents: (_: unknown, { type, status, limit = 100 }: { type?: string; status?: string; limit?: number }) => {
|
||||||
|
let filtered = mockAgents;
|
||||||
|
if (type) filtered = filtered.filter(a => a.type === type);
|
||||||
|
if (status) filtered = filtered.filter(a => a.status === status);
|
||||||
|
return filtered.slice(0, limit);
|
||||||
|
},
|
||||||
|
agentStats: () => ({
|
||||||
|
tasksToday: 847,
|
||||||
|
tasksThisWeek: 4293,
|
||||||
|
averageTaskDuration: 2.3,
|
||||||
|
successRate: 99.7,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Deployments
|
||||||
|
deployment: (_: unknown, { id }: { id: string }) => mockDeployments.find(d => d.id === id),
|
||||||
|
deployments: (_: unknown, { service, status, limit = 100 }: { service?: string; status?: string; limit?: number }) => {
|
||||||
|
let filtered = mockDeployments;
|
||||||
|
if (service) filtered = filtered.filter(d => d.service === service);
|
||||||
|
if (status) filtered = filtered.filter(d => d.status === status);
|
||||||
|
return filtered.slice(0, limit);
|
||||||
|
},
|
||||||
|
latestDeployment: (_: unknown, { service }: { service: string }) =>
|
||||||
|
mockDeployments.filter(d => d.service === service).sort((a, b) =>
|
||||||
|
new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()
|
||||||
|
)[0],
|
||||||
|
|
||||||
|
// Devices
|
||||||
|
device: (_: unknown, { id }: { id: string }) => mockDevices.find(d => d.id === id),
|
||||||
|
devices: () => mockDevices,
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
errors: (_: unknown, { severity, service, resolved, limit = 100 }: { severity?: string; service?: string; resolved?: boolean; limit?: number }) => {
|
||||||
|
let filtered = mockErrors;
|
||||||
|
if (severity) filtered = filtered.filter(e => e.severity === severity);
|
||||||
|
if (service) filtered = filtered.filter(e => e.service === service);
|
||||||
|
if (resolved !== undefined) filtered = filtered.filter(e => e.resolved === resolved);
|
||||||
|
return filtered.slice(0, limit);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Infrastructure
|
||||||
|
infrastructureStats: () => infrastructureStats,
|
||||||
|
usage: () => usage,
|
||||||
|
},
|
||||||
|
|
||||||
|
Mutation: {
|
||||||
|
// Users
|
||||||
|
createUser: (_: unknown, { input }: { input: { email: string; name: string; role: string } }) => ({
|
||||||
|
id: String(mockUsers.length + 1),
|
||||||
|
...input,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}),
|
||||||
|
updateUser: (_: unknown, { id, name, role }: { id: string; name?: string; role?: string }) => {
|
||||||
|
const user = mockUsers.find(u => u.id === id);
|
||||||
|
if (!user) throw new Error('User not found');
|
||||||
|
return { ...user, name: name || user.name, role: role || user.role, updatedAt: new Date().toISOString() };
|
||||||
|
},
|
||||||
|
deleteUser: (_: unknown, { id }: { id: string }) => {
|
||||||
|
const index = mockUsers.findIndex(u => u.id === id);
|
||||||
|
return index !== -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Deployments
|
||||||
|
deploy: (_: unknown, { input }: { input: { service: string; environment: string; version?: string } }) => ({
|
||||||
|
id: String(mockDeployments.length + 1),
|
||||||
|
service: input.service,
|
||||||
|
version: input.version || 'latest',
|
||||||
|
status: 'PENDING',
|
||||||
|
environment: input.environment,
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
completedAt: null,
|
||||||
|
durationSeconds: null,
|
||||||
|
url: null,
|
||||||
|
}),
|
||||||
|
cancelDeployment: (_: unknown, { id }: { id: string }) => {
|
||||||
|
const deployment = mockDeployments.find(d => d.id === id);
|
||||||
|
if (!deployment) throw new Error('Deployment not found');
|
||||||
|
return { ...deployment, status: 'CANCELLED', completedAt: new Date().toISOString() };
|
||||||
|
},
|
||||||
|
rollback: (_: unknown, { service, toVersion }: { service: string; toVersion: string }) => ({
|
||||||
|
id: String(mockDeployments.length + 1),
|
||||||
|
service,
|
||||||
|
version: toVersion,
|
||||||
|
status: 'PENDING',
|
||||||
|
environment: 'PRODUCTION',
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
completedAt: null,
|
||||||
|
durationSeconds: null,
|
||||||
|
url: null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Agents
|
||||||
|
scaleAgents: (_: unknown, { input }: { input: { agentType: string; targetCount: number } }) => ({
|
||||||
|
id: String(mockAgents.length + 1),
|
||||||
|
name: `${input.agentType.toLowerCase()}-scaled`,
|
||||||
|
type: input.agentType,
|
||||||
|
status: 'ONLINE',
|
||||||
|
tasksCompleted: 0,
|
||||||
|
uptimePercent: 100,
|
||||||
|
lastActiveAt: new Date().toISOString(),
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
}),
|
||||||
|
restartAgent: (_: unknown, { id }: { id: string }) => {
|
||||||
|
const agent = mockAgents.find(a => a.id === id);
|
||||||
|
if (!agent) throw new Error('Agent not found');
|
||||||
|
return { ...agent, status: 'ONLINE', lastActiveAt: new Date().toISOString() };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
sendNotification: () => true,
|
||||||
|
|
||||||
|
// Config
|
||||||
|
updateConfig: () => true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Type resolvers for nested fields
|
||||||
|
User: {
|
||||||
|
organization: () => mockOrganizations[0],
|
||||||
|
products: () => mockProducts,
|
||||||
|
},
|
||||||
|
|
||||||
|
Organization: {
|
||||||
|
users: () => mockUsers,
|
||||||
|
products: () => mockProducts,
|
||||||
|
usage: () => usage,
|
||||||
|
},
|
||||||
|
|
||||||
|
Product: {
|
||||||
|
deployments: (product: { slug: string }) =>
|
||||||
|
mockDeployments.filter(d => d.service.includes(product.slug)),
|
||||||
|
metrics: () => ({
|
||||||
|
requestsToday: Math.floor(Math.random() * 100000),
|
||||||
|
requestsThisMonth: Math.floor(Math.random() * 1000000),
|
||||||
|
averageLatencyMs: Math.random() * 50 + 10,
|
||||||
|
errorRate: Math.random() * 0.5,
|
||||||
|
uptime: 99.5 + Math.random() * 0.5,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Agent: {
|
||||||
|
metrics: () => ({
|
||||||
|
tasksToday: Math.floor(Math.random() * 100),
|
||||||
|
tasksThisWeek: Math.floor(Math.random() * 500),
|
||||||
|
averageTaskDuration: Math.random() * 5,
|
||||||
|
successRate: 95 + Math.random() * 5,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Deployment: {
|
||||||
|
deployedBy: () => mockUsers[0],
|
||||||
|
logs: () => [
|
||||||
|
{ timestamp: new Date().toISOString(), level: 'INFO', message: 'Deployment started' },
|
||||||
|
{ timestamp: new Date().toISOString(), level: 'INFO', message: 'Building container' },
|
||||||
|
{ timestamp: new Date().toISOString(), level: 'INFO', message: 'Deployment complete' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
310
src/schema.ts
Normal file
310
src/schema.ts
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* BlackRoad GraphQL Schema
|
||||||
|
* Unified schema for all 40+ products
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const typeDefs = /* GraphQL */ `
|
||||||
|
scalar DateTime
|
||||||
|
scalar JSON
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Enums
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
enum AgentStatus {
|
||||||
|
ONLINE
|
||||||
|
OFFLINE
|
||||||
|
BUSY
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AgentType {
|
||||||
|
INFRASTRUCTURE
|
||||||
|
CODE_REVIEW
|
||||||
|
SECURITY
|
||||||
|
ANALYTICS
|
||||||
|
GENERAL
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DeploymentStatus {
|
||||||
|
PENDING
|
||||||
|
IN_PROGRESS
|
||||||
|
SUCCESS
|
||||||
|
FAILURE
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Environment {
|
||||||
|
PRODUCTION
|
||||||
|
STAGING
|
||||||
|
DEVELOPMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserRole {
|
||||||
|
ADMIN
|
||||||
|
DEVELOPER
|
||||||
|
VIEWER
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Severity {
|
||||||
|
INFO
|
||||||
|
WARNING
|
||||||
|
ERROR
|
||||||
|
CRITICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Types - Core
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id: ID!
|
||||||
|
email: String!
|
||||||
|
name: String!
|
||||||
|
role: UserRole!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
organization: Organization
|
||||||
|
products: [Product!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Organization {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
slug: String!
|
||||||
|
users: [User!]!
|
||||||
|
products: [Product!]!
|
||||||
|
usage: Usage!
|
||||||
|
createdAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
slug: String!
|
||||||
|
description: String
|
||||||
|
status: String!
|
||||||
|
url: String
|
||||||
|
deployments: [Deployment!]!
|
||||||
|
metrics: ProductMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Types - Infrastructure
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type Agent {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
type: AgentType!
|
||||||
|
status: AgentStatus!
|
||||||
|
tasksCompleted: Int!
|
||||||
|
uptimePercent: Float!
|
||||||
|
lastActiveAt: DateTime
|
||||||
|
createdAt: DateTime!
|
||||||
|
metrics: AgentMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentMetrics {
|
||||||
|
tasksToday: Int!
|
||||||
|
tasksThisWeek: Int!
|
||||||
|
averageTaskDuration: Float!
|
||||||
|
successRate: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deployment {
|
||||||
|
id: ID!
|
||||||
|
service: String!
|
||||||
|
version: String!
|
||||||
|
status: DeploymentStatus!
|
||||||
|
environment: Environment!
|
||||||
|
startedAt: DateTime!
|
||||||
|
completedAt: DateTime
|
||||||
|
durationSeconds: Int
|
||||||
|
deployedBy: User
|
||||||
|
url: String
|
||||||
|
logs: [DeploymentLog!]
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeploymentLog {
|
||||||
|
timestamp: DateTime!
|
||||||
|
level: String!
|
||||||
|
message: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Device {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
hostname: String!
|
||||||
|
ipAddress: String
|
||||||
|
tailscaleIp: String
|
||||||
|
status: String!
|
||||||
|
hardware: String
|
||||||
|
role: String
|
||||||
|
aiCapability: Float
|
||||||
|
lastSeen: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Types - Monitoring
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type Error {
|
||||||
|
id: ID!
|
||||||
|
service: String!
|
||||||
|
severity: Severity!
|
||||||
|
message: String!
|
||||||
|
code: String
|
||||||
|
occurredAt: DateTime!
|
||||||
|
resolved: Boolean!
|
||||||
|
resolvedAt: DateTime
|
||||||
|
affectedUsers: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Usage {
|
||||||
|
apiCalls: Int!
|
||||||
|
computeHours: Float!
|
||||||
|
storageGb: Float!
|
||||||
|
bandwidthGb: Float!
|
||||||
|
period: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductMetrics {
|
||||||
|
requestsToday: Int!
|
||||||
|
requestsThisMonth: Int!
|
||||||
|
averageLatencyMs: Float!
|
||||||
|
errorRate: Float!
|
||||||
|
uptime: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Types - Infrastructure Stats
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type InfrastructureStats {
|
||||||
|
githubOrgs: Int!
|
||||||
|
repositories: Int!
|
||||||
|
cloudflarePages: Int!
|
||||||
|
kvNamespaces: Int!
|
||||||
|
railwayServices: Int!
|
||||||
|
devices: Int!
|
||||||
|
totalAiTops: Float!
|
||||||
|
activeAgents: Int!
|
||||||
|
totalAgents: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Input Types
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
input CreateUserInput {
|
||||||
|
email: String!
|
||||||
|
name: String!
|
||||||
|
role: UserRole!
|
||||||
|
products: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeployInput {
|
||||||
|
service: String!
|
||||||
|
environment: Environment!
|
||||||
|
version: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ScaleAgentsInput {
|
||||||
|
agentType: AgentType!
|
||||||
|
targetCount: Int!
|
||||||
|
scalingPolicy: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input NotificationInput {
|
||||||
|
channel: String!
|
||||||
|
title: String!
|
||||||
|
message: String!
|
||||||
|
severity: Severity
|
||||||
|
recipients: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Queries
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
# Users
|
||||||
|
me: User
|
||||||
|
user(id: ID!): User
|
||||||
|
users(limit: Int, offset: Int): [User!]!
|
||||||
|
|
||||||
|
# Organizations
|
||||||
|
organization(id: ID!): Organization
|
||||||
|
organizations: [Organization!]!
|
||||||
|
|
||||||
|
# Products
|
||||||
|
product(slug: String!): Product
|
||||||
|
products: [Product!]!
|
||||||
|
|
||||||
|
# Agents
|
||||||
|
agent(id: ID!): Agent
|
||||||
|
agents(type: AgentType, status: AgentStatus, limit: Int): [Agent!]!
|
||||||
|
agentStats: AgentMetrics!
|
||||||
|
|
||||||
|
# Deployments
|
||||||
|
deployment(id: ID!): Deployment
|
||||||
|
deployments(service: String, status: DeploymentStatus, limit: Int): [Deployment!]!
|
||||||
|
latestDeployment(service: String!): Deployment
|
||||||
|
|
||||||
|
# Devices
|
||||||
|
device(id: ID!): Device
|
||||||
|
devices: [Device!]!
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
errors(severity: Severity, service: String, resolved: Boolean, limit: Int): [Error!]!
|
||||||
|
|
||||||
|
# Infrastructure
|
||||||
|
infrastructureStats: InfrastructureStats!
|
||||||
|
usage: Usage!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Mutations
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
# Users
|
||||||
|
createUser(input: CreateUserInput!): User!
|
||||||
|
updateUser(id: ID!, name: String, role: UserRole): User!
|
||||||
|
deleteUser(id: ID!): Boolean!
|
||||||
|
|
||||||
|
# Deployments
|
||||||
|
deploy(input: DeployInput!): Deployment!
|
||||||
|
cancelDeployment(id: ID!): Deployment!
|
||||||
|
rollback(service: String!, toVersion: String!): Deployment!
|
||||||
|
|
||||||
|
# Agents
|
||||||
|
scaleAgents(input: ScaleAgentsInput!): Agent!
|
||||||
|
restartAgent(id: ID!): Agent!
|
||||||
|
|
||||||
|
# Notifications
|
||||||
|
sendNotification(input: NotificationInput!): Boolean!
|
||||||
|
|
||||||
|
# Config
|
||||||
|
updateConfig(service: String!, key: String!, value: String!): Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Subscriptions (Real-time)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
type Subscription {
|
||||||
|
# Real-time deployment updates
|
||||||
|
deploymentUpdated(service: String): Deployment!
|
||||||
|
|
||||||
|
# Agent status changes
|
||||||
|
agentStatusChanged(type: AgentType): Agent!
|
||||||
|
|
||||||
|
# New errors
|
||||||
|
errorOccurred(severity: Severity): Error!
|
||||||
|
|
||||||
|
# Usage alerts
|
||||||
|
usageThresholdExceeded(metric: String!): Usage!
|
||||||
|
}
|
||||||
|
`;
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"types": ["@cloudflare/workers-types"],
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
10
wrangler.toml
Normal file
10
wrangler.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name = "blackroad-graphql-gateway"
|
||||||
|
main = "src/index.ts"
|
||||||
|
compatibility_date = "2024-01-01"
|
||||||
|
compatibility_flags = ["nodejs_compat"]
|
||||||
|
|
||||||
|
[vars]
|
||||||
|
ENVIRONMENT = "production"
|
||||||
|
|
||||||
|
# Secrets (set via wrangler secret put):
|
||||||
|
# API_SECRET_KEY
|
||||||
Reference in New Issue
Block a user