Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 22 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# OpenQueue Environment Variables
# Copy this file to .env and fill in your values

# Database connection string from Supabase
# Format: postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres
DATABASE_URL=

# Environment (dev, test, prod)
OPENQUEUE_ENV=prod

# Optional: HMAC secret for token hashing (recommended for production)
# If not set, tokens are hashed with plain SHA-256
OPENQUEUE_TOKEN_HMAC_SECRET=

# Rate limiting (disable for serverless, enable for single-instance)
OPENQUEUE_RATE_LIMIT_ENABLED=false

# CORS origins (comma-separated)
OPENQUEUE_CORS_ORIGINS=*

# Log level
OPENQUEUE_LOG_LEVEL=INFO
33 changes: 33 additions & 0 deletions .github/workflows/opencode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: opencode

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]

jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/minimax-m2.5-free
10 changes: 10 additions & 0 deletions api/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Vercel Python API entrypoint.

This module exposes the ASGI app for Vercel serverless deployment.
Vercel automatically handles the ASGI -> WSGI conversion.
"""

from app.fastapi_app import app

handler = app
5 changes: 4 additions & 1 deletion app/core/app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
StructuredLoggingMiddleware,
)
from ..routers import dashboard, jobs, observability, workers
from ..settings import get_settings


def create_app(*, title: str = "OpenQueue", version: str = "0.0.1") -> FastAPI:
Expand Down Expand Up @@ -64,9 +65,11 @@ def create_app(*, title: str = "OpenQueue", version: str = "0.0.1") -> FastAPI:
)

# CORS middleware
settings = get_settings()
cors_origins = [o.strip() for o in settings.cors_origins.split(",")]
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand Down
5 changes: 5 additions & 0 deletions app/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from .auth import CurrentUser, get_current_user
from .rate_limit import DEFAULT_LIMITS, RateLimiter, RateLimitExceeded
from .settings import get_settings

"""
Shared FastAPI dependencies and helpers.
Expand Down Expand Up @@ -82,6 +83,10 @@ async def _dep(
request: Request,
user: AuthUserDep,
) -> None:
settings = get_settings()
if not settings.rate_limit_enabled:
return

principal = get_rate_limit_principal(user, request)
try:
_rate_limiter.consume(principal_key=principal, action=action, tokens=tokens)
Expand Down
10 changes: 10 additions & 0 deletions app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ class Settings(BaseSettings):
description="Application log level (e.g. DEBUG, INFO, WARNING).",
)

# -------------------------
# CORS
# -------------------------

cors_origins: str = Field(
default="*",
alias="OPENQUEUE_CORS_ORIGINS",
description="Comma-separated list of allowed CORS origins. Use '*' for all.",
)

# -------------------------
# Maintenance
# -------------------------
Expand Down
1 change: 1 addition & 0 deletions runtime.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-3.12
58 changes: 58 additions & 0 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# OpenQueue TypeScript SDK

TypeScript client for OpenQueue.

## Installation

```bash
npm install @ravin-d-27/openqueue
```

## Usage

### As a Producer

```typescript
import { OpenQueue } from "@ravin-d-27/openqueue";

const client = new OpenQueue("https://queue.example.com", "your-api-token");

const jobId = await client.enqueue("my-queue", { task: "do_something" });
console.log(`Enqueued job: ${jobId}`);
```

### As a Worker

```typescript
import { OpenQueue } from "@ravin-d-27/openqueue";

const client = new OpenQueue("https://queue.example.com", "your-api-token");

const leased = await client.lease("my-queue", "worker-1");
if (leased) {
console.log(`Processing job: ${leased.job.id}`);
await client.ack(leased.job.id, leased.lease_token, { result: { done: true } });
}
```

## API

### Producer Methods

- `enqueue(queueName, payload, options?)` - Enqueue a new job
- `enqueueBatch(jobs)` - Enqueue multiple jobs
- `getStatus(jobId)` - Get job status
- `getJob(jobId)` - Get full job details
- `listJobs(options?)` - List jobs with filters
- `cancelJob(jobId)` - Cancel a pending job

### Worker Methods

- `lease(queueName, workerId, options?)` - Lease next available job
- `ack(jobId, leaseToken, options?)` - Acknowledge job completion
- `nack(jobId, leaseToken, error, options?)` - Report job failure
- `heartbeat(jobId, leaseToken, options?)` - Send heartbeat to extend lease

### Dashboard Methods

- `getQueueStats()` - Get queue statistics
25 changes: 25 additions & 0 deletions sdk/typescript/examples/producer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { OpenQueue } from "@ravin-d-27/openqueue";

const client = new OpenQueue("http://localhost:8000", "your-api-token");

async function main() {
const jobId = await client.enqueue("my-queue", { task: "do_something" });
console.log(`Enqueued job: ${jobId}`);

const jobs = await client.enqueueBatch([
{ queue_name: "my-queue", payload: { task: "job1" } },
{ queue_name: "my-queue", payload: { task: "job2" }, priority: 10 },
]);
console.log(`Enqueued batch: ${jobs.join(", ")}`);

const status = await client.getStatus(jobId);
console.log(`Job status: ${status}`);

const job = await client.getJob(jobId);
console.log(`Job details: ${JSON.stringify(job)}`);

const list = await client.listJobs({ queue_name: "my-queue", limit: 10 });
console.log(`Found ${list.total} jobs`);
}

main().catch(console.error);
35 changes: 35 additions & 0 deletions sdk/typescript/examples/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { OpenQueue } from "@ravin-d-27/openqueue";

const client = new OpenQueue("http://localhost:8000", "your-api-token");

async function processJob() {
while (true) {
const leased = await client.lease("my-queue", "worker-1");

if (!leased) {
await new Promise((resolve) => setTimeout(resolve, 1000));
continue;
}

console.log(`Processing job: ${leased.job.id}`);

try {
const result = await client.ack(
leased.job.id,
leased.lease_token,
{ result: { success: true } }
);
console.log(`Job acknowledged: ${result}`);
} catch (error) {
console.error(`Failed to ack job: ${error}`);
await client.nack(
leased.job.id,
leased.lease_token,
"Processing failed",
{ retry: true }
);
}
}
}

processJob().catch(console.error);
1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/esbuild

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/rollup

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/sucrase

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/sucrase-node

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/tree-kill

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/tsc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/tsserver

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/tsup

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/tsup-node

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/vite

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/vite-node

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/vitest

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/node_modules/.bin/why-is-node-running

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading