API Rate Limiter
Run ID: 69bca53177c0421c0bf4979c2026-03-29Development
PantheraHive BOS
BOS Dashboard

Workflow Execution Summary

The "API Rate Limiter" workflow (category: Development) has been successfully executed with the following parameters:

This output provides a comprehensive analysis and actionable recommendations for implementing and managing an API rate limit using the Fixed Window strategy with a limit of 60.


Understanding the "Fixed Window" Rate Limiting Strategy

The Fixed Window strategy is one of the simplest and most common approaches to API rate limiting.

Core Mechanism

  1. Time Window: A fixed time interval is defined (e.g., 60 seconds, 1 minute).
  2. Request Counter: For each user or client identifier, a counter is maintained within this window.
  3. Limit Enforcement:

* When a request arrives, the system checks the current time window.

* If the counter for that window is less than the rate_limit (60 in this case), the request is allowed, and the counter is incremented.

* If the counter has reached the rate_limit, subsequent requests within that same window are denied.

  1. Window Reset: At the start of a new fixed window, the counter is reset to zero, and requests can again be processed up to the limit.

Application with rate_limit: 60

Given rate_limit: 60 and a typical interpretation for this strategy, we will assume a 60 requests per minute (RPM) limit.

* A window starts at 00:00:00.

* The API client can make up to 60 requests between 00:00:00 and 00:00:59.

* If the client makes 60 requests by 00:00:15, any further requests until 00:00:59 will be denied.

* At 00:01:00, a new window begins, the counter resets, and the client can make another 60 requests.

Advantages

Disadvantages


Implementation Considerations

To effectively implement the Fixed Window strategy with a 60 requests per minute limit, consider the following technical aspects:

1. Storage Mechanism

* Key Structure: rate_limit:{client_id}:{window_start_timestamp} (e.g., rate_limit:user123:1704067200)

* Data Type: An integer counter.

* Expiration: Set an expiration on the key equal to the window duration to automatically clear old windows.

2. Concurrency and Atomicity

3. Client Identification

4. Response Headers (RFC 6585)

It is crucial to communicate rate limit status back to clients using standard HTTP headers:

5. Error Handling


Actionable Recommendations for Deployment

1. Define the Window Duration and Scope

2. Implement Rate Limiting Logic

Pseudo-code Example (using Redis)

python • 2,431 chars
import time
import redis

# Configuration
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
RATE_LIMIT = 60 # requests
WINDOW_DURATION = 60 # seconds

r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT)

def check_rate_limit(client_id):
    current_time = int(time.time())
    window_start_time = (current_time // WINDOW_DURATION) * WINDOW_DURATION # Calculate start of current fixed window

    key = f"rate_limit:{client_id}:{window_start_time}"

    # Use a Redis pipeline or Lua script for atomicity
    pipe = r.pipeline()
    pipe.incr(key)
    pipe.expire(key, WINDOW_DURATION) # Set/reset expiration for the key
    current_count, _ = pipe.execute()

    # Calculate remaining time in the current window
    time_remaining_in_window = WINDOW_DURATION - (current_time - window_start_time)
    reset_timestamp = window_start_time + WINDOW_DURATION

    if current_count > RATE_LIMIT:
        return {
            "allowed": False,
            "limit": RATE_LIMIT,
            "remaining": 0,
            "reset": reset_timestamp,
            "retry_after": time_remaining_in_window
        }
    else:
        return {
            "allowed": True,
            "limit": RATE_LIMIT,
            "remaining": RATE_LIMIT - current_count,
            "reset": reset_timestamp,
            "retry_after": 0 # Not applicable if allowed
        }

# Example usage in an API endpoint handler
def api_endpoint_handler(request):
    client_id = request.headers.get('X-API-Key') # Or extract from JWT, IP, etc.
    if not client_id:
        return {"error": "Unauthorized"}, 401

    limit_status = check_rate_limit(client_id)

    if not limit_status["allowed"]:
        response_headers = {
            'X-RateLimit-Limit': str(limit_status["limit"]),
            'X-RateLimit-Remaining': str(limit_status["remaining"]),
            'X-RateLimit-Reset': str(limit_status["reset"]),
            'Retry-After': str(limit_status["retry_after"])
        }
        return {"error": "Too Many Requests", "message": f"Try again in {limit_status['retry_after']} seconds."}, 429, response_headers
    else:
        # Process the request
        response_headers = {
            'X-RateLimit-Limit': str(limit_status["limit"]),
            'X-RateLimit-Remaining': str(limit_status["remaining"]),
            'X-RateLimit-Reset': str(limit_status["reset"])
        }
        return {"data": "Request processed successfully"}, 200, response_headers
Sandboxed live preview

3. Client Communication and Documentation

  • Recommendation: Clearly document the rate limits in your API documentation.

* Specify the rate_limit (60 requests per minute).

* Explain the Fixed Window strategy.

List and explain the X-RateLimit- and Retry-After headers.

* Provide example 429 error responses.

* Advise clients on implementing exponential backoff and jitter for retries to avoid hammering the API.

4. Monitoring and Alerting

  • Recommendation: Implement robust monitoring for rate limiting events.

* Metrics to Track:

* Total requests processed.

* Number of requests denied due to rate limiting (HTTP 429 responses).

* X-RateLimit-Remaining values (to understand client behavior).

* Latency introduced by the rate limiter itself.

* Alerting: Set up alerts for:

* High volume of 429 responses for a specific client (indicates abuse or misconfigured client).

* Unusually high global 429 responses (potential service issue or attack).

* Tools: Integrate with your existing APM (e.g., Datadog, New Relic, Prometheus/Grafana) to visualize these metrics.

5. Scalability and High Availability

  • Recommendation: Ensure your Redis (or chosen storage) instance is highly available and scalable.

* Redis Cluster/Sentinel: Use Redis Cluster for sharding and high availability, or Redis Sentinel for automatic failover.

* Cloud Managed Services: Utilize managed Redis services (e.g., AWS ElastiCache, Azure Cache for Redis, Google Cloud Memorystore) for ease of management and built-in scaling/HA.


Next Steps

  1. Pilot Implementation: Implement the Fixed Window rate limiter in a non-critical environment (e.g., staging) for a specific API endpoint or client group.
  2. Testing: Thoroughly test the implementation with various scenarios:

* Under-limit requests.

* Exactly-on-limit requests.

* Over-limit requests (to verify 429 responses and Retry-After).

* Concurrent requests from the same client.

* Requests across window boundaries to observe the "bursting" effect.

  1. Performance Benchmarking: Measure the overhead introduced by the rate limiter itself.
  2. Client Feedback: Engage with early API clients to gather feedback on the rate limiting behavior and documentation.
  3. Consider Alternative Strategies: If the "bursting" problem of Fixed Window becomes a significant concern after initial deployment and monitoring, explore more sophisticated strategies like Sliding Log or Sliding Window Counter in the next iteration. These offer smoother rate limiting but come with increased complexity and resource usage.
Step 2: projectmanager

Workflow Execution Summary

The "API Rate Limiter" workflow (category: Development) has been successfully executed with the following parameters:

  • Rate Limit: 60 requests per window
  • Strategy: Fixed Window

This output provides a comprehensive documentation of the chosen API rate limiting strategy, its implications, and actionable recommendations for implementation and management.

API Rate Limiter Overview

An API Rate Limiter is a critical component in modern application architecture designed to control the number of requests a user or client can make to an API within a given timeframe. Its primary purposes include:

  • Preventing Abuse: Mitigating DoS/DDoS attacks, brute-force attempts, and spamming.
  • Ensuring Fair Usage: Distributing API resources equitably among all consumers.
  • Maintaining Stability: Protecting backend services from overload, preventing performance degradation, and ensuring high availability.
  • Cost Management: For services that incur costs per API call, rate limiting helps control expenditure.

Chosen Strategy: Fixed Window

Detailed Explanation

The Fixed Window rate limiting strategy is one of the simplest and most widely implemented methods. It operates as follows:

  1. Time Window: A fixed time interval (e.g., 60 seconds, 1 hour) is defined.
  2. Request Counter: For each client (identified by IP address, API key, user ID, etc.), a counter is maintained within this time window.
  3. Limit Enforcement:

* When a request arrives, the counter for that client within the current window is incremented.

* If the counter exceeds the predefined rate_limit before the window ends, subsequent requests from that client are rejected until the next window begins.

* At the start of a new window, the counter is reset to zero.

Implications of rate_limit: 60 (per window)

With a rate_limit of 60, if the window is, for instance, 60 seconds, a client can make up to 60 requests within that 60-second period. If the window is 1 minute, it's 60 requests per minute.

Pros of Fixed Window:

  • Simplicity: Easy to understand and implement.
  • Predictability: Clients can easily understand their limits and when they will reset.
  • Low Overhead: Relatively efficient to manage counters.

Cons of Fixed Window (Bursting Problem):

The main drawback of the Fixed Window strategy is the "bursting problem." A client can make all 60 requests at the very beginning of a window and then make another 60 requests immediately at the start of the next window. This means that at the transition point between two windows, a client could potentially make 2 * rate_limit requests in a very short span of time (e.g., 120 requests within a few seconds), potentially overwhelming the server.

Example Scenario (Fixed Window = 60 seconds, Rate Limit = 60):

  • Time 00:00: Window 1 starts. Client makes 60 requests in quick succession (e.g., in the first 5 seconds). All are allowed.
  • Time 00:05 - 00:59: Client makes no more requests or is denied if they try.
  • Time 01:00: Window 2 starts. Client immediately makes another 60 requests in quick succession. All are allowed.
  • Result: The server processed 120 requests from a single client within a 65-second period, potentially causing a spike in load.

Implementation Recommendations

1. Identify Clients

Before implementing, decide how to identify unique clients. Common methods include:

  • IP Address: Simple, but problematic for users behind NATs or proxies (many users share one IP) or for clients with dynamic IPs.
  • API Key/Token: Most robust for authenticated users or applications.
  • User ID: For authenticated user-specific limits.
  • Session ID: For web sessions.

Recommendation: For public APIs, a combination of IP address (as a first line of defense) and API key/token (for authenticated access) is often best.

2. Choose Your Window Size

The rate_limit of 60 needs a defined window size (e.g., 60 seconds, 1 minute, 1 hour).

Recommendation: Start with a window size that aligns with your expected usage patterns. A 60-second window is common for general API usage. If the rate_limit is 60 and the window is 60 seconds, this equates to an average of 1 request per second.

3. Server-Side Logic

Implement the rate limiter as a middleware or an API gateway component.

Example Pseudocode (for a 60-second window, rate_limit=60):


function handleRequest(clientId):
    current_time_ms = getCurrentTimestamp()
    window_start_time_ms = floor(current_time_ms / (60 * 1000)) * (60 * 1000) // Start of current 60s window

    // Retrieve or initialize counter for clientId in current window
    counter = redis.get(clientId + ":" + window_start_time_ms)
    if counter is null:
        counter = 0
        redis.set(clientId + ":" + window_start_time_ms, counter, EX=60) // Set expiry for the window

    if counter < 60:
        redis.incr(clientId + ":" + window_start_time_ms)
        allowRequest()
    else:
        denyRequest(429 Too Many Requests)

Key Considerations for Server-Side Logic:

  • Data Store: Use a fast, in-memory data store like Redis for storing counters to minimize latency.
  • Atomicity: Ensure counter increments and checks are atomic operations to prevent race conditions (Redis INCR is atomic).
  • Concurrency: The rate limiter itself should be highly concurrent and non-blocking.

4. HTTP Response Headers

When a request is rate-limited, return an appropriate HTTP status code and informative headers.

Recommendation:

  • HTTP Status Code: 429 Too Many Requests
  • Standard Headers:

* X-RateLimit-Limit: The maximum number of requests allowed in the current window (e.g., 60).

* X-RateLimit-Remaining: The number of requests remaining in the current window.

* X-RateLimit-Reset: The UTC epoch timestamp when the current rate limit window resets.

* Retry-After: The number of seconds to wait before making another request (should align with X-RateLimit-Reset).

Example Response for a Denied Request:


HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1678886400  // Unix timestamp for next window start
Retry-After: 30              // Seconds until next window start

{
    "error": "Too Many Requests",
    "message": "You have exceeded your API rate limit. Please try again after 30 seconds."
}

5. Client-Side Considerations

Educate API consumers on how to handle rate limits gracefully.

Recommendation:

  • Documentation: Clearly document your rate limiting policy, including the rate_limit, window size, and how to interpret response headers.
  • Error Handling: Advise clients to catch 429 responses.
  • Backoff Strategy: Recommend implementing an exponential backoff algorithm with jitter (random delay) when encountering 429 errors, respecting the Retry-After header. This prevents clients from repeatedly hitting the limit immediately after a reset.

Monitoring and Alerting

Effective monitoring is crucial for understanding API usage patterns and proactively addressing potential issues.

Key Metrics to Track

  • Rate-Limited Requests: Number of requests denied due to rate limiting (per client, per endpoint).
  • Remaining Requests: Average or percentile of X-RateLimit-Remaining across active clients.
  • API Latency: Overall API response times, with specific attention to how they behave under high load or near rate limits.
  • Client Behavior: Identify clients consistently hitting limits or exhibiting unusual request patterns.
  • System Resource Utilization: CPU, memory, network I/O of your API servers and rate limiter service.

Alerting Strategies

  • High Rate Limit Denials: Alert when the rate of 429 responses exceeds a certain threshold (e.g., 5% of total requests over 5 minutes).
  • Specific Client Abuse: Alert if a single clientId repeatedly hits its rate limit within a short period, potentially indicating malicious activity.
  • Rate Limiter Service Health: Ensure the rate limiting service itself (e.g., Redis instance) is healthy and performing optimally.
  • Sudden Drops in Traffic: Could indicate widespread client issues or misconfiguration of the rate limiter.

Best Practices and Further Considerations

Error Handling

  • Clear Error Messages: Provide helpful messages in the 429 response body.
  • Idempotency: Consider if retries could lead to duplicate operations.
  • Graceful Degradation: For non-critical requests, consider queuing or deferring them instead of outright denying.

Scalability

  • Distributed Counters: For highly scalable APIs, ensure your rate limiting solution can manage counters across multiple instances of your API and rate limiter service (e.g., using a distributed cache like Redis Cluster).
  • Edge Caching: Implement rate limiting at the edge (e.g., API Gateway, CDN) to offload your backend servers.

Alternative Strategies (for future consideration)

While Fixed Window is simple, its "bursting problem" can be a limitation. For more sophisticated control, consider:

  • Sliding Window Log: Stores timestamps of all requests and counts requests within a dynamically calculated window. More accurate but higher memory usage.
  • Sliding Window Counter: Divides time into small fixed windows and calculates the current window's count by interpolating between the current and previous window. A good balance of accuracy and efficiency.
  • Token Bucket: A more flexible algorithm that allows for bursts up to a certain capacity while maintaining a steady average rate.

Next Steps / Actionable Items

  1. Define Window Size: Clearly establish the duration of the "Fixed Window" (e.g., 60 seconds, 1 minute).
  2. Client Identification Strategy: Finalize how unique clients will be identified for rate limiting (e.g., IP, API Key, User ID).
  3. Choose Data Store: Select a fast, in-memory data store (e.g., Redis) for storing rate limit counters.
  4. Implement Middleware/Gateway: Develop or configure the rate limiting logic in your API gateway or application middleware.
  5. Standardize HTTP Headers: Ensure your API returns 429 Too Many Requests with X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers.
  6. Update API Documentation: Publish clear guidelines for API consumers on rate limits and how to handle 429 responses.
  7. Set Up Monitoring & Alerting: Configure metrics tracking and alerts for rate limiter performance and abuse patterns.
  8. Test Thoroughly: Conduct stress tests to validate the rate limiter's effectiveness and impact on API performance.
api_rate_limiter.py
Download source file
Copy all content
Full output as text
Download ZIP
IDE-ready project ZIP
Copy share link
Permanent URL for this run
Get Embed Code
Embed this result on any website
Print / Save PDF
Use browser print dialog
\n\n\n"); var hasSrcMain=Object.keys(extracted).some(function(k){return k.indexOf("src/main")>=0;}); if(!hasSrcMain) zip.file(folder+"src/main."+ext,"import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n \n \n \n)\n"); var hasSrcApp=Object.keys(extracted).some(function(k){return k==="src/App."+ext||k==="App."+ext;}); if(!hasSrcApp) zip.file(folder+"src/App."+ext,"import React from 'react'\nimport './App.css'\n\nfunction App(){\n return(\n
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n
\n )\n}\nexport default App\n"); zip.file(folder+"src/index.css","*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;color:#1a1a2e}\n.app{min-height:100vh;display:flex;flex-direction:column}\n.app-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px}\nh1{font-size:2.5rem;font-weight:700}\n"); zip.file(folder+"src/App.css",""); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/pages/.gitkeep",""); zip.file(folder+"src/hooks/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\n## Open in IDE\nOpen the project folder in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Vue (Vite + Composition API + TypeScript) --- */ function buildVue(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vue-tsc -b && vite build",\n "preview": "vite preview"\n },\n "dependencies": {\n "vue": "^3.5.13",\n "vue-router": "^4.4.5",\n "pinia": "^2.3.0",\n "axios": "^1.7.9"\n },\n "devDependencies": {\n "@vitejs/plugin-vue": "^5.2.1",\n "typescript": "~5.7.3",\n "vite": "^6.0.5",\n "vue-tsc": "^2.2.0"\n }\n}\n'); zip.file(folder+"vite.config.ts","import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { resolve } from 'path'\n\nexport default defineConfig({\n plugins: [vue()],\n resolve: { alias: { '@': resolve(__dirname,'src') } }\n})\n"); zip.file(folder+"tsconfig.json",'{"files":[],"references":[{"path":"./tsconfig.app.json"},{"path":"./tsconfig.node.json"}]}\n'); zip.file(folder+"tsconfig.app.json",'{\n "compilerOptions":{\n "target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],\n "skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,\n "isolatedModules":true,"moduleDetection":"force","noEmit":true,"jsxImportSource":"vue",\n "strict":true,"paths":{"@/*":["./src/*"]}\n },\n "include":["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.vue"]\n}\n'); zip.file(folder+"env.d.ts","/// \n"); zip.file(folder+"index.html","\n\n\n \n \n "+slugTitle(pn)+"\n\n\n
\n \n\n\n"); var hasMain=Object.keys(extracted).some(function(k){return k==="src/main.ts"||k==="main.ts";}); if(!hasMain) zip.file(folder+"src/main.ts","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport './assets/main.css'\n\nconst app = createApp(App)\napp.use(createPinia())\napp.mount('#app')\n"); var hasApp=Object.keys(extracted).some(function(k){return k.indexOf("App.vue")>=0;}); if(!hasApp) zip.file(folder+"src/App.vue","\n\n\n\n\n"); zip.file(folder+"src/assets/main.css","*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,sans-serif;background:#fff;color:#213547}\n"); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/views/.gitkeep",""); zip.file(folder+"src/stores/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\nOpen in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Angular (v19 standalone) --- */ function buildAngular(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var sel=pn.replace(/_/g,"-"); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "scripts": {\n "ng": "ng",\n "start": "ng serve",\n "build": "ng build",\n "test": "ng test"\n },\n "dependencies": {\n "@angular/animations": "^19.0.0",\n "@angular/common": "^19.0.0",\n "@angular/compiler": "^19.0.0",\n "@angular/core": "^19.0.0",\n "@angular/forms": "^19.0.0",\n "@angular/platform-browser": "^19.0.0",\n "@angular/platform-browser-dynamic": "^19.0.0",\n "@angular/router": "^19.0.0",\n "rxjs": "~7.8.0",\n "tslib": "^2.3.0",\n "zone.js": "~0.15.0"\n },\n "devDependencies": {\n "@angular-devkit/build-angular": "^19.0.0",\n "@angular/cli": "^19.0.0",\n "@angular/compiler-cli": "^19.0.0",\n "typescript": "~5.6.0"\n }\n}\n'); zip.file(folder+"angular.json",'{\n "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\n "version": 1,\n "newProjectRoot": "projects",\n "projects": {\n "'+pn+'": {\n "projectType": "application",\n "root": "",\n "sourceRoot": "src",\n "prefix": "app",\n "architect": {\n "build": {\n "builder": "@angular-devkit/build-angular:application",\n "options": {\n "outputPath": "dist/'+pn+'",\n "index": "src/index.html",\n "browser": "src/main.ts",\n "tsConfig": "tsconfig.app.json",\n "styles": ["src/styles.css"],\n "scripts": []\n }\n },\n "serve": {"builder":"@angular-devkit/build-angular:dev-server","configurations":{"production":{"buildTarget":"'+pn+':build:production"},"development":{"buildTarget":"'+pn+':build:development"}},"defaultConfiguration":"development"}\n }\n }\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "compileOnSave": false,\n "compilerOptions": {"baseUrl":"./","outDir":"./dist/out-tsc","forceConsistentCasingInFileNames":true,"strict":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":true,"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"paths":{"@/*":["src/*"]},"skipLibCheck":true,"esModuleInterop":true,"sourceMap":true,"declaration":false,"experimentalDecorators":true,"moduleResolution":"bundler","importHelpers":true,"target":"ES2022","module":"ES2022","useDefineForClassFields":false,"lib":["ES2022","dom"]},\n "references":[{"path":"./tsconfig.app.json"}]\n}\n'); zip.file(folder+"tsconfig.app.json",'{\n "extends":"./tsconfig.json",\n "compilerOptions":{"outDir":"./dist/out-tsc","types":[]},\n "files":["src/main.ts"],\n "include":["src/**/*.d.ts"]\n}\n'); zip.file(folder+"src/index.html","\n\n\n \n "+slugTitle(pn)+"\n \n \n \n\n\n \n\n\n"); zip.file(folder+"src/main.ts","import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nbootstrapApplication(AppComponent, appConfig)\n .catch(err => console.error(err));\n"); zip.file(folder+"src/styles.css","* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; color: #111827; }\n"); var hasComp=Object.keys(extracted).some(function(k){return k.indexOf("app.component")>=0;}); if(!hasComp){ zip.file(folder+"src/app/app.component.ts","import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [RouterOutlet],\n templateUrl: './app.component.html',\n styleUrl: './app.component.css'\n})\nexport class AppComponent {\n title = '"+pn+"';\n}\n"); zip.file(folder+"src/app/app.component.html","
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n \n
\n"); zip.file(folder+"src/app/app.component.css",".app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:16px}h1{font-size:2.5rem;font-weight:700;color:#6366f1}\n"); } zip.file(folder+"src/app/app.config.ts","import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes)\n ]\n};\n"); zip.file(folder+"src/app/app.routes.ts","import { Routes } from '@angular/router';\n\nexport const routes: Routes = [];\n"); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nng serve\n# or: npm start\n\`\`\`\n\n## Build\n\`\`\`bash\nng build\n\`\`\`\n\nOpen in VS Code with Angular Language Service extension.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n.angular/\n"); } /* --- Python --- */ function buildPython(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var reqMap={"numpy":"numpy","pandas":"pandas","sklearn":"scikit-learn","tensorflow":"tensorflow","torch":"torch","flask":"flask","fastapi":"fastapi","uvicorn":"uvicorn","requests":"requests","sqlalchemy":"sqlalchemy","pydantic":"pydantic","dotenv":"python-dotenv","PIL":"Pillow","cv2":"opencv-python","matplotlib":"matplotlib","seaborn":"seaborn","scipy":"scipy"}; var reqs=[]; Object.keys(reqMap).forEach(function(k){if(src.indexOf("import "+k)>=0||src.indexOf("from "+k)>=0)reqs.push(reqMap[k]);}); var reqsTxt=reqs.length?reqs.join("\n"):"# add dependencies here\n"; zip.file(folder+"main.py",src||"# "+title+"\n# Generated by PantheraHive BOS\n\nprint(title+\" loaded\")\n"); zip.file(folder+"requirements.txt",reqsTxt); zip.file(folder+".env.example","# Environment variables\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\`\`\`\n\n## Run\n\`\`\`bash\npython main.py\n\`\`\`\n"); zip.file(folder+".gitignore",".venv/\n__pycache__/\n*.pyc\n.env\n.DS_Store\n"); } /* --- Node.js --- */ function buildNode(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var depMap={"mongoose":"^8.0.0","dotenv":"^16.4.5","axios":"^1.7.9","cors":"^2.8.5","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2","socket.io":"^4.7.4","uuid":"^9.0.1","zod":"^3.22.4","express":"^4.18.2"}; var deps={}; Object.keys(depMap).forEach(function(k){if(src.indexOf(k)>=0)deps[k]=depMap[k];}); if(!deps["express"])deps["express"]="^4.18.2"; var pkgJson=JSON.stringify({"name":pn,"version":"1.0.0","main":"src/index.js","scripts":{"start":"node src/index.js","dev":"nodemon src/index.js"},"dependencies":deps,"devDependencies":{"nodemon":"^3.0.3"}},null,2)+"\n"; zip.file(folder+"package.json",pkgJson); var fallback="const express=require(\"express\");\nconst app=express();\napp.use(express.json());\n\napp.get(\"/\",(req,res)=>{\n res.json({message:\""+title+" API\"});\n});\n\nconst PORT=process.env.PORT||3000;\napp.listen(PORT,()=>console.log(\"Server on port \"+PORT));\n"; zip.file(folder+"src/index.js",src||fallback); zip.file(folder+".env.example","PORT=3000\n"); zip.file(folder+".gitignore","node_modules/\n.env\n.DS_Store\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\n\`\`\`\n\n## Run\n\`\`\`bash\nnpm run dev\n\`\`\`\n"); } /* --- Vanilla HTML --- */ function buildVanillaHtml(zip,folder,app,code){ var title=slugTitle(app); var isFullDoc=code.trim().toLowerCase().indexOf("=0||code.trim().toLowerCase().indexOf("=0; var indexHtml=isFullDoc?code:"\n\n\n\n\n"+title+"\n\n\n\n"+code+"\n\n\n\n"; zip.file(folder+"index.html",indexHtml); zip.file(folder+"style.css","/* "+title+" — styles */\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#fff;color:#1a1a2e}\n"); zip.file(folder+"script.js","/* "+title+" — scripts */\n"); zip.file(folder+"assets/.gitkeep",""); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Open\nDouble-click \`index.html\` in your browser.\n\nOr serve locally:\n\`\`\`bash\nnpx serve .\n# or\npython3 -m http.server 3000\n\`\`\`\n"); zip.file(folder+".gitignore",".DS_Store\nnode_modules/\n.env\n"); } /* ===== MAIN ===== */ var sc=document.createElement("script"); sc.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"; sc.onerror=function(){ if(lbl)lbl.textContent="Download ZIP"; alert("JSZip load failed — check connection."); }; sc.onload=function(){ var zip=new JSZip(); var base=(_phFname||"output").replace(/\.[^.]+$/,""); var app=base.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; var folder=app+"/"; var vc=document.getElementById("panel-content"); var panelTxt=vc?(vc.innerText||vc.textContent||""):""; var lang=detectLang(_phCode,panelTxt); if(_phIsHtml){ buildVanillaHtml(zip,folder,app,_phCode); } else if(lang==="flutter"){ buildFlutter(zip,folder,app,_phCode,panelTxt); } else if(lang==="react-native"){ buildReactNative(zip,folder,app,_phCode,panelTxt); } else if(lang==="swift"){ buildSwift(zip,folder,app,_phCode,panelTxt); } else if(lang==="kotlin"){ buildKotlin(zip,folder,app,_phCode,panelTxt); } else if(lang==="react"){ buildReact(zip,folder,app,_phCode,panelTxt); } else if(lang==="vue"){ buildVue(zip,folder,app,_phCode,panelTxt); } else if(lang==="angular"){ buildAngular(zip,folder,app,_phCode,panelTxt); } else if(lang==="python"){ buildPython(zip,folder,app,_phCode); } else if(lang==="node"){ buildNode(zip,folder,app,_phCode); } else { /* Document/content workflow */ var title=app.replace(/_/g," "); var md=_phAll||_phCode||panelTxt||"No content"; zip.file(folder+app+".md",md); var h=""+title+""; h+="

"+title+"

"; var hc=md.replace(/&/g,"&").replace(//g,">"); hc=hc.replace(/^### (.+)$/gm,"

$1

"); hc=hc.replace(/^## (.+)$/gm,"

$1

"); hc=hc.replace(/^# (.+)$/gm,"

$1

"); hc=hc.replace(/\*\*(.+?)\*\*/g,"$1"); hc=hc.replace(/\n{2,}/g,"

"); h+="

"+hc+"

Generated by PantheraHive BOS
"; zip.file(folder+app+".html",h); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\nFiles:\n- "+app+".md (Markdown)\n- "+app+".html (styled HTML)\n"); } zip.generateAsync({type:"blob"}).then(function(blob){ var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=app+".zip"; a.click(); URL.revokeObjectURL(a.href); if(lbl)lbl.textContent="Download ZIP"; }); }; document.head.appendChild(sc); } function phShare(){navigator.clipboard.writeText(window.location.href).then(function(){var el=document.getElementById("ph-share-lbl");if(el){el.textContent="Link copied!";setTimeout(function(){el.textContent="Copy share link";},2500);}});}function phEmbed(){var runId=window.location.pathname.split("/").pop().replace(".html","");var embedUrl="https://pantherahive.com/embed/"+runId;var code='';navigator.clipboard.writeText(code).then(function(){var el=document.getElementById("ph-embed-lbl");if(el){el.textContent="Embed code copied!";setTimeout(function(){el.textContent="Get Embed Code";},2500);}});}